resolving Login page switching issue

This commit is contained in:
Ujjwal Agrawal 2018-05-14 16:55:08 +05:30
commit ed241e09f7
55 changed files with 1500 additions and 456 deletions

View file

@ -5,15 +5,30 @@ If you're not sure where to start head on to [this wiki page](https://github.com
Here's a gist of the guidelines, Here's a gist of the guidelines,
# Make separate commits for logically separate changes 1. Make separate commits for logically separate changes
# Describe your changes well in the commit message 1. Describe your changes well in the commit message
The first line of the commit message should be a short description of what has The first line of the commit message should be a short description of what has
changed. It is also good to prefix the first line with "area: " where the "area" changed. It is also good to prefix the first line with "area: " where the "area"
is a filename or identifier for the general area of the code being modified. is a filename or identifier for the general area of the code being modified.
The body should provide a meaningful commit message. The body should provide a meaningful commit message.
# Write tests for your code (if possible) 1. Write Javadocs
# Make sure the Wiki pages don't become stale by updating them (if needed) We require contributors to include Javadocs for all new methods and classes
submitted via PRs (after 1 May 2018). This is aimed at making it easier for
new contributors to dive into our codebase, especially those who are new to
Android development. A few things to note:
- This should not replace the need for code that is easily-readable in
and of itself
- Please make sure that your Javadocs are reasonably descriptive, not just
a copy of the method name
- Please do not use `@author` tags - we aim for collective code ownership,
and if needed, Git allows us to see who wrote something without needing
to add these tags (`git blame`)
1. Write tests for your code (if possible)
1. Make sure the Wiki pages don't become stale by updating them (if needed)

View file

@ -1,15 +1,15 @@
## Description ## Description (required)
Fixes #{GitHub issue number} Fixes #{GitHub issue number and title}
{Describe the changes made and why they were made.} {Describe the changes made and why they were made.}
## Tests performed ## Tests performed (required)
Tested on {API level & name of device/emulator}, with {build variant, e.g. ProdDebug}. Tested on {API level & name of device/emulator}, with {build variant, e.g. ProdDebug}.
{Please test your PR at least once before submitting.} ## Screenshots showing what changed (optional)
## Screenshots showing what changed
{Only for user interface changes, otherwise remove this section. See [how to take a screenshot](https://android.stackexchange.com/questions/1759/how-to-take-a-screenshot-with-an-android-device)} {Only for user interface changes, otherwise remove this section. See [how to take a screenshot](https://android.stackexchange.com/questions/1759/how-to-take-a-screenshot-with-an-android-device)}
_Note: Please ensure that you have read CONTRIBUTING.md if this is your first pull request._

View file

@ -25,6 +25,8 @@ dependencies {
transitive=true transitive=true
} }
implementation "com.github.deano2390:MaterialShowcaseView:1.2.0"
implementation "com.android.support:support-v4:$SUPPORT_LIB_VERSION" implementation "com.android.support:support-v4:$SUPPORT_LIB_VERSION"
implementation "com.android.support:appcompat-v7:$SUPPORT_LIB_VERSION" implementation "com.android.support:appcompat-v7:$SUPPORT_LIB_VERSION"
implementation "com.android.support:design:$SUPPORT_LIB_VERSION" implementation "com.android.support:design:$SUPPORT_LIB_VERSION"
@ -49,6 +51,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'

View file

@ -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" />

View file

@ -136,19 +136,19 @@ public class CommonsApplication extends MultiDexApplication {
} }
sessionManager.clearAllAccounts() sessionManager.clearAllAccounts()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> { .subscribe(() -> {
Timber.d("All accounts have been removed"); Timber.d("All accounts have been removed");
//TODO: fix preference manager //TODO: fix preference manager
defaultPrefs.edit().clear().apply(); defaultPrefs.edit().clear().apply();
applicationPrefs.edit().clear().apply(); applicationPrefs.edit().clear().apply();
applicationPrefs.edit().putBoolean("firstrun", false).apply(); applicationPrefs.edit().putBoolean("firstrun", false).apply();
otherPrefs.edit().clear().apply(); otherPrefs.edit().clear().apply();
updateAllDatabases(); updateAllDatabases();
logoutListener.onLogoutComplete(); logoutListener.onLogoutComplete();
}); });
} }
/** /**

View file

@ -61,8 +61,8 @@ public class MediaDataExtractor {
} }
try{ try{
Timber.d("Nominated for deletion: " + mediaWikiApi.pageExists("Commons:Deletion_requests/"+filename)); deletionStatus = mediaWikiApi.pageExists("Commons:Deletion_requests/" + filename);
deletionStatus = mediaWikiApi.pageExists("Commons:Deletion_requests/"+filename); Timber.d("Nominated for deletion: " + deletionStatus);
} }
catch (Exception e){ catch (Exception e){
Timber.d(e.getMessage()); Timber.d(e.getMessage());

View file

@ -4,9 +4,7 @@ import android.accounts.Account;
import android.accounts.AccountAuthenticatorActivity; import android.accounts.AccountAuthenticatorActivity;
import android.accounts.AccountAuthenticatorResponse; import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager; import android.accounts.AccountManager;
import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
@ -25,7 +23,6 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView; import android.widget.TextView;
@ -39,16 +36,13 @@ import javax.inject.Named;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import butterknife.OnClick; import butterknife.OnClick;
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.PageTitle; import fr.free.nrw.commons.PageTitle;
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.WelcomeActivity; import fr.free.nrw.commons.WelcomeActivity;
import fr.free.nrw.commons.contributions.ContributionsActivity; import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.di.ApplicationlessInjection; import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.featured.FeaturedImagesActivity_MembersInjector;
import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.nearby.NearbyActivity; import fr.free.nrw.commons.nearby.NearbyActivity;
import fr.free.nrw.commons.theme.NavigationBaseActivity; import fr.free.nrw.commons.theme.NavigationBaseActivity;

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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) {
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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();
} }

View file

@ -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() {

View file

@ -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();
} }

View file

@ -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;
}
}

View file

@ -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) {
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -9,6 +9,7 @@ import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.Editable; import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -22,6 +23,9 @@ import android.widget.ScrollView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import java.io.IOException; import java.io.IOException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
@ -45,6 +49,8 @@ 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.GONE;
import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.LENGTH_SHORT;
public class MediaDetailFragment extends CommonsDaggerSupportFragment { public class MediaDetailFragment extends CommonsDaggerSupportFragment {
@ -74,23 +80,37 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
@Inject @Inject
MediaWikiApi mwApi; MediaWikiApi mwApi;
private MediaWikiImageView image;
private MediaDetailSpacer spacer;
private int initialListTop = 0; private int initialListTop = 0;
private TextView title; @BindView(R.id.mediaDetailImage)
private TextView desc; MediaWikiImageView image;
private TextView author; @BindView(R.id.mediaDetailSpacer)
private TextView license; MediaDetailSpacer spacer;
private TextView coordinates; @BindView(R.id.mediaDetailTitle)
private TextView uploadedDate; TextView title;
private TextView seeMore; @BindView(R.id.mediaDetailDesc)
private LinearLayout nominatedforDeletion; TextView desc;
private LinearLayout categoryContainer; @BindView(R.id.mediaDetailAuthor)
private LinearLayout authorLayout; TextView author;
private Button delete; @BindView(R.id.mediaDetailLicense)
private ScrollView scrollView; TextView license;
@BindView(R.id.mediaDetailCoordinates)
TextView coordinates;
@BindView(R.id.mediaDetailuploadeddate)
TextView uploadedDate;
@BindView(R.id.seeMore)
TextView seeMore;
@BindView(R.id.nominatedDeletionBanner)
LinearLayout nominatedForDeletion;
@BindView(R.id.mediaDetailCategoryContainer)
LinearLayout categoryContainer;
@BindView(R.id.authorLinearLayout)
LinearLayout authorLayout;
@BindView(R.id.nominateDeletion)
Button delete;
@BindView(R.id.mediaDetailScrollView)
ScrollView scrollView;
private ArrayList<String> categoryNames; private ArrayList<String> categoryNames;
private boolean categoriesLoaded = false; private boolean categoriesLoaded = false;
private boolean categoriesPresent = false; private boolean categoriesPresent = false;
@ -100,6 +120,9 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
private AsyncTask<Void, Void, Boolean> detailFetchTask; private AsyncTask<Void, Void, Boolean> detailFetchTask;
private LicenseList licenseList; private LicenseList licenseList;
//Had to make this class variable, to implement various onClicks, which access the media, also I fell why make separate variables when one can serve the purpose
private Media media;
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
@ -136,27 +159,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
final View view = inflater.inflate(R.layout.fragment_media_detail, container, false); final View view = inflater.inflate(R.layout.fragment_media_detail, container, false);
image = (MediaWikiImageView) view.findViewById(R.id.mediaDetailImage); ButterKnife.bind(this,view);
scrollView = (ScrollView) view.findViewById(R.id.mediaDetailScrollView);
// Detail consists of a list view with main pane in header view, plus category list.
spacer = (MediaDetailSpacer) view.findViewById(R.id.mediaDetailSpacer);
title = (TextView) view.findViewById(R.id.mediaDetailTitle);
desc = (TextView) view.findViewById(R.id.mediaDetailDesc);
author = (TextView) view.findViewById(R.id.mediaDetailAuthor);
license = (TextView) view.findViewById(R.id.mediaDetailLicense);
coordinates = (TextView) view.findViewById(R.id.mediaDetailCoordinates);
uploadedDate = (TextView) view.findViewById(R.id.mediaDetailuploadeddate);
seeMore = (TextView) view.findViewById(R.id.seeMore);
nominatedforDeletion = (LinearLayout) view.findViewById(R.id.nominatedDeletionBanner);
delete = (Button) view.findViewById(R.id.nominateDeletion);
categoryContainer = (LinearLayout) view.findViewById(R.id.mediaDetailCategoryContainer);
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());
@ -195,7 +203,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
Media media = detailProvider.getMediaAtPosition(index); media = detailProvider.getMediaAtPosition(index);
if (media == null) { if (media == null) {
// Ask the detail provider to ping us when we're ready // Ask the detail provider to ping us when we're ready
Timber.d("MediaDetailFragment not yet ready to display details; registering observer"); Timber.d("MediaDetailFragment not yet ready to display details; registering observer");
@ -208,17 +216,18 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
Timber.d("MediaDetailFragment ready to display delayed details!"); Timber.d("MediaDetailFragment ready to display delayed details!");
detailProvider.unregisterDataSetObserver(dataObserver); detailProvider.unregisterDataSetObserver(dataObserver);
dataObserver = null; dataObserver = null;
displayMediaDetails(detailProvider.getMediaAtPosition(index)); media=detailProvider.getMediaAtPosition(index);
displayMediaDetails();
} }
}; };
detailProvider.registerDataSetObserver(dataObserver); detailProvider.registerDataSetObserver(dataObserver);
} else { } else {
Timber.d("MediaDetailFragment ready to display details"); Timber.d("MediaDetailFragment ready to display details");
displayMediaDetails(media); displayMediaDetails();
} }
} }
private void displayMediaDetails(final Media media) { private void displayMediaDetails() {
//Always load image from Internet to allow viewing the desc, license, and cats //Always load image from Internet to allow viewing the desc, license, and cats
image.setMedia(media); image.setMedia(media);
@ -255,7 +264,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
if (success) { if (success) {
extractor.fill(media); extractor.fill(media);
setTextFields(media); setTextFields(media);
setOnClickListeners(media);
} else { } else {
Timber.d("Failed to load photo details."); Timber.d("Failed to load photo details.");
} }
@ -306,73 +314,90 @@ 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);
} }
private void setOnClickListeners(final Media media) { @OnClick(R.id.mediaDetailLicense)
if (licenseLink(media) != null) { public void onMediaDetailLicenceClicked(){
license.setOnClickListener(v -> openWebBrowser(licenseLink(media))); if (!TextUtils.isEmpty(licenseLink(media))) {
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();
}
} }
}
@OnClick(R.id.mediaDetailCoordinates)
public void onMediaDetailCoordinatesClicked(){
if (media.getCoordinates() != null) { if (media.getCoordinates() != null) {
coordinates.setOnClickListener(v -> openMap(media.getCoordinates())); openMap(media.getCoordinates());
} }
if (delete.getVisibility() == View.VISIBLE) { }
enableDeleteButton(true);
delete.setOnClickListener(v -> { @OnClick(R.id.nominateDeletion)
public void onDeleteButtonClicked(){
//Reviewer correct me if i have misunderstood something over here
//But how does this if (delete.getVisibility() == View.VISIBLE) {
// enableDeleteButton(true); makes sense ?
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
alert.setMessage("Why should this file be deleted?");
final EditText input = new EditText(getActivity());
alert.setView(input);
input.requestFocus();
alert.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
String reason = input.getText().toString();
DeleteTask deleteTask = new DeleteTask(getActivity(), media, reason);
deleteTask.execute();
enableDeleteButton(false);
}
});
alert.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
});
AlertDialog d = alert.create();
input.addTextChangedListener(new TextWatcher() {
private void handleText() {
final Button okButton = d.getButton(AlertDialog.BUTTON_POSITIVE);
if (input.getText().length() == 0) {
okButton.setEnabled(false);
} else {
okButton.setEnabled(true);
}
}
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity()); @Override
alert.setMessage("Why should this file be deleted?"); public void afterTextChanged(Editable arg0) {
final EditText input = new EditText(getActivity()); handleText();
alert.setView(input); }
input.requestFocus();
alert.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
String reason = input.getText().toString();
DeleteTask deleteTask = new DeleteTask(getActivity(), media, reason);
deleteTask.execute();
enableDeleteButton(false);
}
});
alert.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
});
AlertDialog d = alert.create();
input.addTextChangedListener(new TextWatcher() {
private void handleText() {
final Button okButton = d.getButton(AlertDialog.BUTTON_POSITIVE);
if (input.getText().length() == 0) {
okButton.setEnabled(false);
} else {
okButton.setEnabled(true);
}
}
@Override @Override
public void afterTextChanged(Editable arg0) { public void beforeTextChanged(CharSequence s, int start, int count, int after) {
handleText(); }
}
@Override @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { public void onTextChanged(CharSequence s, int start, int before, int count) {
} }
});
d.show();
d.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
}
@Override @OnClick(R.id.seeMore)
public void onTextChanged(CharSequence s, int start, int before, int count) { public void onSeeMoreClicked(){
} if(nominatedForDeletion.getVisibility()== VISIBLE) {
}); openWebBrowser(media.getFilePageTitle().getMobileUri().toString());
d.show();
d.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
});
}
if (nominatedforDeletion.getVisibility() == View.VISIBLE){
seeMore.setOnClickListener(v -> {
openWebBrowser(media.getFilePageTitle().getMobileUri().toString());
});
} }
} }
@ -476,12 +501,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);
} }
} }

View file

@ -26,6 +26,8 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Toast; import android.widget.Toast;
import butterknife.BindView;
import butterknife.ButterKnife;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@ -53,7 +55,8 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
@Named("default_preferences") @Named("default_preferences")
SharedPreferences prefs; SharedPreferences prefs;
private ViewPager pager; @BindView(R.id.mediaDetailsPager)
ViewPager pager;
private Boolean editable; private Boolean editable;
private boolean isFeaturedImage; private boolean isFeaturedImage;
@ -72,7 +75,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
ViewGroup container, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_media_detail_pager, container, false); View view = inflater.inflate(R.layout.fragment_media_detail_pager, container, false);
pager = (ViewPager) view.findViewById(R.id.mediaDetailsPager); ButterKnife.bind(this,view);
pager.addOnPageChangeListener(this); pager.addOnPageChangeListener(this);
final MediaDetailAdapter adapter = new MediaDetailAdapter(getChildFragmentManager()); final MediaDetailAdapter adapter = new MediaDetailAdapter(getChildFragmentManager());

View file

@ -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")

View file

@ -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;

View file

@ -4,14 +4,18 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Typeface;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.design.widget.BottomSheetBehavior; import android.support.design.widget.BottomSheetBehavior;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
@ -25,10 +29,13 @@ import com.google.gson.GsonBuilder;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.LoginActivity;
import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager; import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.location.LocationUpdateListener; import fr.free.nrw.commons.location.LocationUpdateListener;
@ -41,6 +48,8 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
import uk.co.deanwild.materialshowcaseview.IShowcaseListener;
import uk.co.deanwild.materialshowcaseview.MaterialShowcaseView;
public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener { public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener {
@ -56,12 +65,15 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
LinearLayout bottomSheetDetails; LinearLayout bottomSheetDetails;
@BindView(R.id.transparentView) @BindView(R.id.transparentView)
View transparentView; View transparentView;
@BindView(R.id.fab_recenter)
View fabRecenter;
@Inject @Inject
LocationServiceManager locationManager; LocationServiceManager locationManager;
@Inject @Inject
NearbyController nearbyController; NearbyController nearbyController;
@Inject
@Named("application_preferences") SharedPreferences applicationPrefs;
private LatLng curLatLng; private LatLng curLatLng;
private Bundle bundle; private Bundle bundle;
private Disposable placesDisposable; private Disposable placesDisposable;
@ -72,11 +84,18 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
private NearbyListFragment nearbyListFragment; private NearbyListFragment nearbyListFragment;
private static final String TAG_RETAINED_MAP_FRAGMENT = NearbyMapFragment.class.getSimpleName(); private static final String TAG_RETAINED_MAP_FRAGMENT = NearbyMapFragment.class.getSimpleName();
private static final String TAG_RETAINED_LIST_FRAGMENT = NearbyListFragment.class.getSimpleName(); private static final String TAG_RETAINED_LIST_FRAGMENT = NearbyListFragment.class.getSimpleName();
private View listButton; // Reference to list button to use in tutorial
private final String NETWORK_INTENT_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; private final String NETWORK_INTENT_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
private BroadcastReceiver broadcastReceiver; private BroadcastReceiver broadcastReceiver;
private boolean isListShowcaseAdded = false;
private boolean isMapShowCaseAdded = false;
private LatLng lastKnownLocation; private LatLng lastKnownLocation;
private MaterialShowcaseView secondSingleShowCaseView;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -126,6 +145,39 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
MenuInflater inflater = getMenuInflater(); MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_nearby, menu); inflater.inflate(R.menu.menu_nearby, menu);
new Handler().post(() -> {
listButton = findViewById(R.id.action_display_list);
secondSingleShowCaseView = new MaterialShowcaseView.Builder(this)
.setTarget(listButton)
.setDismissText(getString(R.string.showcase_view_got_it_button))
.setContentText(getString(R.string.showcase_view_list_icon))
.setDelay(500) // optional but starting animations immediately in onCreate can make them choppy
.singleUse(ViewUtil.SHOWCASE_VIEW_ID_1) // provide a unique ID used to ensure it is only shown once
.setDismissStyle(Typeface.defaultFromStyle(Typeface.BOLD))
.setListener(new IShowcaseListener() {
@Override
public void onShowcaseDisplayed(MaterialShowcaseView materialShowcaseView) {
}
// If dismissed, we can inform fragment to start showcase sequence there
@Override
public void onShowcaseDismissed(MaterialShowcaseView materialShowcaseView) {
nearbyMapFragment.onNearbyMaterialShowcaseDismissed();
}
})
.build();
isListShowcaseAdded = true;
if (isMapShowCaseAdded) { // If map showcase is also ready, start ShowcaseSequence
// Probably this case is not possible. Just added to be careful
setMapViewTutorialShowCase();
}
});
return super.onCreateOptionsMenu(menu); return super.onCreateOptionsMenu(menu);
} }
@ -420,6 +472,45 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
updateMapFragment(false); updateMapFragment(false);
updateListFragment(); updateListFragment();
} }
isMapShowCaseAdded = true;
}
public void setMapViewTutorialShowCase() {
/*
*This showcase view will be the first step of our nearbyMaterialShowcaseSequence. The reason we use a
* single item instead of adding another step to nearbyMaterialShowcaseSequence is that we are not able to
* call withoutShape() method on steps. For mapView we need an showcase view without
* any circle on it, it should cover the whole page.
* */
MaterialShowcaseView firstSingleShowCaseView = new MaterialShowcaseView.Builder(this)
.setTarget(nearbyMapFragment.mapView)
.setDismissText(getString(R.string.showcase_view_got_it_button))
.setContentText(getString(R.string.showcase_view_whole_nearby_activity))
.setDelay(500) // optional but starting animations immediately in onCreate can make them choppy
.singleUse(ViewUtil.SHOWCASE_VIEW_ID_2) // provide a unique ID used to ensure it is only shown once
.withoutShape() // no shape on map view since there are no view to focus on
.setDismissStyle(Typeface.defaultFromStyle(Typeface.BOLD))
.setListener(new IShowcaseListener() {
@Override
public void onShowcaseDisplayed(MaterialShowcaseView materialShowcaseView) {
}
@Override
public void onShowcaseDismissed(MaterialShowcaseView materialShowcaseView) {
/* Add other nearbyMaterialShowcaseSequence here, it will make the user feel as they are a
* nearbyMaterialShowcaseSequence whole together.
* */
secondSingleShowCaseView.show(NearbyActivity.this);
}
})
.build();
if (applicationPrefs.getBoolean("firstRunNearby", true)) {
applicationPrefs.edit().putBoolean("firstRunNearby", false).apply();
firstSingleShowCaseView.show(this);
}
} }
private void lockNearbyView(boolean lock) { private void lockNearbyView(boolean lock) {
@ -557,4 +648,5 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
public void prepareViewsForSheetPosition(int bottomSheetState) { public void prepareViewsForSheetPosition(int bottomSheetState) {
// TODO // TODO
} }
} }

View file

@ -2,6 +2,7 @@ package fr.free.nrw.commons.nearby;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@ -21,6 +22,9 @@ import java.lang.reflect.Type;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import dagger.android.support.AndroidSupportInjection; import dagger.android.support.AndroidSupportInjection;
import dagger.android.support.DaggerFragment; import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
@ -33,6 +37,7 @@ import static android.app.Activity.RESULT_OK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.pm.PackageManager.PERMISSION_GRANTED;
public class NearbyListFragment extends DaggerFragment { public class NearbyListFragment extends DaggerFragment {
private Bundle bundleForUpdates; // Carry information from activity about changed nearby places and current location private Bundle bundleForUpdates; // Carry information from activity about changed nearby places and current location
private static final Type LIST_TYPE = new TypeToken<List<Place>>() { private static final Type LIST_TYPE = new TypeToken<List<Place>>() {
@ -129,7 +134,6 @@ public class NearbyListFragment extends DaggerFragment {
} }
} }
@Override @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);

View file

@ -7,6 +7,7 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Typeface;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
@ -14,6 +15,7 @@ import android.support.annotation.Nullable;
import android.support.design.widget.BottomSheetBehavior; import android.support.design.widget.BottomSheetBehavior;
import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AlertDialog;
import android.view.Gravity; import android.view.Gravity;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -52,19 +54,27 @@ import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import dagger.android.support.DaggerFragment; import dagger.android.support.DaggerFragment;
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.Utils;
import fr.free.nrw.commons.auth.LoginActivity;
import fr.free.nrw.commons.category.CategoryImagesActivity;
import fr.free.nrw.commons.contributions.ContributionController; import fr.free.nrw.commons.contributions.ContributionController;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.utils.UriDeserializer; import fr.free.nrw.commons.utils.UriDeserializer;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import timber.log.Timber; import timber.log.Timber;
import uk.co.deanwild.materialshowcaseview.MaterialShowcaseView;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static fr.free.nrw.commons.theme.NavigationBaseActivity.startActivityWithFlags;
public class NearbyMapFragment extends DaggerFragment { public class NearbyMapFragment extends DaggerFragment {
private MapView mapView; @Inject
@Named("application_preferences") SharedPreferences applicationPrefs;
public MapView mapView;
private List<NearbyBaseMarker> baseMarkerOptions; private List<NearbyBaseMarker> baseMarkerOptions;
private fr.free.nrw.commons.location.LatLng curLatLng; private fr.free.nrw.commons.location.LatLng curLatLng;
public fr.free.nrw.commons.location.LatLng[] boundaryCoordinates; public fr.free.nrw.commons.location.LatLng[] boundaryCoordinates;
@ -111,6 +121,10 @@ public class NearbyMapFragment extends DaggerFragment {
private final double CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT = 0.06; private final double CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT = 0.06;
private final double CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE = 0.04; private final double CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE = 0.04;
private boolean isSecondMaterialShowcaseDismissed;
private boolean isMapReady;
private MaterialShowcaseView thirdSingleShowCaseView;
private Bundle bundleForUpdtes;// Carry information from activity about changed nearby places and current location private Bundle bundleForUpdtes;// Carry information from activity about changed nearby places and current location
@Inject @Inject
@ -163,7 +177,6 @@ public class NearbyMapFragment extends DaggerFragment {
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
Timber.d("onCreateView called"); Timber.d("onCreateView called");
if (curLatLng != null) { if (curLatLng != null) {
Timber.d("curLatLng found, setting up map view..."); Timber.d("curLatLng found, setting up map view...");
@ -366,7 +379,26 @@ public class NearbyMapFragment extends DaggerFragment {
} }
private void setListeners() { private void setListeners() {
fabPlus.setOnClickListener(view -> animateFAB(isFabOpen)); fabPlus.setOnClickListener(view -> {
if (applicationPrefs.getBoolean("login_skipped", true)) {
// prompt the user to login
new AlertDialog.Builder(getContext())
.setMessage(R.string.login_alert_message)
.setPositiveButton(R.string.login, (dialog, which) -> {
// logout of the app
// startActivityWithFlags( getContext(), CategoryImagesActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
// Intent.FLAG_ACTIVITY_SINGLE_TOP);
// getActivity().finish();
BaseLogoutListener logoutListener = new BaseLogoutListener();
CommonsApplication app = (CommonsApplication) getActivity().getApplication();
app.clearApplicationData(getContext(), logoutListener);
})
.show();
}else {
animateFAB(isFabOpen);
}
});
bottomSheetDetails.setOnClickListener(view -> { bottomSheetDetails.setOnClickListener(view -> {
if (bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) { if (bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
@ -476,6 +508,7 @@ public class NearbyMapFragment extends DaggerFragment {
mapView.getMapAsync(new OnMapReadyCallback() { mapView.getMapAsync(new OnMapReadyCallback() {
@Override @Override
public void onMapReady(MapboxMap mapboxMap) { public void onMapReady(MapboxMap mapboxMap) {
((NearbyActivity)getActivity()).setMapViewTutorialShowCase();
NearbyMapFragment.this.mapboxMap = mapboxMap; NearbyMapFragment.this.mapboxMap = mapboxMap;
updateMapSignificantly(); updateMapSignificantly();
} }
@ -483,6 +516,18 @@ public class NearbyMapFragment extends DaggerFragment {
mapView.setStyleUrl("asset://mapstyle.json"); mapView.setStyleUrl("asset://mapstyle.json");
} }
private class BaseLogoutListener implements CommonsApplication.LogoutListener {
@Override
public void onLogoutComplete() {
Timber.d("Logout complete callback received.");
Intent nearbyIntent = new Intent( getActivity(), LoginActivity.class);
nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(nearbyIntent);
getActivity().finish();
}
}
/** /**
* Adds a marker for the user's current position. Adds a * Adds a marker for the user's current position. Adds a
* circle which uses the accuracy * 2, to draw a circle * circle which uses the accuracy * 2, to draw a circle
@ -519,6 +564,7 @@ public class NearbyMapFragment extends DaggerFragment {
private void addNearbyMarkerstoMapBoxMap() { private void addNearbyMarkerstoMapBoxMap() {
mapboxMap.addMarkers(baseMarkerOptions); mapboxMap.addMarkers(baseMarkerOptions);
mapboxMap.setOnInfoWindowCloseListener(marker -> { mapboxMap.setOnInfoWindowCloseListener(marker -> {
if (marker == selected) { if (marker == selected) {
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
@ -534,6 +580,7 @@ public class NearbyMapFragment extends DaggerFragment {
}); });
mapboxMap.setOnMarkerClickListener(marker -> { mapboxMap.setOnMarkerClickListener(marker -> {
if (marker instanceof NearbyMarker) { if (marker instanceof NearbyMarker) {
this.selected = marker; this.selected = marker;
NearbyMarker nearbyMarker = (NearbyMarker) marker; NearbyMarker nearbyMarker = (NearbyMarker) marker;
@ -541,6 +588,7 @@ public class NearbyMapFragment extends DaggerFragment {
passInfoToSheet(place); passInfoToSheet(place);
bottomSheetListBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); bottomSheetListBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
} }
return false; return false;
}); });
@ -634,7 +682,19 @@ public class NearbyMapFragment extends DaggerFragment {
addAnchorToSmallFABs(fabGallery, getActivity().findViewById(R.id.empty_view).getId()); addAnchorToSmallFABs(fabGallery, getActivity().findViewById(R.id.empty_view).getId());
addAnchorToSmallFABs(fabCamera, getActivity().findViewById(R.id.empty_view1).getId()); addAnchorToSmallFABs(fabCamera, getActivity().findViewById(R.id.empty_view1).getId());
thirdSingleShowCaseView = new MaterialShowcaseView.Builder(this.getActivity())
.setTarget(fabPlus)
.setDismissText(getString(R.string.showcase_view_got_it_button))
.setContentText(getString(R.string.showcase_view_plus_fab))
.setDelay(500) // optional but starting animations immediately in onCreate can make them choppy
.singleUse(ViewUtil.SHOWCASE_VIEW_ID_3) // provide a unique ID used to ensure it is only shown once
.setDismissStyle(Typeface.defaultFromStyle(Typeface.BOLD))
.build();
isMapReady = true;
if (isSecondMaterialShowcaseDismissed) {
thirdSingleShowCaseView.show(getActivity());
}
} }
@ -757,7 +817,7 @@ public class NearbyMapFragment extends DaggerFragment {
} }
private void animateFAB(boolean isFabOpen) { private void animateFAB(boolean isFabOpen) {
this.isFabOpen = !isFabOpen; this.isFabOpen = !isFabOpen;
if (fabPlus.isShown()){ if (fabPlus.isShown()){
if (isFabOpen) { if (isFabOpen) {
fabPlus.startAnimation(rotate_backward); fabPlus.startAnimation(rotate_backward);
@ -791,6 +851,13 @@ public class NearbyMapFragment extends DaggerFragment {
this.bundleForUpdtes = bundleForUpdtes; this.bundleForUpdtes = bundleForUpdtes;
} }
public void onNearbyMaterialShowcaseDismissed() {
isSecondMaterialShowcaseDismissed = true;
if (isMapReady) {
thirdSingleShowCaseView.show(getActivity());
}
}
@Override @Override
public void onStart() { public void onStart() {

View file

@ -0,0 +1,18 @@
package fr.free.nrw.commons.nearby;
import android.app.Activity;
import uk.co.deanwild.materialshowcaseview.MaterialShowcaseSequence;
import uk.co.deanwild.materialshowcaseview.ShowcaseConfig;
public class NearbyMaterialShowcaseSequence extends MaterialShowcaseSequence {
public NearbyMaterialShowcaseSequence(Activity activity, String sequenceID) {
super(activity, sequenceID);
ShowcaseConfig config = new ShowcaseConfig();
config.setDelay(500); // half second between each showcase view
this.setConfig(config);
this.singleUse(sequenceID); // Display tutorial only once
}
}

View file

@ -6,6 +6,7 @@ import android.net.Uri;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.transition.TransitionManager; import android.support.transition.TransitionManager;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.PopupMenu; import android.support.v7.widget.PopupMenu;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -28,12 +29,17 @@ import butterknife.ButterKnife;
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.Utils;
import fr.free.nrw.commons.auth.LoginActivity;
import fr.free.nrw.commons.contributions.ContributionController; import fr.free.nrw.commons.contributions.ContributionController;
import fr.free.nrw.commons.di.ApplicationlessInjection; import fr.free.nrw.commons.di.ApplicationlessInjection;
import timber.log.Timber; import timber.log.Timber;
import static fr.free.nrw.commons.theme.NavigationBaseActivity.startActivityWithFlags;
public class PlaceRenderer extends Renderer<Place> { public class PlaceRenderer extends Renderer<Place> {
@Inject
@Named("application_preferences") SharedPreferences applicationPrefs;
@BindView(R.id.tvName) TextView tvName; @BindView(R.id.tvName) TextView tvName;
@BindView(R.id.tvDesc) TextView tvDesc; @BindView(R.id.tvDesc) TextView tvDesc;
@BindView(R.id.distance) TextView distance; @BindView(R.id.distance) TextView distance;
@ -89,9 +95,9 @@ public class PlaceRenderer extends Renderer<Place> {
Log.d("Renderer", "clicked"); Log.d("Renderer", "clicked");
TransitionManager.beginDelayedTransition(buttonLayout); TransitionManager.beginDelayedTransition(buttonLayout);
if(buttonLayout.isShown()){ if (buttonLayout.isShown()) {
closeLayout(buttonLayout); closeLayout(buttonLayout);
}else { } else {
openLayout(buttonLayout); openLayout(buttonLayout);
} }
@ -107,18 +113,46 @@ public class PlaceRenderer extends Renderer<Place> {
}); });
cameraButton.setOnClickListener(view2 -> { cameraButton.setOnClickListener(view2 -> {
Timber.d("Camera button tapped. Image title: " + place.getName() + "Image desc: " + place.getLongDescription()); if (applicationPrefs.getBoolean("login_skipped", true)) {
DirectUpload directUpload = new DirectUpload(fragment, controller); // prompt the user to login
storeSharedPrefs(); new AlertDialog.Builder(getContext())
directUpload.initiateCameraUpload(); .setMessage(R.string.login_alert_message)
.setPositiveButton(R.string.login, (dialog, which) -> {
startActivityWithFlags( getContext(), LoginActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
Intent.FLAG_ACTIVITY_SINGLE_TOP);
prefs.edit().putBoolean("login_skipped", false).apply();
fragment.getActivity().finish();
})
.show();
} else {
Timber.d("Camera button tapped. Image title: " + place.getName() + "Image desc: " + place.getLongDescription());
DirectUpload directUpload = new DirectUpload(fragment, controller);
storeSharedPrefs();
directUpload.initiateCameraUpload();
}
}); });
galleryButton.setOnClickListener(view3 -> { galleryButton.setOnClickListener(view3 -> {
Timber.d("Gallery button tapped. Image title: " + place.getName() + "Image desc: " + place.getLongDescription()); if (applicationPrefs.getBoolean("login_skipped", true)) {
DirectUpload directUpload = new DirectUpload(fragment, controller); // prompt the user to login
storeSharedPrefs(); new AlertDialog.Builder(getContext())
directUpload.initiateGalleryUpload(); .setMessage(R.string.login_alert_message)
.setPositiveButton(R.string.login, (dialog, which) -> {
startActivityWithFlags( getContext(), LoginActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
Intent.FLAG_ACTIVITY_SINGLE_TOP);
prefs.edit().putBoolean("login_skipped", false).apply();
fragment.getActivity().finish();
})
.show();
}else {
Timber.d("Gallery button tapped. Image title: " + place.getName() + "Image desc: " + place.getLongDescription());
DirectUpload directUpload = new DirectUpload(fragment, controller);
storeSharedPrefs();
directUpload.initiateGalleryUpload();
}
}); });
} }
private void storeSharedPrefs() { private void storeSharedPrefs() {

View file

@ -32,7 +32,7 @@ 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;
@ -41,6 +41,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)
@ -173,8 +175,8 @@ public abstract class NavigationBaseActivity extends BaseActivity
.setCancelable(false) .setCancelable(false)
.setPositiveButton(R.string.yes, (dialog, which) -> { .setPositiveButton(R.string.yes, (dialog, which) -> {
BaseLogoutListener logoutListener = new BaseLogoutListener(); BaseLogoutListener logoutListener = new BaseLogoutListener();
CommonsApplication app = (CommonsApplication) getApplication(); // CommonsApplication app = (CommonsApplication) getApplication();
app.clearApplicationData(this, logoutListener); // app.clearApplicationData(this, logoutListener);
}) })
.setNegativeButton(R.string.no, (dialog, which) -> dialog.cancel()) .setNegativeButton(R.string.no, (dialog, which) -> dialog.cancel())
.show(); .show();
@ -185,7 +187,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);

View file

@ -81,8 +81,7 @@ import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import timber.log.Timber; import timber.log.Timber;
import android.support.design.widget.FloatingActionButton;
import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.DUPLICATE_PROCEED; import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.DUPLICATE_PROCEED;
import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.NO_DUPLICATE; import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.NO_DUPLICATE;
import static java.lang.Long.min; import static java.lang.Long.min;
@ -122,6 +121,7 @@ public class ShareActivity
private Uri mediaUri; private Uri mediaUri;
private Contribution contribution; private Contribution contribution;
private SimpleDraweeView backgroundImageView; private SimpleDraweeView backgroundImageView;
private FloatingActionButton maps_fragment;
private boolean cacheFound; private boolean cacheFound;
@ -145,6 +145,8 @@ public class ShareActivity
private long ShortAnimationDuration; private long ShortAnimationDuration;
private FloatingActionButton zoomInButton; private FloatingActionButton zoomInButton;
private FloatingActionButton zoomOutButton; private FloatingActionButton zoomOutButton;
private FloatingActionButton mainFab;
private boolean isFABOpen = false;
/** /**
@ -284,6 +286,24 @@ public class ShareActivity
if (mediaUri != null) { if (mediaUri != null) {
backgroundImageView.setImageURI(mediaUri); backgroundImageView.setImageURI(mediaUri);
} }
mainFab = (FloatingActionButton) findViewById(R.id.main_fab);
/*
* called when upper arrow floating button
*/
mainFab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(!isFABOpen){
showFABMenu();
}else{
closeFABMenu();
}
}
});
zoomInButton = (FloatingActionButton) findViewById(R.id.media_upload_zoom_in); zoomInButton = (FloatingActionButton) findViewById(R.id.media_upload_zoom_in);
try { try {
zoomInButton.setOnClickListener(new View.OnClickListener() { zoomInButton.setOnClickListener(new View.OnClickListener() {
@ -358,7 +378,74 @@ public class ShareActivity
.commitAllowingStateLoss(); .commitAllowingStateLoss();
} }
uploadController.prepareService(); uploadController.prepareService();
maps_fragment = (FloatingActionButton) findViewById(R.id.media_map);
maps_fragment.setVisibility(View.VISIBLE);
if( imageObj == null || imageObj.imageCoordsExists != true){
maps_fragment.setVisibility(View.INVISIBLE);
}
maps_fragment.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if( imageObj != null && imageObj.imageCoordsExists == true) {
Uri gmmIntentUri = Uri.parse("google.streetview:cbll=" + imageObj.getDecLatitude() + "," + imageObj.getDecLongitude());
Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
mapIntent.setPackage("com.google.android.apps.maps");
startActivity(mapIntent);
}
}
});
} }
/*
* Function to display the zoom and map FAB
*/
private void showFABMenu(){
isFABOpen=true;
if( imageObj != null && imageObj.imageCoordsExists == true)
maps_fragment.setVisibility(View.VISIBLE);
zoomInButton.setVisibility(View.VISIBLE);
mainFab.animate().rotationBy(180);
maps_fragment.animate().translationY(-getResources().getDimension(R.dimen.second_fab));
zoomInButton.animate().translationY(-getResources().getDimension(R.dimen.first_fab));
}
/*
* function to close the zoom and map FAB
*/
private void closeFABMenu(){
isFABOpen=false;
mainFab.animate().rotationBy(-180);
maps_fragment.animate().translationY(0);
zoomInButton.animate().translationY(0).setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
if(!isFABOpen){
maps_fragment.setVisibility(View.GONE);
zoomInButton.setVisibility(View.GONE);
}
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
}
@Override @Override
public void onRequestPermissionsResult(int requestCode, public void onRequestPermissionsResult(int requestCode,
@ -461,6 +548,9 @@ public class ShareActivity
detectUnwantedPicturesAsync.execute(); detectUnwantedPicturesAsync.execute();
} }
/*
* to display permission snackbar in share activity
*/
private Snackbar requestPermissionUsingSnackBar(String rationale, private Snackbar requestPermissionUsingSnackBar(String rationale,
final String[] perms, final String[] perms,
final int code) { final int code) {
@ -693,7 +783,9 @@ public class ShareActivity
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
// Get SHA1 of file from input stream /*
* Get SHA1 of file from input stream
*/
private String getSHA1(InputStream is) { private String getSHA1(InputStream is) {
MessageDigest digest; MessageDigest digest;
@ -730,6 +822,9 @@ public class ShareActivity
} }
} }
/*
* function to provide pinch zoom
*/
private void zoomImageFromThumb(final View thumbView, Uri imageuri ) { private void zoomImageFromThumb(final View thumbView, Uri imageuri ) {
// If there's an animation in progress, cancel it // If there's an animation in progress, cancel it
// immediately and proceed with this one. // immediately and proceed with this one.
@ -737,6 +832,8 @@ public class ShareActivity
CurrentAnimator.cancel(); CurrentAnimator.cancel();
} }
ViewUtil.hideKeyboard(ShareActivity.this.findViewById(R.id.titleEdit | R.id.descEdit)); ViewUtil.hideKeyboard(ShareActivity.this.findViewById(R.id.titleEdit | R.id.descEdit));
closeFABMenu();
mainFab.setVisibility(View.GONE);
InputStream input = null; InputStream input = null;
Bitmap scaled = null; Bitmap scaled = null;
try { try {
@ -866,7 +963,7 @@ public class ShareActivity
CurrentAnimator.cancel(); CurrentAnimator.cancel();
} }
zoomOutButton.setVisibility(View.GONE); zoomOutButton.setVisibility(View.GONE);
zoomInButton.setVisibility(View.VISIBLE); mainFab.setVisibility(View.VISIBLE);
// Animate the four positioning/sizing properties in parallel, // Animate the four positioning/sizing properties in parallel,
// back to their original values. // back to their original values.

View file

@ -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"));
}
}

View file

@ -10,6 +10,10 @@ import android.widget.Toast;
public class ViewUtil { public class ViewUtil {
public static final String SHOWCASE_VIEW_ID_1 = "SHOWCASE_VIEW_ID_1";
public static final String SHOWCASE_VIEW_ID_2 = "SHOWCASE_VIEW_ID_2";
public static final String SHOWCASE_VIEW_ID_3 = "SHOWCASE_VIEW_ID_3";
public static void showSnackbar(View view, int messageResourceId) { public static void showSnackbar(View view, int messageResourceId) {
Snackbar.make(view, messageResourceId, Snackbar.LENGTH_SHORT).show(); Snackbar.make(view, messageResourceId, Snackbar.LENGTH_SHORT).show();
} }

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
</vector>

View file

@ -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>

View file

@ -41,8 +41,6 @@
</FrameLayout> </FrameLayout>
<android.support.design.widget.FloatingActionButton <android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -50,8 +48,21 @@
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:layout_marginRight="@dimen/standard_gap" android:layout_marginRight="@dimen/standard_gap"
android:layout_marginBottom="@dimen/standard_gap" android:layout_marginBottom="@dimen/standard_gap"
app:backgroundTint="@color/button_blue"
app:srcCompat="@drawable/ic_keyboard_arrow_up_black_24dp"
android:id="@+id/main_fab"/>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:visibility="gone"
android:layout_marginRight="@dimen/standard_gap"
android:src="@drawable/ic_zoom_in_white_24dp" android:src="@drawable/ic_zoom_in_white_24dp"
android:layout_above="@+id/main_fab"
android:id="@+id/media_upload_zoom_in"/> android:id="@+id/media_upload_zoom_in"/>
<android.support.design.widget.FloatingActionButton <android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -61,7 +72,19 @@
android:layout_marginRight="@dimen/standard_gap" android:layout_marginRight="@dimen/standard_gap"
android:layout_marginBottom="@dimen/standard_gap" android:layout_marginBottom="@dimen/standard_gap"
android:src="@drawable/ic_zoom_out_white_24dp" android:src="@drawable/ic_zoom_out_white_24dp"
android:layout_above="@+id/main_fab"
android:id="@+id/media_upload_zoom_out"/> android:id="@+id/media_upload_zoom_out"/>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_above="@+id/media_upload_zoom_in"
android:visibility="gone"
android:layout_marginRight="@dimen/standard_gap"
app:srcCompat="@drawable/ic_map_white_24dp"
android:id="@+id/media_map"/>
</RelativeLayout> </RelativeLayout>
<android.support.design.widget.NavigationView <android.support.design.widget.NavigationView

View file

@ -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"

View file

@ -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"

View file

@ -269,4 +269,5 @@
<string name="about_translate_proceed">Fortfahren</string> <string name="about_translate_proceed">Fortfahren</string>
<string name="about_translate_cancel">Abbrechen</string> <string name="about_translate_cancel">Abbrechen</string>
<string name="retry">Erneut versuchen</string> <string name="retry">Erneut versuchen</string>
<string name="share_app_title">App teilen</string>
</resources> </resources>

View file

@ -273,4 +273,5 @@
<string name="about_translate_proceed">Συνέχεια</string> <string name="about_translate_proceed">Συνέχεια</string>
<string name="about_translate_cancel">Ακύρωση</string> <string name="about_translate_cancel">Ακύρωση</string>
<string name="retry">Ξαναπροσπαθήστε</string> <string name="retry">Ξαναπροσπαθήστε</string>
<string name="share_app_title">Κοινοποίηση εφαρμογής</string>
</resources> </resources>

View file

@ -279,4 +279,5 @@
<string name="about_translate_proceed">Continuer</string> <string name="about_translate_proceed">Continuer</string>
<string name="about_translate_cancel">Annuler</string> <string name="about_translate_cancel">Annuler</string>
<string name="retry">Réessayer</string> <string name="retry">Réessayer</string>
<string name="share_app_title">Partager les applications</string>
</resources> </resources>

View file

@ -13,6 +13,7 @@
* ViDam * ViDam
--> -->
<resources> <resources>
<string name="preference_category_appearance">Megjelenés</string>
<string name="preference_category_general">Általános</string> <string name="preference_category_general">Általános</string>
<string name="preference_category_feedback">Visszajelzés</string> <string name="preference_category_feedback">Visszajelzés</string>
<string name="preference_category_location">Helyszín</string> <string name="preference_category_location">Helyszín</string>
@ -145,6 +146,7 @@
<string name="tutorial_2_subtext_3">Híres emberek (a polgármestered, olimpikonok, akikkel találkoztál)</string> <string name="tutorial_2_subtext_3">Híres emberek (a polgármestered, olimpikonok, akikkel találkoztál)</string>
<string name="tutorial_3_text">Kérjük, NE tölts fel:</string> <string name="tutorial_3_text">Kérjük, NE tölts fel:</string>
<string name="tutorial_3_subtext">- Szelfiket vagy képeket a barátaidról\n- Internetröl letöltött képeket\n- Kereskedelmi alkalmazások képernyőképeit</string> <string name="tutorial_3_subtext">- Szelfiket vagy képeket a barátaidról\n- Internetröl letöltött képeket\n- Kereskedelmi alkalmazások képernyőképeit</string>
<string name="tutorial_3_subtext_2">Az Internetről letöltött képek</string>
<string name="tutorial_4_text">Példa feltöltés:</string> <string name="tutorial_4_text">Példa feltöltés:</string>
<string name="tutorial_4_subtext">- Cím: Sydney-i Operaház\n- Leírás: A Sydney-i Operaház az öböl túlpartjáról\n- Kategóriák: Sydney Opera House from the west, Sydney Opera House remote views</string> <string name="tutorial_4_subtext">- Cím: Sydney-i Operaház\n- Leírás: A Sydney-i Operaház az öböl túlpartjáról\n- Kategóriák: Sydney Opera House from the west, Sydney Opera House remote views</string>
<string name="tutorial_4_subtext_1">Cím: Sydney-i Operaház</string> <string name="tutorial_4_subtext_1">Cím: Sydney-i Operaház</string>
@ -191,6 +193,7 @@
<string name="commons_logo">Commons Logo</string> <string name="commons_logo">Commons Logo</string>
<string name="commons_website">Commons weboldal</string> <string name="commons_website">Commons weboldal</string>
<string name="commons_facebook">Commons Facebook-oldal</string> <string name="commons_facebook">Commons Facebook-oldal</string>
<string name="commons_github">Commons Github forráskód</string>
<string name="background_image">Háttérkép</string> <string name="background_image">Háttérkép</string>
<string name="no_image_found">Nem található kép</string> <string name="no_image_found">Nem található kép</string>
<string name="upload_image">Kép feltöltése</string> <string name="upload_image">Kép feltöltése</string>
@ -223,6 +226,8 @@
<string name="error_while_cache">Hiba a képek gyorsítótárazásakor</string> <string name="error_while_cache">Hiba a képek gyorsítótárazásakor</string>
<string name="title_info">Egy egyedi, leíró cím a fájlnak, ami fájlnévként fog szolgálni. Egyszerű nyelvezetet használhatsz szóközökkel. Ne tedd bele a kiterjesztést.</string> <string name="title_info">Egy egyedi, leíró cím a fájlnak, ami fájlnévként fog szolgálni. Egyszerű nyelvezetet használhatsz szóközökkel. Ne tedd bele a kiterjesztést.</string>
<string name="description_info">Kérlek a lehető legteljesebb módon írd le a fájlt: hol készült, mit ábrázol, mi a kontextus? Kérlek add meg az objektumokat vagy személyeket a képen, valamint a nehezen kitalálható információkat (például a kép készítésének dátumát, ha az egy tájkép). Amennyiben a média valami szokatlant ábrázol, kérlek fejtsd ki, hogy mi teszi szokatlanná.</string> <string name="description_info">Kérlek a lehető legteljesebb módon írd le a fájlt: hol készült, mit ábrázol, mi a kontextus? Kérlek add meg az objektumokat vagy személyeket a képen, valamint a nehezen kitalálható információkat (például a kép készítésének dátumát, ha az egy tájkép). Amennyiben a média valami szokatlant ábrázol, kérlek fejtsd ki, hogy mi teszi szokatlanná.</string>
<string name="upload_image_too_dark">Ez a fénykép túl sötét, biztos fel akarod tölteni? A Wikimédia Commons csak enciklopédikus értékkel bíró képeket tart meg.</string>
<string name="upload_image_blurry">Ez a fénykép homályos, biztos fel akarod tölteni? A Wikimédia Commons csak enciklopédikus értékkel bíró képeket tart meg.</string>
<string name="give_permission">Engedély adása</string> <string name="give_permission">Engedély adása</string>
<string name="use_external_storage">Külső tárhely használata</string> <string name="use_external_storage">Külső tárhely használata</string>
<string name="use_external_storage_summary">Az alkalmazáson belüli kamerával készült képek mentése az eszközre</string> <string name="use_external_storage_summary">Az alkalmazáson belüli kamerával készült képek mentése az eszközre</string>
@ -238,6 +243,7 @@
<string name="nearby_location_not_available">A hely nem érhető el.</string> <string name="nearby_location_not_available">A hely nem érhető el.</string>
<string name="location_permission_rationale_nearby">Közeli helyek listájának megtekintéséhez engedély szükséges</string> <string name="location_permission_rationale_nearby">Közeli helyek listájának megtekintéséhez engedély szükséges</string>
<string name="notifications_welcome">Üdvözlünk a Wikimedia Commonson, %1$s! Örülünk, hogy itt vagy.</string> <string name="notifications_welcome">Üdvözlünk a Wikimedia Commonson, %1$s! Örülünk, hogy itt vagy.</string>
<string name="notifications_talk_page_message">%1$s üzenetet hagyott a vitalapodon</string>
<string name="notifications_thank_you_edit">Köszönjük a szerkesztésedet!</string> <string name="notifications_thank_you_edit">Köszönjük a szerkesztésedet!</string>
<string name="nearby_wikidata">WIKIDATA</string> <string name="nearby_wikidata">WIKIDATA</string>
<string name="nearby_wikipedia">WIKIPÉDIA</string> <string name="nearby_wikipedia">WIKIPÉDIA</string>
@ -249,6 +255,8 @@
<string name="internet_established">Internet elérhető</string> <string name="internet_established">Internet elérhető</string>
<string name="no_notifications">Nincs értesítés</string> <string name="no_notifications">Nincs értesítés</string>
<string name="about_translate_title">Nyelvek</string> <string name="about_translate_title">Nyelvek</string>
<string name="about_translate_proceed">Folytatás</string>
<string name="about_translate_cancel">Mégse</string> <string name="about_translate_cancel">Mégse</string>
<string name="retry">Újra</string> <string name="retry">Újra</string>
<string name="share_app_title">Alkalmazás megosztása</string>
</resources> </resources>

View file

@ -273,4 +273,5 @@
<string name="about_translate_proceed">המשך</string> <string name="about_translate_proceed">המשך</string>
<string name="about_translate_cancel">ביטול</string> <string name="about_translate_cancel">ביטול</string>
<string name="retry">לנסות שוב</string> <string name="retry">לנסות שוב</string>
<string name="share_app_title">שיתוף היישום</string>
</resources> </resources>

View file

@ -266,4 +266,5 @@
<string name="about_translate_proceed">진행</string> <string name="about_translate_proceed">진행</string>
<string name="about_translate_cancel">취소</string> <string name="about_translate_cancel">취소</string>
<string name="retry">다시 시도</string> <string name="retry">다시 시도</string>
<string name="share_app_title">앱 공유</string>
</resources> </resources>

View file

@ -263,4 +263,5 @@
<string name="about_translate_proceed">Продолжи</string> <string name="about_translate_proceed">Продолжи</string>
<string name="about_translate_cancel">Откажи</string> <string name="about_translate_cancel">Откажи</string>
<string name="retry">Пробај пак</string> <string name="retry">Пробај пак</string>
<string name="share_app_title">Сподели прилог</string>
</resources> </resources>

View file

@ -263,4 +263,5 @@
<string name="about_translate_proceed">Andé anans</string> <string name="about_translate_proceed">Andé anans</string>
<string name="about_translate_cancel">Anulé</string> <string name="about_translate_cancel">Anulé</string>
<string name="retry">Prové torna</string> <string name="retry">Prové torna</string>
<string name="share_app_title">Partagé j\'aplicassion</string>
</resources> </resources>

View file

@ -275,4 +275,5 @@
<string name="about_translate_proceed">Avançar</string> <string name="about_translate_proceed">Avançar</string>
<string name="about_translate_cancel">Cancelar</string> <string name="about_translate_cancel">Cancelar</string>
<string name="retry">Tentar novamente</string> <string name="retry">Tentar novamente</string>
<string name="share_app_title">Compartilhar o aplicativo</string>
</resources> </resources>

View file

@ -159,6 +159,7 @@
<string name="welcome_copyright_subtext">Evite materiais protegidos por direitos de autor que tenham sido encontrados na Internet, bem como imagens de cartazes, capas de livros, etc.</string> <string name="welcome_copyright_subtext">Evite materiais protegidos por direitos de autor que tenham sido encontrados na Internet, bem como imagens de cartazes, capas de livros, etc.</string>
<string name="welcome_final_text">Acha que conseguiu?</string> <string name="welcome_final_text">Acha que conseguiu?</string>
<string name="welcome_final_button_text">Sim!</string> <string name="welcome_final_button_text">Sim!</string>
<string name="welcome_help_button_text"/>
<string name="detail_panel_cats_label">Categorias</string> <string name="detail_panel_cats_label">Categorias</string>
<string name="detail_panel_cats_loading">A carregar…</string> <string name="detail_panel_cats_loading">A carregar…</string>
<string name="detail_panel_cats_none">Nenhuma selecionada</string> <string name="detail_panel_cats_none">Nenhuma selecionada</string>

View file

@ -156,6 +156,7 @@
<string name="welcome_copyright_subtext">Undvik upphovsrättsskyddat material som du hittar på Internet, samt bilder av affischer, bokomslag, etc.</string> <string name="welcome_copyright_subtext">Undvik upphovsrättsskyddat material som du hittar på Internet, samt bilder av affischer, bokomslag, etc.</string>
<string name="welcome_final_text">Tror du att du förstår?</string> <string name="welcome_final_text">Tror du att du förstår?</string>
<string name="welcome_final_button_text">Ja!</string> <string name="welcome_final_button_text">Ja!</string>
<string name="welcome_help_button_text"/>
<string name="detail_panel_cats_label">Kategorier</string> <string name="detail_panel_cats_label">Kategorier</string>
<string name="detail_panel_cats_loading">Läser in…</string> <string name="detail_panel_cats_loading">Läser in…</string>
<string name="detail_panel_cats_none">Ingen markerad</string> <string name="detail_panel_cats_none">Ingen markerad</string>
@ -270,4 +271,5 @@
<string name="about_translate_proceed">Fortsätt</string> <string name="about_translate_proceed">Fortsätt</string>
<string name="about_translate_cancel">Avbryt</string> <string name="about_translate_cancel">Avbryt</string>
<string name="retry">Försök igen</string> <string name="retry">Försök igen</string>
<string name="share_app_title">Dela app</string>
</resources> </resources>

View file

@ -271,4 +271,5 @@
<string name="about_translate_proceed">已進行</string> <string name="about_translate_proceed">已進行</string>
<string name="about_translate_cancel">取消</string> <string name="about_translate_cancel">取消</string>
<string name="retry">重試</string> <string name="retry">重試</string>
<string name="share_app_title">分享應用程式</string>
</resources> </resources>

View file

@ -269,4 +269,5 @@
<string name="about_translate_proceed">已处理</string> <string name="about_translate_proceed">已处理</string>
<string name="about_translate_cancel">取消</string> <string name="about_translate_cancel">取消</string>
<string name="retry">重试</string> <string name="retry">重试</string>
<string name="share_app_title">分享应用</string>
</resources> </resources>

View file

@ -23,4 +23,6 @@
<dimen name="subheading_text_size">20sp</dimen> <dimen name="subheading_text_size">20sp</dimen>
<dimen name="normal_text">16sp</dimen> <dimen name="normal_text">16sp</dimen>
<dimen name="description_text_size">14sp</dimen> <dimen name="description_text_size">14sp</dimen>
<dimen name="first_fab">15dp</dimen>
<dimen name="second_fab">25dp</dimen>
</resources> </resources>

View file

@ -241,13 +241,11 @@
<string name="nominated_for_deletion">This image has been nominated for deletion.</string> <string name="nominated_for_deletion">This image has been nominated for deletion.</string>
<string name="nominated_see_more"><u>See webpage for details</u></string> <string name="nominated_see_more"><u>See webpage for details</u></string>
<string name="view_browser">View in Browser</string> <string name="view_browser">View in Browser</string>
<string name="nearby_location_has_not_changed">Location has not changed.</string> <string name="nearby_location_has_not_changed">Location has not changed.</string>
<string name="nearby_location_not_available">Location not available.</string> <string name="nearby_location_not_available">Location not available.</string>
<string name="location_permission_rationale_nearby">Permission required to display a list of nearby places</string> <string name="location_permission_rationale_nearby">Permission required to display a list of nearby places</string>
<string name="get_directions">GET DIRECTIONS</string> <string name="get_directions">GET DIRECTIONS</string>
<string name="read_article">READ ARTICLE</string> <string name="read_article">READ ARTICLE</string>
<string name="notifications_welcome" formatted="false">Welcome to Wikimedia Commons, %1$s! We\'re glad you\'re here.</string> <string name="notifications_welcome" formatted="false">Welcome to Wikimedia Commons, %1$s! We\'re glad you\'re here.</string>
<string name="notifications_talk_page_message">%1$s left a message on your talk page</string> <string name="notifications_talk_page_message">%1$s left a message on your talk page</string>
<string name="notifications_thank_you_edit">Thank you for making an edit</string> <string name="notifications_thank_you_edit">Thank you for making an edit</string>
@ -270,9 +268,19 @@
<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="skip_login">Skip </string> <string name="skip_login">Skip</string>
<string name="navigation_item_login">Login</string> <string name="navigation_item_login">Login</string>
<string name="skip_login_title">Do you really want to skip login ?</string> <string name="skip_login_title">Do you really want to skip login ?</string>
<string name="skip_login_message">You might not be able to access some features of the app.</string>======= <string name="skip_login_message">You might not be able to access some features of the app.</string>
<string name="showcase_view_got_it_button">Got it!</string>
<string name="showcase_view_whole_nearby_activity">These are the places near you that need pictures to illustrate their Wikipedia articles</string>
<string name="showcase_view_list_icon">Tapping this button brings up a list of these places</string>
<string name="showcase_view_plus_fab">You can upload a picture for any place from your gallery or camera</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>
<string name="share_coordinates_not_present">Coordinates were not specified during image selection</string>
<string name="login_alert_message">This feature requires user to be logged in !!</string>
</resources> </resources>

View file

@ -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

View file

@ -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 + "/")
} }