mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
Integrate API for displaying featured images (#1456)
* Integrate API for displaying featured images * Add pagination and refactor code so that it can be reused for category images * Add license info to the images * Fix author view * Remove unused values * Fix minor issues with featured images * Fix null license url issue * Remove some log lines * Fix back navigation issue * fix tests * fix test inits * Gracefully handling various error situations * Added java docs
This commit is contained in:
parent
96eae8c0a6
commit
9845a6265d
26 changed files with 953 additions and 309 deletions
|
|
@ -49,6 +49,8 @@ dependencies {
|
||||||
implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.0.0'
|
implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.0.0'
|
||||||
implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0'
|
implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0'
|
||||||
|
|
||||||
|
implementation 'org.jsoup:jsoup:1.11.3'
|
||||||
|
|
||||||
implementation 'com.facebook.fresco:fresco:1.5.0'
|
implementation 'com.facebook.fresco:fresco:1.5.0'
|
||||||
implementation 'com.facebook.stetho:stetho:1.5.0'
|
implementation 'com.facebook.stetho:stetho:1.5.0'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,8 +92,9 @@
|
||||||
android:label="@string/navigation_item_notification" />
|
android:label="@string/navigation_item_notification" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".featured.FeaturedImagesActivity"
|
android:name=".category.CategoryImagesActivity"
|
||||||
android:label="@string/title_activity_featured_images" />
|
android:label="@string/title_activity_featured_images"
|
||||||
|
android:parentActivityName=".contributions.ContributionsActivity" />
|
||||||
|
|
||||||
<service android:name=".upload.UploadService" />
|
<service android:name=".upload.UploadService" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class CategoryImageController {
|
||||||
|
|
||||||
|
private MediaWikiApi mediaWikiApi;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CategoryImageController(MediaWikiApi mediaWikiApi) {
|
||||||
|
this.mediaWikiApi = mediaWikiApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a category name as input and calls the API to get a list of images for that category
|
||||||
|
* @param categoryName
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<Media> getCategoryImages(String categoryName) {
|
||||||
|
return mediaWikiApi.getCategoryImages(categoryName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,225 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class CategoryImageUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method iterates over the child nodes to return a list of Media objects
|
||||||
|
* @param childNodes
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static List<Media> getMediaList(NodeList childNodes) {
|
||||||
|
List<Media> categoryImages = new ArrayList<>();
|
||||||
|
for (int i = 0; i < childNodes.getLength(); i++) {
|
||||||
|
Node node = childNodes.item(i);
|
||||||
|
categoryImages.add(getMediaFromPage(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return categoryImages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Media object from the XML response as received by the API
|
||||||
|
* @param node
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static Media getMediaFromPage(Node node) {
|
||||||
|
Media media = new Media(null,
|
||||||
|
getImageUrl(node),
|
||||||
|
getFileName(node),
|
||||||
|
getDescription(node),
|
||||||
|
getDataLength(node),
|
||||||
|
getDateCreated(node),
|
||||||
|
getDateCreated(node),
|
||||||
|
getCreator(node)
|
||||||
|
);
|
||||||
|
|
||||||
|
media.setLicense(getLicense(node));
|
||||||
|
|
||||||
|
return media;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the filename of the uploaded image
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static String getFileName(Node document) {
|
||||||
|
Element element = (Element) document;
|
||||||
|
return element.getAttribute("title");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the image description for that particular upload
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static String getDescription(Node document) {
|
||||||
|
return getMetaDataValue(document, "ImageDescription");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts license information from the image meta data
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static String getLicense(Node document) {
|
||||||
|
return getMetaDataValue(document, "License");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parsed value of artist from the response
|
||||||
|
* The artist information is returned as a HTML string from the API. Jsoup library parses the HTML string
|
||||||
|
* to extract just the text value
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static String getCreator(Node document) {
|
||||||
|
String artist = getMetaDataValue(document, "Artist");
|
||||||
|
if (artist != null) {
|
||||||
|
return Jsoup.parse(artist).text();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parsed date of creation of the image
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static Date getDateCreated(Node document) {
|
||||||
|
String dateTime = getMetaDataValue(document, "DateTime");
|
||||||
|
if (dateTime != null && !dateTime.equals("")) {
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
try {
|
||||||
|
return format.parse(dateTime);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
Timber.d("Error occurred while parsing date %s", dateTime);
|
||||||
|
return new Date();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param document
|
||||||
|
* @return Returns the url attribute from the imageInfo node
|
||||||
|
*/
|
||||||
|
private static String getImageUrl(Node document) {
|
||||||
|
Element element = (Element) getImageInfo(document);
|
||||||
|
if (element != null) {
|
||||||
|
return element.getAttribute("url");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes the node document and gives out the attribute length from the node document
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static long getDataLength(Node document) {
|
||||||
|
Element element = (Element) document;
|
||||||
|
if (element != null) {
|
||||||
|
String length = element.getAttribute("length");
|
||||||
|
if (length != null && !length.equals("")) {
|
||||||
|
return Long.parseLong(length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic method to get the value of any meta as returned by the getMetaData function
|
||||||
|
* @param document node document as returned by API
|
||||||
|
* @param metaName the name of meta node to be returned
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static String getMetaDataValue(Node document, String metaName) {
|
||||||
|
Element metaData = getMetaData(document, metaName);
|
||||||
|
if (metaData != null) {
|
||||||
|
return metaData.getAttribute("value");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic method to return an element taking the node document and metaName as input
|
||||||
|
* @param document node document as returned by API
|
||||||
|
* @param metaName the name of meta node to be returned
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static Element getMetaData(Node document, String metaName) {
|
||||||
|
Node extraMetaData = getExtraMetaData(document);
|
||||||
|
if (extraMetaData != null) {
|
||||||
|
Node node = getNode(extraMetaData, metaName);
|
||||||
|
if (node != null) {
|
||||||
|
return (Element) node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts extmetadata from the response XML
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static Node getExtraMetaData(Node document) {
|
||||||
|
Node imageInfo = getImageInfo(document);
|
||||||
|
if (imageInfo != null) {
|
||||||
|
return getNode(imageInfo, "extmetadata");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the ii node from the imageinfo node
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static Node getImageInfo(Node document) {
|
||||||
|
Node imageInfo = getNode(document, "imageinfo");
|
||||||
|
if (imageInfo != null) {
|
||||||
|
return getNode(imageInfo, "ii");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a parent node as input and returns a child node if present
|
||||||
|
* @param node parent node
|
||||||
|
* @param nodeName child node name
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static Node getNode(Node node, String nodeName) {
|
||||||
|
NodeList childNodes = node.getChildNodes();
|
||||||
|
for (int i = 0; i < childNodes.getLength(); i++) {
|
||||||
|
Node nodeItem = childNodes.item(i);
|
||||||
|
Element item = (Element) nodeItem;
|
||||||
|
if (item.getTagName().equals(nodeName)) {
|
||||||
|
return nodeItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.database.DataSetObserver;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.support.v4.app.FragmentTransaction;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
||||||
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This activity displays pictures of a particular category
|
||||||
|
* Its generic and simply takes the name of category name in its start intent to load all images in
|
||||||
|
* a particular category. This activity is currently being used to display a list of featured images,
|
||||||
|
* which is nothing but another category on wikimedia commons.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class CategoryImagesActivity
|
||||||
|
extends AuthenticatedActivity
|
||||||
|
implements FragmentManager.OnBackStackChangedListener,
|
||||||
|
MediaDetailPagerFragment.MediaDetailProvider,
|
||||||
|
AdapterView.OnItemClickListener{
|
||||||
|
|
||||||
|
|
||||||
|
private FragmentManager supportFragmentManager;
|
||||||
|
private CategoryImagesListFragment categoryImagesListFragment;
|
||||||
|
private MediaDetailPagerFragment mediaDetails;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onAuthCookieAcquired(String authCookie) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onAuthFailure() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_category_images);
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
|
// Activity can call methods in the fragment by acquiring a
|
||||||
|
// reference to the Fragment from FragmentManager, using findFragmentById()
|
||||||
|
supportFragmentManager = getSupportFragmentManager();
|
||||||
|
setCategoryImagesFragment();
|
||||||
|
supportFragmentManager.addOnBackStackChangedListener(this);
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
mediaDetails = (MediaDetailPagerFragment) supportFragmentManager
|
||||||
|
.findFragmentById(R.id.fragmentContainer);
|
||||||
|
|
||||||
|
}
|
||||||
|
requestAuthToken();
|
||||||
|
initDrawer();
|
||||||
|
setPageTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the categoryName from the intent and initializes the fragment for showing images of that category
|
||||||
|
*/
|
||||||
|
private void setCategoryImagesFragment() {
|
||||||
|
categoryImagesListFragment = new CategoryImagesListFragment();
|
||||||
|
String categoryName = getIntent().getStringExtra("categoryName");
|
||||||
|
if (getIntent() != null && categoryName != null) {
|
||||||
|
Bundle arguments = new Bundle();
|
||||||
|
arguments.putString("categoryName", categoryName);
|
||||||
|
categoryImagesListFragment.setArguments(arguments);
|
||||||
|
FragmentTransaction transaction = supportFragmentManager.beginTransaction();
|
||||||
|
transaction
|
||||||
|
.add(R.id.fragmentContainer, categoryImagesListFragment)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the passed title from the intents and displays it as the page title
|
||||||
|
*/
|
||||||
|
private void setPageTitle() {
|
||||||
|
if (getIntent() != null && getIntent().getStringExtra("title") != null) {
|
||||||
|
setTitle(getIntent().getStringExtra("title"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackStackChanged() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||||
|
if (mediaDetails == null || !mediaDetails.isVisible()) {
|
||||||
|
// set isFeaturedImage true for featured images, to include author field on media detail
|
||||||
|
mediaDetails = new MediaDetailPagerFragment(false, true);
|
||||||
|
FragmentManager supportFragmentManager = getSupportFragmentManager();
|
||||||
|
supportFragmentManager
|
||||||
|
.beginTransaction()
|
||||||
|
.replace(R.id.fragmentContainer, mediaDetails)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit();
|
||||||
|
supportFragmentManager.executePendingTransactions();
|
||||||
|
}
|
||||||
|
mediaDetails.showImage(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consumers should be simply using this method to use this activity.
|
||||||
|
* @param context
|
||||||
|
* @param title Page title
|
||||||
|
* @param categoryName Name of the category for displaying its images
|
||||||
|
*/
|
||||||
|
public static void startYourself(Context context, String title, String categoryName) {
|
||||||
|
Intent intent = new Intent(context, CategoryImagesActivity.class);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
||||||
|
intent.putExtra("title", title);
|
||||||
|
intent.putExtra("categoryName", categoryName);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Media getMediaAtPosition(int i) {
|
||||||
|
if (categoryImagesListFragment.getAdapter() == null) {
|
||||||
|
// not yet ready to return data
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return (Media) categoryImagesListFragment.getAdapter().getItem(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTotalMediaCount() {
|
||||||
|
if (categoryImagesListFragment.getAdapter() == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return categoryImagesListFragment.getAdapter().getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyDatasetChanged() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerDataSetObserver(DataSetObserver observer) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,227 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.AbsListView;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.GridView;
|
||||||
|
import android.widget.ListAdapter;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import dagger.android.support.DaggerFragment;
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays images for a particular category with load more on scrolling incorporated
|
||||||
|
*/
|
||||||
|
public class CategoryImagesListFragment extends DaggerFragment {
|
||||||
|
|
||||||
|
private static int TIMEOUT_SECONDS = 15;
|
||||||
|
|
||||||
|
private GridViewAdapter gridAdapter;
|
||||||
|
|
||||||
|
@BindView(R.id.statusMessage)
|
||||||
|
TextView statusTextView;
|
||||||
|
@BindView(R.id.loadingImagesProgressBar) ProgressBar progressBar;
|
||||||
|
@BindView(R.id.categoryImagesList) GridView gridView;
|
||||||
|
|
||||||
|
private boolean hasMoreImages = true;
|
||||||
|
private boolean isLoading;
|
||||||
|
private String categoryName = null;
|
||||||
|
|
||||||
|
@Inject CategoryImageController controller;
|
||||||
|
@Inject @Named("category_prefs") SharedPreferences categoryPreferences;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View v = inflater.inflate(R.layout.fragment_category_images, container, false);
|
||||||
|
ButterKnife.bind(this, v);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
gridView.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity());
|
||||||
|
initViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the UI elements for the fragment
|
||||||
|
* Setup the grid view to and scroll listener for it
|
||||||
|
*/
|
||||||
|
private void initViews() {
|
||||||
|
String categoryName = getArguments().getString("categoryName");
|
||||||
|
if (getArguments() != null && categoryName != null) {
|
||||||
|
this.categoryName = categoryName;
|
||||||
|
resetQueryContinueValues(categoryName);
|
||||||
|
initList();
|
||||||
|
setScrollListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query continue values determine the last page that was loaded for the particular keyword
|
||||||
|
* This method resets those values, so that the results can be queried from the first page itself
|
||||||
|
* @param keyword
|
||||||
|
*/
|
||||||
|
private void resetQueryContinueValues(String keyword) {
|
||||||
|
SharedPreferences.Editor editor = categoryPreferences.edit();
|
||||||
|
editor.remove(keyword);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for internet connection and then initializes the grid view with first 10 images of that category
|
||||||
|
*/
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
private void initList() {
|
||||||
|
if(!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||||
|
handleNoInternet();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading = true;
|
||||||
|
progressBar.setVisibility(VISIBLE);
|
||||||
|
Observable.fromCallable(() -> controller.getCategoryImages(categoryName))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
.subscribe(this::handleSuccess, this::handleError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the UI updates for no internet scenario
|
||||||
|
*/
|
||||||
|
private void handleNoInternet() {
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
if (gridAdapter == null || gridAdapter.isEmpty()) {
|
||||||
|
statusTextView.setVisibility(VISIBLE);
|
||||||
|
statusTextView.setText(getString(R.string.no_internet));
|
||||||
|
} else {
|
||||||
|
ViewUtil.showSnackbar(gridView, R.string.no_internet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs and handles API error scenario
|
||||||
|
* @param throwable
|
||||||
|
*/
|
||||||
|
private void handleError(Throwable throwable) {
|
||||||
|
Timber.e(throwable, "Error occurred while loading featured images");
|
||||||
|
initErrorView();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the UI updates for a error scenario
|
||||||
|
*/
|
||||||
|
private void initErrorView() {
|
||||||
|
ViewUtil.showSnackbar(gridView, R.string.error_loading_images);
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
if (gridAdapter == null || gridAdapter.isEmpty()) {
|
||||||
|
statusTextView.setVisibility(VISIBLE);
|
||||||
|
statusTextView.setText(getString(R.string.no_images_found));
|
||||||
|
} else {
|
||||||
|
statusTextView.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the adapter with a list of Media objects
|
||||||
|
* @param mediaList
|
||||||
|
*/
|
||||||
|
private void setAdapter(List<Media> mediaList) {
|
||||||
|
gridAdapter = new GridViewAdapter(this.getContext(), R.layout.layout_category_images, mediaList);
|
||||||
|
gridView.setAdapter(gridAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the scroll listener for the grid view so that more images are fetched when the user scrolls down
|
||||||
|
* Checks if the category has more images before loading
|
||||||
|
* Also checks whether images are currently being fetched before triggering another request
|
||||||
|
*/
|
||||||
|
private void setScrollListener() {
|
||||||
|
gridView.setOnScrollListener(new AbsListView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(AbsListView view, int scrollState) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
|
||||||
|
if (hasMoreImages && !isLoading && (firstVisibleItem + visibleItemCount + 1 >= totalItemCount)) {
|
||||||
|
isLoading = true;
|
||||||
|
fetchMoreImages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches more images for the category and adds it to the grid view adapter
|
||||||
|
*/
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
private void fetchMoreImages() {
|
||||||
|
if(!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||||
|
handleNoInternet();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
progressBar.setVisibility(VISIBLE);
|
||||||
|
Observable.fromCallable(() -> controller.getCategoryImages(categoryName))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
.subscribe(this::handleSuccess, this::handleError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the success scenario
|
||||||
|
* On first load, it initializes the grid view. On subsequent loads, it adds items to the adapter
|
||||||
|
* @param collection
|
||||||
|
*/
|
||||||
|
private void handleSuccess(List<Media> collection) {
|
||||||
|
if(collection == null || collection.isEmpty()) {
|
||||||
|
initErrorView();
|
||||||
|
hasMoreImages = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(gridAdapter == null) {
|
||||||
|
setAdapter(collection);
|
||||||
|
} else {
|
||||||
|
gridAdapter.addItems(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
isLoading = false;
|
||||||
|
statusTextView.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListAdapter getAdapter() {
|
||||||
|
return gridView.getAdapter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import fr.free.nrw.commons.MediaWikiImageView;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is created to only display UI implementation. Needs to be changed in real implementation
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class GridViewAdapter extends ArrayAdapter {
|
||||||
|
private Context context;
|
||||||
|
private List<Media> data;
|
||||||
|
|
||||||
|
public GridViewAdapter(Context context, int layoutResourceId, List<Media> data) {
|
||||||
|
super(context, layoutResourceId, data);
|
||||||
|
this.context = context;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds more item to the list
|
||||||
|
* Its triggered on scrolling down in the list
|
||||||
|
* @param images
|
||||||
|
*/
|
||||||
|
public void addItems(List<Media> images) {
|
||||||
|
if (data == null) {
|
||||||
|
data = new ArrayList<>();
|
||||||
|
}
|
||||||
|
data.addAll(images);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return data == null || data.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the UI for the category image item
|
||||||
|
* @param position
|
||||||
|
* @param convertView
|
||||||
|
* @param parent
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
|
||||||
|
if (convertView == null) {
|
||||||
|
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
|
||||||
|
convertView = inflater.inflate(R.layout.layout_category_images, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Media item = data.get(position);
|
||||||
|
MediaWikiImageView imageView = convertView.findViewById(R.id.categoryImageView);
|
||||||
|
TextView fileName = convertView.findViewById(R.id.categoryImageTitle);
|
||||||
|
TextView author = convertView.findViewById(R.id.categoryImageAuthor);
|
||||||
|
fileName.setText(item.getFilename());
|
||||||
|
setAuthorView(item, author);
|
||||||
|
imageView.setMedia(item);
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows author information if its present
|
||||||
|
* @param item
|
||||||
|
* @param author
|
||||||
|
*/
|
||||||
|
private void setAuthorView(Media item, TextView author) {
|
||||||
|
if (item.getCreator() != null && !item.getCreator().equals("")) {
|
||||||
|
String uploadedByTemplate = context.getString(R.string.image_uploaded_by);
|
||||||
|
author.setText(String.format(uploadedByTemplate, item.getCreator()));
|
||||||
|
} else {
|
||||||
|
author.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For APIs that return paginated responses, MediaWiki APIs uses the QueryContinue to facilitate fetching of subsequent pages
|
||||||
|
* https://www.mediawiki.org/wiki/API:Raw_query_continue
|
||||||
|
*/
|
||||||
|
public class QueryContinue {
|
||||||
|
private String continueParam;
|
||||||
|
private String gcmContinueParam;
|
||||||
|
|
||||||
|
public QueryContinue(String continueParam, String gcmContinueParam) {
|
||||||
|
this.continueParam = continueParam;
|
||||||
|
this.gcmContinueParam = gcmContinueParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGcmContinueParam() {
|
||||||
|
return gcmContinueParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContinueParam() {
|
||||||
|
return continueParam;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -7,7 +7,7 @@ import fr.free.nrw.commons.WelcomeActivity;
|
||||||
import fr.free.nrw.commons.auth.LoginActivity;
|
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.contributions.ContributionsActivity;
|
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||||
import fr.free.nrw.commons.featured.FeaturedImagesActivity;
|
import fr.free.nrw.commons.category.CategoryImagesActivity;
|
||||||
import fr.free.nrw.commons.nearby.NearbyActivity;
|
import fr.free.nrw.commons.nearby.NearbyActivity;
|
||||||
import fr.free.nrw.commons.notification.NotificationActivity;
|
import fr.free.nrw.commons.notification.NotificationActivity;
|
||||||
import fr.free.nrw.commons.settings.SettingsActivity;
|
import fr.free.nrw.commons.settings.SettingsActivity;
|
||||||
|
|
@ -49,5 +49,5 @@ public abstract class ActivityBuilderModule {
|
||||||
abstract NotificationActivity bindNotificationActivity();
|
abstract NotificationActivity bindNotificationActivity();
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract FeaturedImagesActivity bindFeaturedImagesActivity();
|
abstract CategoryImagesActivity bindFeaturedImagesActivity();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import android.content.SharedPreferences;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.v4.util.LruCache;
|
import android.support.v4.util.LruCache;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
|
@ -85,6 +87,17 @@ public class CommonsApplicationModule {
|
||||||
return context.getSharedPreferences("prefs", MODE_PRIVATE);
|
return context.getSharedPreferences("prefs", MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param context
|
||||||
|
* @return returns categoryPrefs
|
||||||
|
*/
|
||||||
|
@Provides
|
||||||
|
@Named("category_prefs")
|
||||||
|
public SharedPreferences providesCategorySharedPreferences(Context context) {
|
||||||
|
return context.getSharedPreferences("categoryPrefs", MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Named("direct_nearby_upload_prefs")
|
@Named("direct_nearby_upload_prefs")
|
||||||
public SharedPreferences providesDirectNearbyUploadPreferences(Context context) {
|
public SharedPreferences providesDirectNearbyUploadPreferences(Context context) {
|
||||||
|
|
@ -106,8 +119,11 @@ public class CommonsApplicationModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
public MediaWikiApi provideMediaWikiApi(Context context, @Named("default_preferences") SharedPreferences sharedPreferences) {
|
public MediaWikiApi provideMediaWikiApi(Context context,
|
||||||
return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, sharedPreferences);
|
@Named("default_preferences") SharedPreferences defaultPreferences,
|
||||||
|
@Named("category_prefs") SharedPreferences categoryPrefs,
|
||||||
|
Gson gson) {
|
||||||
|
return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, defaultPreferences, categoryPrefs, gson);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
@ -116,6 +132,16 @@ public class CommonsApplicationModule {
|
||||||
return new LocationServiceManager(context);
|
return new LocationServiceManager(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gson objects are very heavy. The app should ideally be using just one instance of it instead of creating new instances everywhere.
|
||||||
|
* @return returns a singleton Gson instance
|
||||||
|
*/
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public Gson provideGson() {
|
||||||
|
return new Gson();
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
public CacheController provideCacheController() {
|
public CacheController provideCacheController() {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import dagger.Module;
|
||||||
import dagger.android.ContributesAndroidInjector;
|
import dagger.android.ContributesAndroidInjector;
|
||||||
import fr.free.nrw.commons.category.CategorizationFragment;
|
import fr.free.nrw.commons.category.CategorizationFragment;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsListFragment;
|
import fr.free.nrw.commons.contributions.ContributionsListFragment;
|
||||||
import fr.free.nrw.commons.featured.FeaturedImagesListFragment;
|
import fr.free.nrw.commons.category.CategoryImagesListFragment;
|
||||||
import fr.free.nrw.commons.media.MediaDetailFragment;
|
import fr.free.nrw.commons.media.MediaDetailFragment;
|
||||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
import fr.free.nrw.commons.nearby.NearbyListFragment;
|
import fr.free.nrw.commons.nearby.NearbyListFragment;
|
||||||
|
|
@ -49,6 +49,6 @@ public abstract class FragmentBuilderModule {
|
||||||
abstract SingleUploadFragment bindSingleUploadFragment();
|
abstract SingleUploadFragment bindSingleUploadFragment();
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract FeaturedImagesListFragment bindFeaturedImagesListFragment();
|
abstract CategoryImagesListFragment bindFeaturedImagesListFragment();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
package fr.free.nrw.commons.featured;
|
|
||||||
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.Media;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object to hold FeaturedImage
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class FeaturedImage {
|
|
||||||
private Media image;
|
|
||||||
private String author;
|
|
||||||
private String fileName;
|
|
||||||
|
|
||||||
public FeaturedImage(Media image, String author, String fileName) {
|
|
||||||
this.image = image;
|
|
||||||
this.author = author;
|
|
||||||
this.fileName = fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Media getImage() {
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setImage(Media image) {
|
|
||||||
this.image = image;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAuthor() {
|
|
||||||
return author;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAuthor(String author) {
|
|
||||||
this.author = author;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFileName() {
|
|
||||||
return fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFileName(String fileName) {
|
|
||||||
this.fileName = fileName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
package fr.free.nrw.commons.featured;
|
|
||||||
|
|
||||||
import android.database.DataSetObserver;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v4.app.FragmentManager;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import fr.free.nrw.commons.Media;
|
|
||||||
import fr.free.nrw.commons.R;
|
|
||||||
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
|
||||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This activity displays pic of the days of last xx days
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class FeaturedImagesActivity
|
|
||||||
extends AuthenticatedActivity
|
|
||||||
implements FragmentManager.OnBackStackChangedListener,
|
|
||||||
MediaDetailPagerFragment.MediaDetailProvider,
|
|
||||||
AdapterView.OnItemClickListener{
|
|
||||||
|
|
||||||
private FeaturedImagesListFragment featuredImagesListFragment;
|
|
||||||
private MediaDetailPagerFragment mediaDetails;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onAuthCookieAcquired(String authCookie) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onAuthFailure() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_featured_images);
|
|
||||||
ButterKnife.bind(this);
|
|
||||||
|
|
||||||
// Activity can call methods in the fragment by acquiring a
|
|
||||||
// reference to the Fragment from FragmentManager, using findFragmentById()
|
|
||||||
FragmentManager supportFragmentManager = getSupportFragmentManager();
|
|
||||||
featuredImagesListFragment = (FeaturedImagesListFragment)supportFragmentManager
|
|
||||||
.findFragmentById(R.id.featuedListFragment);
|
|
||||||
|
|
||||||
supportFragmentManager.addOnBackStackChangedListener(this);
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
mediaDetails = (MediaDetailPagerFragment)supportFragmentManager
|
|
||||||
.findFragmentById(R.id.featuredFragmentContainer);
|
|
||||||
|
|
||||||
}
|
|
||||||
requestAuthToken();
|
|
||||||
initDrawer();
|
|
||||||
setTitle(getString(R.string.title_activity_featured_images));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBackStackChanged() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
|
||||||
if (mediaDetails == null || !mediaDetails.isVisible()) {
|
|
||||||
// set isFeaturedImage true for featured images, to include author field on media detail
|
|
||||||
mediaDetails = new MediaDetailPagerFragment(false, true);
|
|
||||||
FragmentManager supportFragmentManager = getSupportFragmentManager();
|
|
||||||
supportFragmentManager
|
|
||||||
.beginTransaction()
|
|
||||||
.replace(R.id.featuredFragmentContainer, mediaDetails)
|
|
||||||
.addToBackStack(null)
|
|
||||||
.commit();
|
|
||||||
supportFragmentManager.executePendingTransactions();
|
|
||||||
}
|
|
||||||
mediaDetails.showImage(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Media getMediaAtPosition(int i) {
|
|
||||||
if (featuredImagesListFragment.getAdapter() == null) {
|
|
||||||
// not yet ready to return data
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return ((FeaturedImage)featuredImagesListFragment.getAdapter().getItem(i)).getImage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTotalMediaCount() {
|
|
||||||
if (featuredImagesListFragment.getAdapter() == null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return featuredImagesListFragment.getAdapter().getCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void notifyDatasetChanged() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerDataSetObserver(DataSetObserver observer) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unregisterDataSetObserver(DataSetObserver observer) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
package fr.free.nrw.commons.featured;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.GridView;
|
|
||||||
import android.widget.ListAdapter;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import dagger.android.support.DaggerFragment;
|
|
||||||
import fr.free.nrw.commons.Media;
|
|
||||||
import fr.free.nrw.commons.R;
|
|
||||||
|
|
||||||
public class FeaturedImagesListFragment extends DaggerFragment {
|
|
||||||
private GridView gridView;
|
|
||||||
private MockGridViewAdapter gridAdapter;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View v = inflater.inflate(R.layout.fragment_featured_images, container, false);
|
|
||||||
ButterKnife.bind(this, v);
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
|
|
||||||
gridView = getView().findViewById(R.id.featuredImagesList);
|
|
||||||
gridView.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity());
|
|
||||||
gridAdapter = new MockGridViewAdapter(this.getContext(), R.layout.layout_featured_images, getMockFeaturedImages());
|
|
||||||
gridView.setAdapter(gridAdapter);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private ArrayList<FeaturedImage> getMockFeaturedImages(){
|
|
||||||
ArrayList<FeaturedImage> featuredImages = new ArrayList<>();
|
|
||||||
for (int i=0; i<10; i++){
|
|
||||||
featuredImages.add(new FeaturedImage(new Media("test.jpg"), "username: test", "test file name"));
|
|
||||||
}
|
|
||||||
return featuredImages;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ListAdapter getAdapter() {
|
|
||||||
return gridView.getAdapter();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
package fr.free.nrw.commons.featured;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.MediaWikiImageView;
|
|
||||||
import fr.free.nrw.commons.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is created to only display UI implementation. Needs to be changed in real implementation
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class MockGridViewAdapter extends ArrayAdapter {
|
|
||||||
private Context context;
|
|
||||||
private int layoutResourceId;
|
|
||||||
private ArrayList<FeaturedImage> data = new ArrayList();
|
|
||||||
|
|
||||||
public MockGridViewAdapter(Context context, int layoutResourceId, ArrayList<FeaturedImage> data) {
|
|
||||||
super(context, layoutResourceId, data);
|
|
||||||
this.layoutResourceId = layoutResourceId;
|
|
||||||
this.context = context;
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
|
||||||
|
|
||||||
if (convertView == null) {
|
|
||||||
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
|
|
||||||
convertView = inflater.inflate(R.layout.layout_featured_images, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
FeaturedImage item = data.get(position);
|
|
||||||
MediaWikiImageView imageView = convertView.findViewById(R.id.featuredImageView);
|
|
||||||
TextView fileName = convertView.findViewById(R.id.featuredImageTitle);
|
|
||||||
TextView author = convertView.findViewById(R.id.featuredImageAuthor);
|
|
||||||
fileName.setText("Test file name");
|
|
||||||
author.setText("Uploaded by: Test user name");
|
|
||||||
imageView.setMedia(item.getImage());
|
|
||||||
return convertView;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -45,6 +45,7 @@ import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import fr.free.nrw.commons.ui.widget.CompatTextView;
|
import fr.free.nrw.commons.ui.widget.CompatTextView;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static android.view.View.*;
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
|
|
||||||
public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
@ -154,9 +155,9 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
authorLayout = (LinearLayout) view.findViewById(R.id.authorLinearLayout);
|
authorLayout = (LinearLayout) view.findViewById(R.id.authorLinearLayout);
|
||||||
|
|
||||||
if (isFeaturedMedia){
|
if (isFeaturedMedia){
|
||||||
authorLayout.setVisibility(View.VISIBLE);
|
authorLayout.setVisibility(VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
authorLayout.setVisibility(View.GONE);
|
authorLayout.setVisibility(GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
licenseList = new LicenseList(getActivity());
|
licenseList = new LicenseList(getActivity());
|
||||||
|
|
@ -306,6 +307,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
}
|
}
|
||||||
rebuildCatList();
|
rebuildCatList();
|
||||||
|
|
||||||
|
if(media.getCreator() == null || media.getCreator().equals("")) {
|
||||||
|
authorLayout.setVisibility(GONE);
|
||||||
|
} else {
|
||||||
|
author.setText(media.getCreator());
|
||||||
|
}
|
||||||
|
|
||||||
checkDeletion(media);
|
checkDeletion(media);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,13 +320,17 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
if (licenseLink(media) != null) {
|
if (licenseLink(media) != null) {
|
||||||
license.setOnClickListener(v -> openWebBrowser(licenseLink(media)));
|
license.setOnClickListener(v -> openWebBrowser(licenseLink(media)));
|
||||||
} else {
|
} else {
|
||||||
Toast toast = Toast.makeText(getContext(), getString(R.string.null_url), Toast.LENGTH_SHORT);
|
if(isFeaturedMedia) {
|
||||||
toast.show();
|
Timber.d("Unable to fetch license URL for %s", media.getLicense());
|
||||||
|
} else {
|
||||||
|
Toast toast = Toast.makeText(getContext(), getString(R.string.null_url), Toast.LENGTH_SHORT);
|
||||||
|
toast.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (media.getCoordinates() != null) {
|
if (media.getCoordinates() != null) {
|
||||||
coordinates.setOnClickListener(v -> openMap(media.getCoordinates()));
|
coordinates.setOnClickListener(v -> openMap(media.getCoordinates()));
|
||||||
}
|
}
|
||||||
if (delete.getVisibility() == View.VISIBLE) {
|
if (delete.getVisibility() == VISIBLE) {
|
||||||
enableDeleteButton(true);
|
enableDeleteButton(true);
|
||||||
|
|
||||||
delete.setOnClickListener(v -> {
|
delete.setOnClickListener(v -> {
|
||||||
|
|
@ -369,7 +380,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
d.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
d.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (nominatedforDeletion.getVisibility() == View.VISIBLE){
|
if (nominatedforDeletion.getVisibility() == VISIBLE){
|
||||||
seeMore.setOnClickListener(v -> {
|
seeMore.setOnClickListener(v -> {
|
||||||
openWebBrowser(media.getFilePageTitle().getMobileUri().toString());
|
openWebBrowser(media.getFilePageTitle().getMobileUri().toString());
|
||||||
});
|
});
|
||||||
|
|
@ -476,12 +487,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
private void checkDeletion(Media media){
|
private void checkDeletion(Media media){
|
||||||
if (media.getRequestedDeletion()){
|
if (media.getRequestedDeletion()){
|
||||||
delete.setVisibility(View.GONE);
|
delete.setVisibility(GONE);
|
||||||
nominatedforDeletion.setVisibility(View.VISIBLE);
|
nominatedforDeletion.setVisibility(VISIBLE);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
delete.setVisibility(View.VISIBLE);
|
delete.setVisibility(VISIBLE);
|
||||||
nominatedforDeletion.setVisibility(View.GONE);
|
nominatedforDeletion.setVisibility(GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ import android.support.annotation.VisibleForTesting;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.conn.ClientConnectionManager;
|
import org.apache.http.conn.ClientConnectionManager;
|
||||||
import org.apache.http.conn.scheme.PlainSocketFactory;
|
import org.apache.http.conn.scheme.PlainSocketFactory;
|
||||||
|
|
@ -38,7 +40,10 @@ import java.util.Locale;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
import fr.free.nrw.commons.BuildConfig;
|
import fr.free.nrw.commons.BuildConfig;
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.PageTitle;
|
import fr.free.nrw.commons.PageTitle;
|
||||||
|
import fr.free.nrw.commons.category.CategoryImageUtils;
|
||||||
|
import fr.free.nrw.commons.category.QueryContinue;
|
||||||
import fr.free.nrw.commons.notification.Notification;
|
import fr.free.nrw.commons.notification.Notification;
|
||||||
import fr.free.nrw.commons.notification.NotificationUtils;
|
import fr.free.nrw.commons.notification.NotificationUtils;
|
||||||
import in.yuvi.http.fluent.Http;
|
import in.yuvi.http.fluent.Http;
|
||||||
|
|
@ -46,6 +51,8 @@ import io.reactivex.Observable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.utils.ContinueUtils.getQueryContinue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Addshore
|
* @author Addshore
|
||||||
*/
|
*/
|
||||||
|
|
@ -56,9 +63,15 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
private AbstractHttpClient httpClient;
|
private AbstractHttpClient httpClient;
|
||||||
private MWApi api;
|
private MWApi api;
|
||||||
private Context context;
|
private Context context;
|
||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences defaultPreferences;
|
||||||
|
private SharedPreferences categoryPreferences;
|
||||||
|
private Gson gson;
|
||||||
|
|
||||||
public ApacheHttpClientMediaWikiApi(Context context, String apiURL, SharedPreferences sharedPreferences) {
|
public ApacheHttpClientMediaWikiApi(Context context,
|
||||||
|
String apiURL,
|
||||||
|
SharedPreferences defaultPreferences,
|
||||||
|
SharedPreferences categoryPreferences,
|
||||||
|
Gson gson) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
BasicHttpParams params = new BasicHttpParams();
|
BasicHttpParams params = new BasicHttpParams();
|
||||||
SchemeRegistry schemeRegistry = new SchemeRegistry();
|
SchemeRegistry schemeRegistry = new SchemeRegistry();
|
||||||
|
|
@ -69,7 +82,9 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
params.setParameter(CoreProtocolPNames.USER_AGENT, getUserAgent());
|
params.setParameter(CoreProtocolPNames.USER_AGENT, getUserAgent());
|
||||||
httpClient = new DefaultHttpClient(cm, params);
|
httpClient = new DefaultHttpClient(cm, params);
|
||||||
api = new MWApi(apiURL, httpClient);
|
api = new MWApi(apiURL, httpClient);
|
||||||
this.sharedPreferences = sharedPreferences;
|
this.defaultPreferences = defaultPreferences;
|
||||||
|
this.categoryPreferences = categoryPreferences;
|
||||||
|
this.gson = gson;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -160,7 +175,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAuthCookieOnLogin(boolean isLoggedIn) {
|
private void setAuthCookieOnLogin(boolean isLoggedIn) {
|
||||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
SharedPreferences.Editor editor = defaultPreferences.edit();
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
editor.putBoolean("isUserLoggedIn", true);
|
editor.putBoolean("isUserLoggedIn", true);
|
||||||
editor.putString("getAuthCookie", api.getAuthCookie());
|
editor.putString("getAuthCookie", api.getAuthCookie());
|
||||||
|
|
@ -448,6 +463,81 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
return NotificationUtils.getNotificationsFromList(context, childNodes);
|
return NotificationUtils.getNotificationsFromList(context, childNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method takes categoryName as input and returns a List of Media objects
|
||||||
|
* It uses the generator query API to get the images in a category, 10 at a time.
|
||||||
|
* Uses the query continue values for fetching paginated responses
|
||||||
|
* @param categoryName Category name as defined on commons
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public List<Media> getCategoryImages(String categoryName) {
|
||||||
|
ApiResult apiResult = null;
|
||||||
|
try {
|
||||||
|
MWApi.RequestBuilder requestBuilder = api.action("query")
|
||||||
|
.param("generator", "categorymembers")
|
||||||
|
.param("format", "xml")
|
||||||
|
.param("gcmtype", "file")
|
||||||
|
.param("gcmtitle", categoryName)
|
||||||
|
.param("prop", "imageinfo")
|
||||||
|
.param("gcmlimit", "10")
|
||||||
|
.param("iiprop", "url|extmetadata");
|
||||||
|
|
||||||
|
QueryContinue queryContinueValues = getQueryContinueValues(categoryName);
|
||||||
|
if (queryContinueValues != null) {
|
||||||
|
requestBuilder.param("continue", queryContinueValues.getContinueParam());
|
||||||
|
requestBuilder.param("gcmcontinue", queryContinueValues.getGcmContinueParam());
|
||||||
|
}
|
||||||
|
|
||||||
|
apiResult = requestBuilder.get();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.e("Failed to obtain searchCategories", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiResult == null) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiResult categoryImagesNode = apiResult.getNode("/api/query/pages");
|
||||||
|
if (categoryImagesNode == null
|
||||||
|
|| categoryImagesNode.getDocument() == null
|
||||||
|
|| categoryImagesNode.getDocument().getChildNodes() == null
|
||||||
|
|| categoryImagesNode.getDocument().getChildNodes().getLength() == 0) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryContinue queryContinue = getQueryContinue(apiResult.getNode("/api/continue").getDocument());
|
||||||
|
setQueryContinueValues(categoryName, queryContinue);
|
||||||
|
|
||||||
|
NodeList childNodes = categoryImagesNode.getDocument().getChildNodes();
|
||||||
|
return CategoryImageUtils.getMediaList(childNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For APIs that return paginated responses, MediaWiki APIs uses the QueryContinue to facilitate fetching of subsequent pages
|
||||||
|
* https://www.mediawiki.org/wiki/API:Raw_query_continue
|
||||||
|
* After fetching images a page of image for a particular category, shared prefs are updated with the latest QueryContinue Values
|
||||||
|
* @param keyword
|
||||||
|
* @param queryContinue
|
||||||
|
*/
|
||||||
|
private void setQueryContinueValues(String keyword, QueryContinue queryContinue) {
|
||||||
|
SharedPreferences.Editor editor = categoryPreferences.edit();
|
||||||
|
editor.putString(keyword, gson.toJson(queryContinue));
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Before making a paginated API call, this method is called to get the latest query continue values to be used
|
||||||
|
* @param keyword
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private QueryContinue getQueryContinueValues(String keyword) {
|
||||||
|
String queryContinueString = categoryPreferences.getString(keyword, null);
|
||||||
|
return gson.fromJson(queryContinueString, QueryContinue.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean existingFile(String fileSha1) throws IOException {
|
public boolean existingFile(String fileSha1) throws IOException {
|
||||||
return api.action("query")
|
return api.action("query")
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.notification.Notification;
|
import fr.free.nrw.commons.notification.Notification;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
|
|
@ -34,6 +35,8 @@ public interface MediaWikiApi {
|
||||||
|
|
||||||
boolean logEvents(LogBuilder[] logBuilders);
|
boolean logEvents(LogBuilder[] logBuilders);
|
||||||
|
|
||||||
|
List<Media> getCategoryImages(String categoryName);
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, ProgressListener progressListener) throws IOException;
|
UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, ProgressListener progressListener) throws IOException;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,11 @@ import fr.free.nrw.commons.AboutActivity;
|
||||||
import fr.free.nrw.commons.BuildConfig;
|
import fr.free.nrw.commons.BuildConfig;
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
|
||||||
import fr.free.nrw.commons.WelcomeActivity;
|
import fr.free.nrw.commons.WelcomeActivity;
|
||||||
import fr.free.nrw.commons.auth.AccountUtil;
|
import fr.free.nrw.commons.auth.AccountUtil;
|
||||||
import fr.free.nrw.commons.auth.LoginActivity;
|
import fr.free.nrw.commons.auth.LoginActivity;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||||
import fr.free.nrw.commons.featured.FeaturedImagesActivity;
|
import fr.free.nrw.commons.category.CategoryImagesActivity;
|
||||||
import fr.free.nrw.commons.nearby.NearbyActivity;
|
import fr.free.nrw.commons.nearby.NearbyActivity;
|
||||||
import fr.free.nrw.commons.notification.NotificationActivity;
|
import fr.free.nrw.commons.notification.NotificationActivity;
|
||||||
import fr.free.nrw.commons.settings.SettingsActivity;
|
import fr.free.nrw.commons.settings.SettingsActivity;
|
||||||
|
|
@ -37,6 +36,8 @@ import timber.log.Timber;
|
||||||
public abstract class NavigationBaseActivity extends BaseActivity
|
public abstract class NavigationBaseActivity extends BaseActivity
|
||||||
implements NavigationView.OnNavigationItemSelectedListener {
|
implements NavigationView.OnNavigationItemSelectedListener {
|
||||||
|
|
||||||
|
private static final String FEATURED_IMAGES_CATEGORY = "Category:Featured_pictures_on_Wikimedia_Commons";
|
||||||
|
|
||||||
@BindView(R.id.toolbar)
|
@BindView(R.id.toolbar)
|
||||||
Toolbar toolbar;
|
Toolbar toolbar;
|
||||||
@BindView(R.id.navigation_view)
|
@BindView(R.id.navigation_view)
|
||||||
|
|
@ -157,7 +158,7 @@ public abstract class NavigationBaseActivity extends BaseActivity
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_featured_images:
|
case R.id.action_featured_images:
|
||||||
drawerLayout.closeDrawer(navigationView);
|
drawerLayout.closeDrawer(navigationView);
|
||||||
startActivityWithFlags(this, FeaturedImagesActivity.class, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
CategoryImagesActivity.startYourself(this, getString(R.string.title_activity_featured_images), FEATURED_IMAGES_CATEGORY);
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
Timber.e("Unknown option [%s] selected from the navigation menu", itemId);
|
Timber.e("Unknown option [%s] selected from the navigation menu", itemId);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package fr.free.nrw.commons.utils;
|
||||||
|
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.category.QueryContinue;
|
||||||
|
|
||||||
|
public class ContinueUtils {
|
||||||
|
|
||||||
|
public static QueryContinue getQueryContinue(Node document) {
|
||||||
|
Element continueElement = (Element) document;
|
||||||
|
return new QueryContinue(continueElement.getAttribute("continue"),
|
||||||
|
continueElement.getAttribute("gcmcontinue"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/drawer_layout"
|
android:id="@+id/drawer_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
@ -16,20 +15,12 @@
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:id="@+id/featuredFragmentContainer"
|
android:id="@+id/fragmentContainer"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_below="@id/toolbar">
|
android:layout_below="@id/toolbar">
|
||||||
|
|
||||||
<fragment
|
|
||||||
android:id="@+id/featuedListFragment"
|
|
||||||
android:name="fr.free.nrw.commons.featured.FeaturedImagesListFragment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:layout="@layout/fragment_contributions" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
|
@ -1,20 +1,21 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?attr/mainBackground"
|
android:background="?attr/mainBackground">
|
||||||
>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/waiting_first_sync"
|
android:text="@string/waiting_first_sync"
|
||||||
android:id="@+id/waitingMessage"
|
android:id="@+id/statusMessage"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
|
|
@ -22,11 +23,11 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:id="@+id/loadingFeaturedImagesProgressBar"
|
android:id="@+id/loadingImagesProgressBar"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<GridView
|
<GridView
|
||||||
android:id="@+id/featuredImagesList"
|
android:id="@+id/categoryImagesList"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:stretchMode="columnWidth"
|
android:stretchMode="columnWidth"
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
>
|
>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/featuredImagesSequenceNumber"
|
android:id="@+id/categoryImagesSequenceNumber"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="98sp"
|
android:textSize="98sp"
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<fr.free.nrw.commons.MediaWikiImageView
|
<fr.free.nrw.commons.MediaWikiImageView
|
||||||
android:id="@+id/featuredImageView"
|
android:id="@+id/categoryImageView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="240dp"
|
android:layout_height="240dp"
|
||||||
/>
|
/>
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
android:padding="@dimen/small_gap"
|
android:padding="@dimen/small_gap"
|
||||||
>
|
>
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/featuredProgress"
|
android:id="@+id/categoryProgress"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
style="@style/ProgressBar"
|
style="@style/ProgressBar"
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/featuredImageTitle"
|
android:id="@+id/categoryImageTitle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="#FFFFFFFF"
|
android:textColor="#FFFFFFFF"
|
||||||
|
|
@ -53,7 +53,7 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/featuredImageAuthor"
|
android:id="@+id/categoryImageAuthor"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="#FFFFFFFF"
|
android:textColor="#FFFFFFFF"
|
||||||
|
|
@ -270,5 +270,9 @@
|
||||||
<string name="about_translate_proceed">Proceed</string>
|
<string name="about_translate_proceed">Proceed</string>
|
||||||
<string name="about_translate_cancel">Cancel</string>
|
<string name="about_translate_cancel">Cancel</string>
|
||||||
<string name="retry">Retry</string>
|
<string name="retry">Retry</string>
|
||||||
|
|
||||||
|
<string name="no_images_found">No images found!</string>
|
||||||
|
<string name="error_loading_images">Error occurred while loading images.</string>
|
||||||
|
<string name="image_uploaded_by">Uploaded by: %1$s</string>
|
||||||
<string name="share_app_title">Share App</string>
|
<string name="share_app_title">Share App</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package fr.free.nrw.commons
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.support.v4.util.LruCache
|
import android.support.v4.util.LruCache
|
||||||
|
import com.google.gson.Gson
|
||||||
import com.nhaarman.mockito_kotlin.mock
|
import com.nhaarman.mockito_kotlin.mock
|
||||||
import com.squareup.leakcanary.RefWatcher
|
import com.squareup.leakcanary.RefWatcher
|
||||||
import fr.free.nrw.commons.auth.AccountUtil
|
import fr.free.nrw.commons.auth.AccountUtil
|
||||||
|
|
@ -36,6 +37,7 @@ class MockCommonsApplicationModule(appContext: Context) : CommonsApplicationModu
|
||||||
val accountUtil: AccountUtil = mock()
|
val accountUtil: AccountUtil = mock()
|
||||||
val appSharedPreferences: SharedPreferences = mock()
|
val appSharedPreferences: SharedPreferences = mock()
|
||||||
val defaultSharedPreferences: SharedPreferences = mock()
|
val defaultSharedPreferences: SharedPreferences = mock()
|
||||||
|
val categorySharedPreferences: SharedPreferences = mock()
|
||||||
val otherSharedPreferences: SharedPreferences = mock()
|
val otherSharedPreferences: SharedPreferences = mock()
|
||||||
val uploadController: UploadController = mock()
|
val uploadController: UploadController = mock()
|
||||||
val mockSessionManager: SessionManager = mock()
|
val mockSessionManager: SessionManager = mock()
|
||||||
|
|
@ -45,6 +47,7 @@ class MockCommonsApplicationModule(appContext: Context) : CommonsApplicationModu
|
||||||
val mockDbOpenHelper: DBOpenHelper = mock()
|
val mockDbOpenHelper: DBOpenHelper = mock()
|
||||||
val nearbyPlaces: NearbyPlaces = mock()
|
val nearbyPlaces: NearbyPlaces = mock()
|
||||||
val lruCache: LruCache<String, String> = mock()
|
val lruCache: LruCache<String, String> = mock()
|
||||||
|
val gson: Gson = Gson()
|
||||||
|
|
||||||
override fun providesAccountUtil(context: Context): AccountUtil = accountUtil
|
override fun providesAccountUtil(context: Context): AccountUtil = accountUtil
|
||||||
|
|
||||||
|
|
@ -58,7 +61,7 @@ class MockCommonsApplicationModule(appContext: Context) : CommonsApplicationModu
|
||||||
|
|
||||||
override fun providesSessionManager(context: Context, mediaWikiApi: MediaWikiApi, sharedPreferences: SharedPreferences): SessionManager = mockSessionManager
|
override fun providesSessionManager(context: Context, mediaWikiApi: MediaWikiApi, sharedPreferences: SharedPreferences): SessionManager = mockSessionManager
|
||||||
|
|
||||||
override fun provideMediaWikiApi(context: Context, sharedPreferences: SharedPreferences): MediaWikiApi = mediaWikiApi
|
override fun provideMediaWikiApi(context: Context, sharedPreferences: SharedPreferences, categorySharedPreferences: SharedPreferences, gson: Gson): MediaWikiApi = mediaWikiApi
|
||||||
|
|
||||||
override fun provideLocationServiceManager(context: Context): LocationServiceManager = locationServiceManager
|
override fun provideLocationServiceManager(context: Context): LocationServiceManager = locationServiceManager
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package fr.free.nrw.commons.mwapi
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
|
import com.google.gson.Gson
|
||||||
import fr.free.nrw.commons.BuildConfig
|
import fr.free.nrw.commons.BuildConfig
|
||||||
import fr.free.nrw.commons.TestCommonsApplication
|
import fr.free.nrw.commons.TestCommonsApplication
|
||||||
import okhttp3.mockwebserver.MockResponse
|
import okhttp3.mockwebserver.MockResponse
|
||||||
|
|
@ -26,12 +27,14 @@ class ApacheHttpClientMediaWikiApiTest {
|
||||||
private lateinit var testObject: ApacheHttpClientMediaWikiApi
|
private lateinit var testObject: ApacheHttpClientMediaWikiApi
|
||||||
private lateinit var server: MockWebServer
|
private lateinit var server: MockWebServer
|
||||||
private lateinit var sharedPreferences: SharedPreferences
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
|
private lateinit var categoryPreferences: SharedPreferences
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
server = MockWebServer()
|
server = MockWebServer()
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(RuntimeEnvironment.application)
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(RuntimeEnvironment.application)
|
||||||
testObject = ApacheHttpClientMediaWikiApi(RuntimeEnvironment.application, "http://" + server.hostName + ":" + server.port + "/", sharedPreferences)
|
categoryPreferences = PreferenceManager.getDefaultSharedPreferences(RuntimeEnvironment.application)
|
||||||
|
testObject = ApacheHttpClientMediaWikiApi(RuntimeEnvironment.application, "http://" + server.hostName + ":" + server.port + "/", sharedPreferences, categoryPreferences, Gson())
|
||||||
testObject.setWikiMediaToolforgeUrl("http://" + server.hostName + ":" + server.port + "/")
|
testObject.setWikiMediaToolforgeUrl("http://" + server.hostName + ":" + server.port + "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue