Merge remote-tracking branch 'refs/remotes/commons-app/master'

This commit is contained in:
misaochan 2018-05-08 17:38:14 +10:00
commit 49a808fbf3
62 changed files with 1518 additions and 496 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,
# 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"
is a filename or identifier for the general area of the code being modified.
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.}
## Tests performed
## Tests performed (required)
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
## Screenshots showing what changed (optional)
{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
}
implementation "com.github.deano2390:MaterialShowcaseView:1.2.0"
implementation "com.android.support:support-v4:$SUPPORT_LIB_VERSION"
implementation "com.android.support:appcompat-v7:$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-design:2.0.0'
implementation 'org.jsoup:jsoup:1.11.3'
implementation 'com.facebook.fresco:fresco:1.5.0'
implementation 'com.facebook.stetho:stetho:1.5.0'

View file

@ -92,8 +92,9 @@
android:label="@string/navigation_item_notification" />
<activity
android:name=".featured.FeaturedImagesActivity"
android:label="@string/title_activity_featured_images" />
android:name=".category.CategoryImagesActivity"
android:label="@string/title_activity_featured_images"
android:parentActivityName=".contributions.ContributionsActivity" />
<service android:name=".upload.UploadService" />

View file

@ -135,9 +135,10 @@ public class AboutActivity extends NavigationBaseActivity {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.share_app_icon:
String shareText = "Upload photos to Wikimedia Commons on your phone\nDownload the Commons app: http://play.google.com/store/apps/details?id=fr.free.nrw.commons";
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, "http://play.google.com/store/apps/details?id=fr.free.nrw.commons");
sendIntent.putExtra(Intent.EXTRA_TEXT, shareText);
sendIntent.setType("text/plain");
startActivity(Intent.createChooser(sendIntent, "Share app via..."));
return true;

View file

@ -61,8 +61,8 @@ public class MediaDataExtractor {
}
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){
Timber.d(e.getMessage());

View file

@ -47,6 +47,7 @@ import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.ui.widget.HtmlTextView;
import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
@ -109,14 +110,14 @@ public class LoginActivity extends AccountAuthenticatorActivity {
usernameEdit.addTextChangedListener(textWatcher);
usernameEdit.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
hideKeyboard(v);
ViewUtil.hideKeyboard(v);
}
});
passwordEdit.addTextChangedListener(textWatcher);
passwordEdit.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
hideKeyboard(v);
ViewUtil.hideKeyboard(v);
}
});
@ -144,16 +145,6 @@ public class LoginActivity extends AccountAuthenticatorActivity {
Utils.handleWebUrl(this,Uri.parse("https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\\"));
}
public void hideKeyboard(View view) {
if (view != null) {
InputMethodManager inputMethodManager = (InputMethodManager) this.getSystemService(Activity.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);

View file

@ -1,7 +1,6 @@
package fr.free.nrw.commons.category;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
@ -16,7 +15,6 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
@ -43,6 +41,7 @@ import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.upload.MwVolleyApi;
import fr.free.nrw.commons.utils.StringSortingUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
@ -116,7 +115,7 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
categoriesFilter.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
hideKeyboard(v);
ViewUtil.hideKeyboard(v);
}
});
@ -128,16 +127,6 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
return rootView;
}
public void hideKeyboard(View view) {
if (view != null) {
InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
}
@Override
public void onDestroyView() {
categoriesFilter.removeTextChangedListener(textWatcher);

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.SignupActivity;
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.notification.NotificationActivity;
import fr.free.nrw.commons.settings.SettingsActivity;
@ -49,5 +49,5 @@ public abstract class ActivityBuilderModule {
abstract NotificationActivity bindNotificationActivity();
@ContributesAndroidInjector
abstract FeaturedImagesActivity bindFeaturedImagesActivity();
abstract CategoryImagesActivity bindFeaturedImagesActivity();
}

View file

@ -6,6 +6,8 @@ import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v4.util.LruCache;
import com.google.gson.Gson;
import javax.inject.Named;
import javax.inject.Singleton;
@ -85,6 +87,17 @@ public class CommonsApplicationModule {
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
@Named("direct_nearby_upload_prefs")
public SharedPreferences providesDirectNearbyUploadPreferences(Context context) {
@ -106,8 +119,11 @@ public class CommonsApplicationModule {
@Provides
@Singleton
public MediaWikiApi provideMediaWikiApi(Context context, @Named("default_preferences") SharedPreferences sharedPreferences) {
return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, sharedPreferences);
public MediaWikiApi provideMediaWikiApi(Context context,
@Named("default_preferences") SharedPreferences defaultPreferences,
@Named("category_prefs") SharedPreferences categoryPrefs,
Gson gson) {
return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, defaultPreferences, categoryPrefs, gson);
}
@Provides
@ -116,6 +132,16 @@ public class CommonsApplicationModule {
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
@Singleton
public CacheController provideCacheController() {

View file

@ -4,7 +4,7 @@ import dagger.Module;
import dagger.android.ContributesAndroidInjector;
import fr.free.nrw.commons.category.CategorizationFragment;
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.MediaDetailPagerFragment;
import fr.free.nrw.commons.nearby.NearbyListFragment;
@ -49,6 +49,6 @@ public abstract class FragmentBuilderModule {
abstract SingleUploadFragment bindSingleUploadFragment();
@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.support.annotation.Nullable;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.TypedValue;
import android.view.LayoutInflater;
@ -22,6 +23,9 @@ import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@ -45,6 +49,8 @@ import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.ui.widget.CompatTextView;
import timber.log.Timber;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_SHORT;
public class MediaDetailFragment extends CommonsDaggerSupportFragment {
@ -74,23 +80,37 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
@Inject
MediaWikiApi mwApi;
private MediaWikiImageView image;
private MediaDetailSpacer spacer;
private int initialListTop = 0;
private TextView title;
private TextView desc;
private TextView author;
private TextView license;
private TextView coordinates;
private TextView uploadedDate;
private TextView seeMore;
private LinearLayout nominatedforDeletion;
private LinearLayout categoryContainer;
private LinearLayout authorLayout;
private Button delete;
private ScrollView scrollView;
@BindView(R.id.mediaDetailImage)
MediaWikiImageView image;
@BindView(R.id.mediaDetailSpacer)
MediaDetailSpacer spacer;
@BindView(R.id.mediaDetailTitle)
TextView title;
@BindView(R.id.mediaDetailDesc)
TextView desc;
@BindView(R.id.mediaDetailAuthor)
TextView author;
@BindView(R.id.mediaDetailLicense)
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 boolean categoriesLoaded = false;
private boolean categoriesPresent = false;
@ -100,6 +120,9 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
private AsyncTask<Void, Void, Boolean> detailFetchTask;
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
public void onSaveInstanceState(Bundle 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);
image = (MediaWikiImageView) view.findViewById(R.id.mediaDetailImage);
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);
ButterKnife.bind(this,view);
if (isFeaturedMedia){
authorLayout.setVisibility(View.VISIBLE);
authorLayout.setVisibility(VISIBLE);
} else {
authorLayout.setVisibility(View.GONE);
authorLayout.setVisibility(GONE);
}
licenseList = new LicenseList(getActivity());
@ -195,7 +203,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
@Override
public void onResume() {
super.onResume();
Media media = detailProvider.getMediaAtPosition(index);
media = detailProvider.getMediaAtPosition(index);
if (media == null) {
// Ask the detail provider to ping us when we're ready
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!");
detailProvider.unregisterDataSetObserver(dataObserver);
dataObserver = null;
displayMediaDetails(detailProvider.getMediaAtPosition(index));
media=detailProvider.getMediaAtPosition(index);
displayMediaDetails();
}
};
detailProvider.registerDataSetObserver(dataObserver);
} else {
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
image.setMedia(media);
@ -255,7 +264,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
if (success) {
extractor.fill(media);
setTextFields(media);
setOnClickListeners(media);
} else {
Timber.d("Failed to load photo details.");
}
@ -306,24 +314,41 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
}
rebuildCatList();
if(media.getCreator() == null || media.getCreator().equals("")) {
authorLayout.setVisibility(GONE);
} else {
author.setText(media.getCreator());
}
checkDeletion(media);
}
private void setOnClickListeners(final Media media) {
if (licenseLink(media) != null) {
license.setOnClickListener(v -> openWebBrowser(licenseLink(media)));
@OnClick(R.id.mediaDetailLicense)
public void onMediaDetailLicenceClicked(){
if (!TextUtils.isEmpty(licenseLink(media))) {
openWebBrowser(licenseLink(media));
} else {
if(isFeaturedMedia) {
Timber.d("Unable to fetch license URL for %s", media.getLicense());
} else {
Toast toast = Toast.makeText(getContext(), getString(R.string.null_url), Toast.LENGTH_SHORT);
toast.show();
}
if (media.getCoordinates() != null) {
coordinates.setOnClickListener(v -> openMap(media.getCoordinates()));
}
if (delete.getVisibility() == View.VISIBLE) {
enableDeleteButton(true);
}
delete.setOnClickListener(v -> {
@OnClick(R.id.mediaDetailCoordinates)
public void onMediaDetailCoordinatesClicked(){
if (media.getCoordinates() != null) {
openMap(media.getCoordinates());
}
}
@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());
@ -367,12 +392,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
});
d.show();
d.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
});
}
if (nominatedforDeletion.getVisibility() == View.VISIBLE){
seeMore.setOnClickListener(v -> {
@OnClick(R.id.seeMore)
public void onSeeMoreClicked(){
if(nominatedForDeletion.getVisibility()== VISIBLE) {
openWebBrowser(media.getFilePageTitle().getMobileUri().toString());
});
}
}
@ -476,12 +501,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
private void checkDeletion(Media media){
if (media.getRequestedDeletion()){
delete.setVisibility(View.GONE);
nominatedforDeletion.setVisibility(View.VISIBLE);
delete.setVisibility(GONE);
nominatedForDeletion.setVisibility(VISIBLE);
}
else{
delete.setVisibility(View.VISIBLE);
nominatedforDeletion.setVisibility(View.GONE);
delete.setVisibility(VISIBLE);
nominatedForDeletion.setVisibility(GONE);
}
}

View file

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

View file

@ -9,6 +9,8 @@ import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
import com.google.gson.Gson;
import org.apache.http.HttpResponse;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
@ -38,7 +40,10 @@ import java.util.Locale;
import java.util.concurrent.Callable;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.Media;
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.NotificationUtils;
import in.yuvi.http.fluent.Http;
@ -46,6 +51,8 @@ import io.reactivex.Observable;
import io.reactivex.Single;
import timber.log.Timber;
import static fr.free.nrw.commons.utils.ContinueUtils.getQueryContinue;
/**
* @author Addshore
*/
@ -56,9 +63,15 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
private AbstractHttpClient httpClient;
private MWApi api;
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;
BasicHttpParams params = new BasicHttpParams();
SchemeRegistry schemeRegistry = new SchemeRegistry();
@ -69,7 +82,9 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
params.setParameter(CoreProtocolPNames.USER_AGENT, getUserAgent());
httpClient = new DefaultHttpClient(cm, params);
api = new MWApi(apiURL, httpClient);
this.sharedPreferences = sharedPreferences;
this.defaultPreferences = defaultPreferences;
this.categoryPreferences = categoryPreferences;
this.gson = gson;
}
@Override
@ -160,7 +175,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
}
private void setAuthCookieOnLogin(boolean isLoggedIn) {
SharedPreferences.Editor editor = sharedPreferences.edit();
SharedPreferences.Editor editor = defaultPreferences.edit();
if (isLoggedIn) {
editor.putBoolean("isUserLoggedIn", true);
editor.putString("getAuthCookie", api.getAuthCookie());
@ -448,6 +463,81 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
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
public boolean existingFile(String fileSha1) throws IOException {
return api.action("query")

View file

@ -7,6 +7,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.notification.Notification;
import io.reactivex.Observable;
import io.reactivex.Single;
@ -34,6 +35,8 @@ public interface MediaWikiApi {
boolean logEvents(LogBuilder[] logBuilders);
List<Media> getCategoryImages(String categoryName);
@NonNull
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.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.design.widget.BottomSheetBehavior;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AlertDialog;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@ -25,6 +29,7 @@ import com.google.gson.GsonBuilder;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.BindView;
import butterknife.ButterKnife;
@ -41,6 +46,8 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
import uk.co.deanwild.materialshowcaseview.IShowcaseListener;
import uk.co.deanwild.materialshowcaseview.MaterialShowcaseView;
public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener {
@ -56,12 +63,15 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
LinearLayout bottomSheetDetails;
@BindView(R.id.transparentView)
View transparentView;
@BindView(R.id.fab_recenter)
View fabRecenter;
@Inject
LocationServiceManager locationManager;
@Inject
NearbyController nearbyController;
@Inject
@Named("application_preferences") SharedPreferences applicationPrefs;
private LatLng curLatLng;
private Bundle bundle;
private Disposable placesDisposable;
@ -72,11 +82,18 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
private NearbyListFragment nearbyListFragment;
private static final String TAG_RETAINED_MAP_FRAGMENT = NearbyMapFragment.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 BroadcastReceiver broadcastReceiver;
private boolean isListShowcaseAdded = false;
private boolean isMapShowCaseAdded = false;
private LatLng lastKnownLocation;
private MaterialShowcaseView secondSingleShowCaseView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -126,6 +143,39 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
MenuInflater inflater = getMenuInflater();
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);
}
@ -420,6 +470,45 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
updateMapFragment(false);
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) {

View file

@ -7,6 +7,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
@ -58,13 +59,14 @@ import fr.free.nrw.commons.contributions.ContributionController;
import fr.free.nrw.commons.utils.UriDeserializer;
import fr.free.nrw.commons.utils.ViewUtil;
import timber.log.Timber;
import uk.co.deanwild.materialshowcaseview.MaterialShowcaseView;
import static android.app.Activity.RESULT_OK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
public class NearbyMapFragment extends DaggerFragment {
private MapView mapView;
public MapView mapView;
private List<NearbyBaseMarker> baseMarkerOptions;
private fr.free.nrw.commons.location.LatLng curLatLng;
public fr.free.nrw.commons.location.LatLng[] boundaryCoordinates;
@ -111,6 +113,10 @@ public class NearbyMapFragment extends DaggerFragment {
private final double CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT = 0.06;
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
@Inject
@ -163,7 +169,6 @@ public class NearbyMapFragment extends DaggerFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Timber.d("onCreateView called");
if (curLatLng != null) {
Timber.d("curLatLng found, setting up map view...");
@ -476,6 +481,7 @@ public class NearbyMapFragment extends DaggerFragment {
mapView.getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(MapboxMap mapboxMap) {
((NearbyActivity)getActivity()).setMapViewTutorialShowCase();
NearbyMapFragment.this.mapboxMap = mapboxMap;
updateMapSignificantly();
}
@ -519,6 +525,7 @@ public class NearbyMapFragment extends DaggerFragment {
private void addNearbyMarkerstoMapBoxMap() {
mapboxMap.addMarkers(baseMarkerOptions);
mapboxMap.setOnInfoWindowCloseListener(marker -> {
if (marker == selected) {
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
@ -534,6 +541,7 @@ public class NearbyMapFragment extends DaggerFragment {
});
mapboxMap.setOnMarkerClickListener(marker -> {
if (marker instanceof NearbyMarker) {
this.selected = marker;
NearbyMarker nearbyMarker = (NearbyMarker) marker;
@ -541,6 +549,7 @@ public class NearbyMapFragment extends DaggerFragment {
passInfoToSheet(place);
bottomSheetListBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
return false;
});
@ -634,7 +643,19 @@ public class NearbyMapFragment extends DaggerFragment {
addAnchorToSmallFABs(fabGallery, getActivity().findViewById(R.id.empty_view).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());
}
}
@ -791,6 +812,13 @@ public class NearbyMapFragment extends DaggerFragment {
this.bundleForUpdtes = bundleForUpdtes;
}
public void onNearbyMaterialShowcaseDismissed() {
isSecondMaterialShowcaseDismissed = true;
if (isMapReady) {
thirdSingleShowCaseView.show(getActivity());
}
}
@Override
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

@ -23,12 +23,11 @@ import fr.free.nrw.commons.AboutActivity;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.WelcomeActivity;
import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.auth.LoginActivity;
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.notification.NotificationActivity;
import fr.free.nrw.commons.settings.SettingsActivity;
@ -37,6 +36,8 @@ import timber.log.Timber;
public abstract class NavigationBaseActivity extends BaseActivity
implements NavigationView.OnNavigationItemSelectedListener {
private static final String FEATURED_IMAGES_CATEGORY = "Category:Featured_pictures_on_Wikimedia_Commons";
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.navigation_view)
@ -157,7 +158,7 @@ public abstract class NavigationBaseActivity extends BaseActivity
return true;
case R.id.action_featured_images:
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;
default:
Timber.e("Unknown option [%s] selected from the navigation menu", itemId);

View file

@ -33,6 +33,7 @@ import dagger.android.support.AndroidSupportInjection;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.utils.ViewUtil;
public class MultipleUploadListFragment extends Fragment {
@ -129,7 +130,7 @@ public class MultipleUploadListFragment extends Fragment {
// FIXME: Stops the keyboard from being shown 'stale' while moving out of this fragment into the next
View target = getActivity().getCurrentFocus();
hideKeyboard(target);
ViewUtil.hideKeyboard(target);
}
// FIXME: Wrong result type
@ -178,22 +179,13 @@ public class MultipleUploadListFragment extends Fragment {
baseTitle.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
hideKeyboard(v);
ViewUtil.hideKeyboard(v);
}
});
return view;
}
public void hideKeyboard(View view) {
if (view != null) {
InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
}
@Override
public void onDestroyView() {
baseTitle.removeTextChangedListener(textWatcher);

View file

@ -78,10 +78,10 @@ import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
import fr.free.nrw.commons.utils.ImageUtils;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.utils.ViewUtil;
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.NO_DUPLICATE;
import static java.lang.Long.min;
@ -121,6 +121,7 @@ public class ShareActivity
private Uri mediaUri;
private Contribution contribution;
private SimpleDraweeView backgroundImageView;
private FloatingActionButton maps_fragment;
private boolean cacheFound;
@ -144,6 +145,8 @@ public class ShareActivity
private long ShortAnimationDuration;
private FloatingActionButton zoomInButton;
private FloatingActionButton zoomOutButton;
private FloatingActionButton mainFab;
private boolean isFABOpen = false;
/**
@ -283,6 +286,24 @@ public class ShareActivity
if (mediaUri != null) {
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);
try {
zoomInButton.setOnClickListener(new View.OnClickListener() {
@ -357,8 +378,75 @@ public class ShareActivity
.commitAllowingStateLoss();
}
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
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
@ -460,6 +548,9 @@ public class ShareActivity
detectUnwantedPicturesAsync.execute();
}
/*
* to display permission snackbar in share activity
*/
private Snackbar requestPermissionUsingSnackBar(String rationale,
final String[] perms,
final int code) {
@ -692,7 +783,9 @@ public class ShareActivity
return super.onOptionsItemSelected(item);
}
// Get SHA1 of file from input stream
/*
* Get SHA1 of file from input stream
*/
private String getSHA1(InputStream is) {
MessageDigest digest;
@ -729,13 +822,18 @@ public class ShareActivity
}
}
/*
* function to provide pinch zoom
*/
private void zoomImageFromThumb(final View thumbView, Uri imageuri ) {
// If there's an animation in progress, cancel it
// immediately and proceed with this one.
if (CurrentAnimator != null) {
CurrentAnimator.cancel();
}
hideKeyboard(ShareActivity.this);
ViewUtil.hideKeyboard(ShareActivity.this.findViewById(R.id.titleEdit | R.id.descEdit));
closeFABMenu();
mainFab.setVisibility(View.GONE);
InputStream input = null;
Bitmap scaled = null;
try {
@ -865,7 +963,7 @@ public class ShareActivity
CurrentAnimator.cancel();
}
zoomOutButton.setVisibility(View.GONE);
zoomInButton.setVisibility(View.VISIBLE);
mainFab.setVisibility(View.VISIBLE);
// Animate the four positioning/sizing properties in parallel,
// back to their original values.
@ -905,12 +1003,5 @@ public class ShareActivity
});
}
public static void hideKeyboard(Activity activity) {
View view = activity.findViewById(R.id.titleEdit | R.id.descEdit);
if (view != null) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
}

View file

@ -1,8 +1,6 @@
package fr.free.nrw.commons.upload;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
@ -11,12 +9,10 @@ import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.view.ViewCompat;
import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.Html;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -24,7 +20,6 @@ import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
@ -47,9 +42,9 @@ import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.utils.ViewUtil;
import timber.log.Timber;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
public class SingleUploadFragment extends CommonsDaggerSupportFragment {
@ -168,13 +163,13 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
titleEdit.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
hideKeyboard(v);
ViewUtil.hideKeyboard(v);
}
});
descEdit.setOnFocusChangeListener((v, hasFocus) -> {
if(!hasFocus){
hideKeyboard(v);
ViewUtil.hideKeyboard(v);
}
});
@ -183,15 +178,6 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
return rootView;
}
public void hideKeyboard(View view) {
if (view != null) {
InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
}
@Override
public void onDestroyView() {
titleEdit.removeTextChangedListener(textWatcher);
@ -305,7 +291,7 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
// FIXME: Stops the keyboard from being shown 'stale' while moving out of this fragment into the next
View target = getActivity().getCurrentFocus();
hideKeyboard(target);
ViewUtil.hideKeyboard(target);
}
@NonNull

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

@ -5,10 +5,15 @@ import android.content.Context;
import android.support.design.widget.Snackbar;
import android.view.Display;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
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) {
Snackbar.make(view, messageResourceId, Snackbar.LENGTH_SHORT).show();
}
@ -27,4 +32,14 @@ public class ViewUtil {
}
}
public static void hideKeyboard(View view){
if (view != null) {
InputMethodManager manager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
view.clearFocus();
if (manager != null) {
manager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
}
}

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,5 +1,5 @@
<vector android:alpha="0.84" android:height="32dp"
<vector android:alpha="0.84" android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#ffffffff" android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
</vector>

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<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"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
@ -16,20 +15,12 @@
android:layout_height="wrap_content" />
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/featuredFragmentContainer"
android:id="@+id/fragmentContainer"
android:orientation="horizontal"
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>
</RelativeLayout>

View file

@ -41,8 +41,6 @@
</FrameLayout>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -50,8 +48,21 @@
android:layout_alignParentBottom="true"
android:layout_marginRight="@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:layout_above="@+id/main_fab"
android:id="@+id/media_upload_zoom_in"/>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -61,7 +72,19 @@
android:layout_marginRight="@dimen/standard_gap"
android:layout_marginBottom="@dimen/standard_gap"
android:src="@drawable/ic_zoom_out_white_24dp"
android:layout_above="@+id/main_fab"
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>
<android.support.design.widget.NavigationView

View file

@ -1,20 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/mainBackground"
>
android:background="?attr/mainBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/waiting_first_sync"
android:id="@+id/waitingMessage"
android:id="@+id/statusMessage"
android:layout_gravity="center"
android:visibility="gone"
tools:visibility="visible"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
/>
<ProgressBar
@ -22,11 +23,11 @@
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"
android:id="@+id/loadingFeaturedImagesProgressBar"
android:id="@+id/loadingImagesProgressBar"
/>
<GridView
android:id="@+id/featuredImagesList"
android:id="@+id/categoryImagesList"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:stretchMode="columnWidth"

View file

@ -9,7 +9,7 @@
>
<TextView
android:id="@+id/featuredImagesSequenceNumber"
android:id="@+id/categoryImagesSequenceNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="98sp"
@ -19,7 +19,7 @@
/>
<fr.free.nrw.commons.MediaWikiImageView
android:id="@+id/featuredImageView"
android:id="@+id/categoryImageView"
android:layout_width="match_parent"
android:layout_height="240dp"
/>
@ -33,7 +33,7 @@
android:padding="@dimen/small_gap"
>
<ProgressBar
android:id="@+id/featuredProgress"
android:id="@+id/categoryProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/ProgressBar"
@ -43,7 +43,7 @@
/>
<TextView
android:id="@+id/featuredImageTitle"
android:id="@+id/categoryImageTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFFFF"
@ -53,7 +53,7 @@
/>
<TextView
android:id="@+id/featuredImageAuthor"
android:id="@+id/categoryImageAuthor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFFFF"

View file

@ -7,7 +7,7 @@
<item
android:id="@+id/share_app_icon"
android:title="@string/refresh_button"
android:title="@string/share_app_title"
android:icon="@drawable/ic_share_black_24dp"
android:orderInCategory="1"
app:showAsAction="ifRoom"

View file

@ -154,6 +154,7 @@
<string name="welcome_copyright_subtext">Vermeide urheberrechtlich geschütztes Material, das du im Internet gefunden hast wie Bilder von Postern, Buchcovern etc.</string>
<string name="welcome_final_text">Verstanden?</string>
<string name="welcome_final_button_text">Ja!</string>
<string name="welcome_help_button_text"/>
<string name="detail_panel_cats_label">Kategorien</string>
<string name="detail_panel_cats_loading">Lade …</string>
<string name="detail_panel_cats_none">Keine ausgewählt</string>
@ -268,4 +269,5 @@
<string name="about_translate_proceed">Fortfahren</string>
<string name="about_translate_cancel">Abbrechen</string>
<string name="retry">Erneut versuchen</string>
<string name="share_app_title">App teilen</string>
</resources>

View file

@ -83,7 +83,7 @@
<string name="menu_feedback">Peyd rışten bırış (E-posta ra)</string>
<string name="no_email_client">E-posta eyar nêbi</string>
<string name="provider_categories">Karıyaye Kategoriyê peyêni</string>
<string name="menu_retry_upload">Fına</string>
<string name="menu_retry_upload">Anciya bıcerrebne</string>
<string name="menu_cancel_upload">Bıtexelne</string>
<string name="menu_download">Ron</string>
<string name="preference_license" fuzzy="true">Lisans</string>
@ -129,4 +129,5 @@
<string name="navigation_item_home">Keye</string>
<string name="navigation_item_upload">Bar ke</string>
<string name="navigation_item_logout">Veciyayış</string>
<string name="retry">Anciya bıcerrebne</string>
</resources>

View file

@ -158,6 +158,7 @@
<string name="welcome_copyright_subtext">Αποφύγετε προστατευμένο υλικό που βρήκατε από το Internet, καθώς και εικόνες, αφίσες, εξώφυλλα βιβλίων, κλπ.</string>
<string name="welcome_final_text">Τι λες, μπορείς;</string>
<string name="welcome_final_button_text">Ναι!</string>
<string name="welcome_help_button_text"/>
<string name="detail_panel_cats_label">Κατηγορίες</string>
<string name="detail_panel_cats_loading">Φόρτωση…</string>
<string name="detail_panel_cats_none">Καμία επιλεγμένη</string>
@ -272,4 +273,5 @@
<string name="about_translate_proceed">Συνέχεια</string>
<string name="about_translate_cancel">Ακύρωση</string>
<string name="retry">Ξαναπροσπαθήστε</string>
<string name="share_app_title">Κοινοποίηση εφαρμογής</string>
</resources>

View file

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

View file

@ -90,6 +90,7 @@
<string name="categories_activity_title">Categorías</string>
<string name="title_activity_settings">Configuracións</string>
<string name="title_activity_signup">Rexistrarse</string>
<string name="title_activity_featured_images">Imaxes destacadas</string>
<string name="menu_about">Acerca de</string>
<string name="about_license">A aplicación Wikimedia Commons é unha aplicación de código aberto creada e mantida polos cesionarios e voluntarios da comunidade de Wikimedia. A Fundación Wikimedia non está involucrada na creación, desenvolvemento ou mantemento da aplicación.</string>
<string name="about_improve">Crear unha nova &lt;a href=\"https://github.com/commons-app/apps-android-commons/issues\"&gt;incidencia&lt;/a&gt; para informar de problemas e suxestións.</string>
@ -175,6 +176,8 @@
<string name="media_detail_media_title">Título do ficheiro multimedia</string>
<string name="media_detail_description">Descrición</string>
<string name="media_detail_description_explanation">Aquí vai a descrición do ficheiro multimedia. Potencialmente, pode ser bastante longo, e necesitará agruparse en múltiples liñas. De tódolos xeitos esperamos que se vexa ben.</string>
<string name="media_detail_author">Autor</string>
<string name="media_detail_author_explanation">O nome de usuario do autor da imaxe destacada vai aquí.</string>
<string name="media_detail_uploaded_date">Data de suba</string>
<string name="media_detail_license">Licenza</string>
<string name="media_detail_coordinates">Coordenadas</string>
@ -217,6 +220,7 @@
<string name="navigation_item_logout">Saír</string>
<string name="navigation_item_info">Titorial</string>
<string name="navigation_item_notification">Notificacións</string>
<string name="navigation_item_featured_images">Destacados</string>
<string name="nearby_needs_permissions">Os sitios situados preto non poden visualizarse sen permisos de localización</string>
<string name="no_description_found">non se atopou descrición</string>
<string name="nearby_info_menu_commons_article">Páxina do ficheiro en Commons</string>

View file

@ -13,6 +13,7 @@
* ViDam
-->
<resources>
<string name="preference_category_appearance">Megjelenés</string>
<string name="preference_category_general">Általános</string>
<string name="preference_category_feedback">Visszajelzés</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_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_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_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>
@ -191,6 +193,7 @@
<string name="commons_logo">Commons Logo</string>
<string name="commons_website">Commons weboldal</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="no_image_found">Nem található kép</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="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="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="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>
@ -238,6 +243,7 @@
<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="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="nearby_wikidata">WIKIDATA</string>
<string name="nearby_wikipedia">WIKIPÉDIA</string>
@ -249,6 +255,8 @@
<string name="internet_established">Internet elérhető</string>
<string name="no_notifications">Nincs értesítés</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="retry">Újra</string>
<string name="share_app_title">Alkalmazás megosztása</string>
</resources>

View file

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

View file

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

View file

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

View file

@ -5,15 +5,23 @@
* Santhosh.thottingal
-->
<resources>
<string name="app_name" fuzzy="true">വിക്കിമീഡിയ കോമൺസ്</string>
<string name="preference_category_appearance">ദൃശ്യരൂപം</string>
<string name="preference_category_general">സാർവത്രികം</string>
<string name="preference_category_feedback">പ്രതികരണം</string>
<string name="preference_category_location">സ്ഥലം</string>
<string name="app_name">കോമൺസ്</string>
<string name="menu_settings">സജ്ജീകരണങ്ങൾ</string>
<string name="username">ഉപയോക്തൃനാമം</string>
<string name="password">രഹസ്യവാക്ക്</string>
<string name="login_credential">താങ്കളുടെ കോമൺസ് ബീറ്റ അംഗത്വത്തിൽ പ്രവേശിക്കുക</string>
<string name="login">പ്രവേശിക്കുക</string>
<string name="forgot_password">രഹസ്യവാക്ക് മറന്നോ?</string>
<string name="signup">അംഗത്വമെടുക്കുക</string>
<string name="logging_in_title">പ്രവേശിക്കുന്നു</string>
<string name="logging_in_message">ദയവായി കാത്തിരിക്കുക…</string>
<string name="login_success">പ്രവേശനം വിജയകരം!</string>
<string name="login_failed">പ്രവേശനം പരാജയപ്പെട്ടു!</string>
<string name="upload_failed">പ്രമാണം കണ്ടെത്താനായില്ല. ദയവായി മറ്റൊരു പ്രമാണം നോക്കുക.</string>
<string name="authentication_failed">സാധുതാനിർണ്ണയം പരാജയപ്പെട്ടു!</string>
<string name="uploading_started">അപ്‌ലോഡ് തുടങ്ങി!</string>
<string name="upload_completed_notification_title">%1$s അപ്‌ലോഡ് ചെയ്തിരിക്കുന്നു!</string>
@ -23,27 +31,30 @@
<string name="upload_progress_notification_title_finishing">%1$s അപ്‌ലോഡിങ് പൂർത്തിയാക്കുന്നു</string>
<string name="upload_failed_notification_title">%1$s അപ്‌ലോഡിങ് പരാജയപ്പെട്ടു</string>
<string name="upload_failed_notification_subtitle">കാണാനായി ടാപ് ചെയ്യുക</string>
<plurals name="uploads_pending_notification_indicator" fuzzy="true">
<item quantity="one">1 പ്രമാണം അപ്‌ലോഡ് ചെയ്യുന്നു</item>
<plurals name="uploads_pending_notification_indicator">
<item quantity="one">ഒരു പ്രമാണം അപ്‌ലോഡ് ചെയ്യുന്നു</item>
<item quantity="other">%1$d പ്രമാണങ്ങൾ അപ്‌ലോഡ് ചെയ്യുന്നു</item>
</plurals>
<string name="title_activity_contributions" fuzzy="true">എന്റെ അപ്‌ലോഡുകൾ</string>
<string name="title_activity_contributions">എന്റെ സമീപകാല അപ്‌ലോഡുകൾ</string>
<string name="contribution_state_queued">നിരയായി വെച്ചു</string>
<string name="contribution_state_failed">പരാജയപ്പെട്ടു</string>
<string name="contribution_state_in_progress">%1$d%% പൂർണ്ണം</string>
<string name="contribution_state_starting">അപ്‌ലോഡ് ചെയ്തുകൊണ്ടിരിക്കുന്നു</string>
<string name="menu_from_gallery">ചിത്രശാല</string>
<string name="menu_from_camera">ചിത്രം എടുക്കുക</string>
<string name="menu_nearby">സമീപസ്ഥം</string>
<string name="provider_contributions">എന്റെ അപ്‌ലോഡുകൾ</string>
<string name="menu_share">പങ്ക് വെയ്ക്കുക</string>
<string name="menu_open_in_browser">ബ്രൗസറിൽ കാണുക</string>
<string name="share_title_hint">തലക്കെട്ട്</string>
<string name="add_title_toast">ഈ പ്രമാണത്തിന് ഒരു തലക്കെട്ട് നൽകുക.</string>
<string name="share_description_hint">വിവരണം</string>
<string name="login_failed_network">പ്രവേശിക്കാനായില്ല - നെറ്റ്‌വർക്ക് പരാജയപ്പെട്ടു</string>
<string name="login_failed_username">പ്രവേശിക്കാനായില്ല - ദയവായി താങ്കളുടെ ഉപയോക്തൃനാമം പരിശോധിക്കുക</string>
<string name="login_failed_password">പ്രവേശിക്കാനായില്ല - ദയവായി താങ്കളുടെ രഹസ്യവാക്ക് പരിശോധിക്കുക</string>
<string name="login_failed_throttled" fuzzy="true">നിരവധി വിജയകരമല്ലാത്ത ശ്രമങ്ങൾ നടന്നിരിക്കുന്നു. വീണ്ടും ശ്രമിക്കുന്നതിനു മുമ്പ് ഏതാനം മിനിറ്റുകൾ വിശ്രമിക്കുക</string>
<string name="login_failed_throttled">നിരവധി വിജയകരമല്ലാത്ത ശ്രമങ്ങൾ നടന്നിരിക്കുന്നു. വീണ്ടും ശ്രമിക്കുന്നതിനു മുമ്പ് ഏതാനം മിനിറ്റുകൾ വിശ്രമിക്കുക.</string>
<string name="login_failed_blocked">ക്ഷമിക്കുക, ഈ ഉപയോക്താവ് കോമൺസിൽ നിന്ന് തടയപ്പെട്ടിരിക്കുകയാണ്</string>
<string name="login_failed_2fa_needed">താങ്കളുടെ ദ്വി-ഘടക സാധൂകരണ കോഡ് നൽകുക.</string>
<string name="login_failed_generic">പ്രവേശനം പരാജയപ്പെട്ടു</string>
<string name="share_upload_button">അപ്‌ലോഡ്</string>
<string name="multiple_share_base_title">ഈ ഗണത്തിന് പേരിടുക</string>
@ -51,6 +62,11 @@
<string name="menu_upload_single">അപ്‌ലോഡ്</string>
<string name="categories_search_text_hint">വർഗ്ഗങ്ങളിൽ തിരയുക</string>
<string name="menu_save_categories">സേവ് ചെയ്യുക</string>
<string name="refresh_button">പുതുക്കുക</string>
<string name="display_list_button">പട്ടിക</string>
<string name="gps_disabled">താങ്കളുടെ ഉപകരണത്തിൽ ജി.പി.എസ്. പ്രവർത്തനരഹിതമാണ്. അത് പ്രവർത്തനസജ്ജമാക്കണോ?</string>
<string name="enable_gps">ജി.പി.എസ്. സജ്ജമാക്കുക</string>
<string name="contributions_subtitle_zero">ഇതുവരെ അപ്‌ലോഡുകൾ ഒന്നുമില്ല</string>
<plurals name="contributions_subtitle" fuzzy="true">
<item quantity="zero">ഒരു അപ്‌ലോഡും ചെയ്തില്ല</item>
<item quantity="one">ഒരു അപ്‌ലോഡ്</item>
@ -68,6 +84,8 @@
<string name="categories_skip_explanation" fuzzy="true">താങ്കളുടെ ചിത്രങ്ങൾ വിക്കിമീഡിയ കോമൺസിൽ കൂടുതൽ എളുപ്പത്തിൽ കണ്ടെത്തപ്പെടാനായി വർഗ്ഗങ്ങൾ ചേർക്കുക.\n\nവർഗ്ഗങ്ങൾ ചേർക്കാനായി ടൈപ്പ് ചെയ്ത് തുടങ്ങുക.\nഈ ഘട്ടം ഒഴിവാക്കാൻ ടാപ് ചെയ്യുക (അല്ലെങ്കിൽ പിന്നോട്ട് പോവുക).</string>
<string name="categories_activity_title">വർഗ്ഗങ്ങൾ</string>
<string name="title_activity_settings">സജ്ജീകരണങ്ങൾ</string>
<string name="title_activity_signup">അംഗത്വമെടുക്കുക</string>
<string name="title_activity_featured_images">തിരഞ്ഞെടുത്ത ചിത്രങ്ങൾ</string>
<string name="menu_about">വിവരണം</string>
<string name="about_license" fuzzy="true">&lt;a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\"&gt;അപാച്ചേ അനുമതിപത്രം പതിപ്പ് 2&lt;/a&gt; പ്രകാരം പുറത്തിറക്കപ്പെട്ട ഓപ്പൺ സോഴ്സ് സോഫ്റ്റ്‌വേർ</string>
<string name="about_improve" fuzzy="true">സ്രോതസ്സ് രൂപം &lt;a href=\"https://github.com/commons-app/apps-android-commons\"&gt;ജിറ്റ്ഹബിൽ&lt;/a&gt; ലഭ്യമാണ്.\nപ്രശ്നങ്ങൾ &lt;a href=\" https://github.com/commons-app/apps-android-commons/issues\"&gt;ബഗ്സില്ലയിൽ&lt;/a&gt; അറിയിക്കുക.</string>
@ -81,9 +99,11 @@
<string name="menu_cancel_upload">റദ്ദാക്കുക</string>
<string name="share_license_summary">ചിത്രം %1$s പ്രകാരം അനുമതി നൽകപ്പെടുന്നതാണ്</string>
<string name="menu_download">ഡൗൺലോഡ്</string>
<string name="preference_license" fuzzy="true">അനുമതി</string>
<string name="license_name_cc_by_sa" fuzzy="true">സി.സി. ആട്രിബ്യൂഷൻ-ഷെയർ‌എലൈക് 3.0</string>
<string name="license_name_cc_by" fuzzy="true">സി.സി. ആട്രിബ്യൂഷൻ 3.0</string>
<string name="preference_license">സ്വതേയുള്ള ഉപയോഗാനുമതി</string>
<string name="license_name_cc_by_sa_four">ആട്രിബ്യൂഷൻ-ഷെയർ‌എലൈക് 4.0</string>
<string name="license_name_cc_by_four">ആട്രിബ്യൂഷൻ 4.0</string>
<string name="license_name_cc_by_sa">ആട്രിബ്യൂഷൻ-ഷെയർ‌എലൈക് 3.0</string>
<string name="license_name_cc_by">ആട്രിബ്യൂഷൻ 3.0</string>
<string name="license_name_cc0">സി.സി.0</string>
<string name="license_name_cc_by_sa_3_0">സി.സി. ബൈ-എസ്.എ. 3.0</string>
<string name="license_name_cc_by_sa_3_0_at">സി.സി. ബൈ-എസ്.എ. 3.0 (ഓസ്ട്രിയ)</string>
@ -97,6 +117,8 @@
<string name="license_name_cc_by_sa_3_0_pl">സി.സി. ബൈ-എസ്.എ. 3.0 (പോളണ്ട്)</string>
<string name="license_name_cc_by_sa_3_0_ro">സി.സി. ബൈ-എസ്.എ. 3.0 (റൊമേനിയ)</string>
<string name="license_name_cc_by_3_0">സി.സി. ബൈ 3.0</string>
<string name="license_name_cc_by_sa_4_0">സി.സി. ബൈ-എസ്.എ. 4.0</string>
<string name="license_name_cc_by_4_0">സി.സി. ബൈ 4.0</string>
<string name="license_name_cc_zero">സി.സി. സീറോ</string>
<string name="welcome_wikipedia_text">താങ്കളെടുക്കുന്ന ചിത്രങ്ങൾ സംഭാവന ചെയ്യുക. വിക്കിപീഡിയ ലേഖനങ്ങൾ ജീവസ്സുറ്റതാക്കിത്തീർക്കുക!</string>
<string name="welcome_wikipedia_subtext">വിക്കിപീഡിയയിലുള്ള ചിത്രങ്ങൾ വിക്കിമീഡിയ കോമൺസിൽ നിന്നാണ്</string>
@ -105,9 +127,63 @@
<string name="welcome_final_text">മനസ്സിലായോ?</string>
<string name="welcome_final_button_text">ശരി!</string>
<string name="detail_panel_cats_label">വർഗ്ഗങ്ങൾ</string>
<string name="detail_panel_cats_loading" fuzzy="true">ശേഖരിക്കുന്നു…</string>
<string name="detail_panel_cats_loading">ശേഖരിക്കുന്നു…</string>
<string name="detail_panel_cats_none">ഒന്നും തിരഞ്ഞെടുത്തിട്ടില്ല</string>
<string name="detail_description_empty">വിവരണമൊന്നുമില്ല</string>
<string name="detail_license_empty">അജ്ഞാതമായ അനുമതി</string>
<string name="menu_refresh">പുതുക്കുക</string>
<string name="ok">ശരി</string>
<string name="title_activity_nearby">സമീപ സ്ഥലങ്ങൾ</string>
<string name="warning">മുന്നറിയിപ്പ്</string>
<string name="file_exists">ഈ പ്രമാണം കോമൺസിൽ നിലവിലുണ്ട്. തുടരണം എന്ന് താങ്കൾക്കുറപ്പാണോ?</string>
<string name="yes">അതെ</string>
<string name="no">അല്ല</string>
<string name="media_detail_title">ശീർഷകം</string>
<string name="media_detail_media_title">മീഡിയയുടെ തലക്കെട്ട്</string>
<string name="media_detail_description">വിവരണം</string>
<string name="media_detail_author">സ്രഷ്ടാവ്</string>
<string name="media_detail_uploaded_date">അപ്‌ലോഡ് ചെയ്ത തീയതി</string>
<string name="media_detail_license">ഉപയോഗാനുമതി</string>
<string name="media_detail_coordinates">നിർദ്ദേശാങ്കങ്ങൾ</string>
<string name="media_detail_coordinates_empty">ഒന്നും നൽകിയിട്ടില്ല</string>
<string name="commons_logo">കോമൺസ് ലോഗോ</string>
<string name="commons_website">കോമൺസ് വെബ്‌സൈറ്റ്</string>
<string name="background_image">പശ്ചാത്തല ചിത്രം</string>
<string name="upload_image">ചിത്രം അപ്‌ലോഡ് ചെയ്യുക</string>
<string name="welcome_image_mount_zao">സാവോ പർവ്വതം</string>
<string name="welcome_image_llamas">ലാമകൾ</string>
<string name="welcome_image_rainbow_bridge">മഴവിൽ പാലം</string>
<string name="welcome_image_tulip">തുലിപ്</string>
<string name="welcome_image_no_selfies">സെൽഫികൾ വേണ്ട</string>
<string name="welcome_image_proprietary">പകർപ്പവകാശ സംരക്ഷിത ചിത്രം</string>
<string name="welcome_image_sydney_opera_house">സിഡ്നി ഓപെറാ ഹൗസ്</string>
<string name="cancel">റദ്ദാക്കുക</string>
<string name="navigation_drawer_open">തുറക്കുക</string>
<string name="navigation_drawer_close">അടയ്ക്കുക</string>
<string name="navigation_item_home">പ്രധാനം</string>
<string name="navigation_item_upload">അപ്‌ലോഡ്</string>
<string name="navigation_item_nearby">സമീപസ്ഥം</string>
<string name="navigation_item_about">വിവരണം</string>
<string name="navigation_item_settings">സജ്ജീകരണങ്ങൾ</string>
<string name="navigation_item_feedback">പ്രതികരണം</string>
<string name="navigation_item_logout">ലോഗൗട്ട്</string>
<string name="navigation_item_info">സഹായം</string>
<string name="navigation_item_notification">അറിയിപ്പുകൾ</string>
<string name="navigation_item_featured_images">തിരഞ്ഞെടുക്കപ്പെട്ടത്</string>
<string name="no_description_found">വിവരണങ്ങൾ ഒന്നും കണ്ടെത്തിയില്ല</string>
<string name="nearby_info_menu_commons_article">കോമൺസ് പ്രമാണ താൾ</string>
<string name="nearby_info_menu_wikidata_article">വിക്കിഡേറ്റാ ഇനം</string>
<string name="nearby_info_menu_wikipedia_article">വിക്കിപീഡിയ ലേഖനം</string>
<string name="give_permission">അനുമതി നൽകുക</string>
<string name="login_to_your_account">താങ്കളുടെ അംഗത്വത്തിൽ പ്രവേശിക്കുക</string>
<string name="view_browser">ബ്രൗസറിൽ കാണുക</string>
<string name="nearby_wikidata">വിക്കിഡേറ്റാ</string>
<string name="nearby_wikipedia">വിക്കിപീഡിയ</string>
<string name="nearby_commons">കോമൺസ്</string>
<string name="about_faq">&lt;u&gt;പതിവുചോദ്യങ്ങൾ&lt;/u&gt;</string>
<string name="about_translate">&lt;u&gt;പരിഭാഷപ്പെടുത്തുക&lt;/u&gt;</string>
<string name="about_translate_title">ഭാഷകൾ</string>
<string name="about_translate_proceed">തുടരുക</string>
<string name="about_translate_cancel">റദ്ദാക്കുക</string>
<string name="retry">വീണ്ടും ശ്രമിക്കുക</string>
</resources>

View file

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

View file

@ -275,4 +275,5 @@
<string name="about_translate_proceed">Avançar</string>
<string name="about_translate_cancel">Cancelar</string>
<string name="retry">Tentar novamente</string>
<string name="share_app_title">Compartilhar o aplicativo</string>
</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_final_text">Acha que conseguiu?</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_loading">A carregar…</string>
<string name="detail_panel_cats_none">Nenhuma selecionada</string>

View file

@ -170,6 +170,7 @@
<string name="welcome_copyright_subtext">Избегайте материалов, защищённых авторским правом, например, найденных в Интернете, изображений плакатов, книжных обложек и т.п.</string>
<string name="welcome_final_text">Вам это понятно?</string>
<string name="welcome_final_button_text">Да!</string>
<string name="welcome_help_button_text">*</string>
<string name="detail_panel_cats_label">Категории</string>
<string name="detail_panel_cats_loading">Загрузка…</string>
<string name="detail_panel_cats_none">Ничего не выбрано</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_final_text">Tror du att du förstår?</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_loading">Läser in…</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_cancel">Avbryt</string>
<string name="retry">Försök igen</string>
<string name="share_app_title">Dela app</string>
</resources>

View file

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

View file

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

View file

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

View file

@ -270,4 +270,17 @@
<string name="about_translate_proceed">Proceed</string>
<string name="about_translate_cancel">Cancel</string>
<string name="retry">Retry</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_coordinates_not_present">Coordinates were not specified during image selection</string>
</resources>

View file

@ -3,6 +3,7 @@ package fr.free.nrw.commons
import android.content.Context
import android.content.SharedPreferences
import android.support.v4.util.LruCache
import com.google.gson.Gson
import com.nhaarman.mockito_kotlin.mock
import com.squareup.leakcanary.RefWatcher
import fr.free.nrw.commons.auth.AccountUtil
@ -36,6 +37,7 @@ class MockCommonsApplicationModule(appContext: Context) : CommonsApplicationModu
val accountUtil: AccountUtil = mock()
val appSharedPreferences: SharedPreferences = mock()
val defaultSharedPreferences: SharedPreferences = mock()
val categorySharedPreferences: SharedPreferences = mock()
val otherSharedPreferences: SharedPreferences = mock()
val uploadController: UploadController = mock()
val mockSessionManager: SessionManager = mock()
@ -45,6 +47,7 @@ class MockCommonsApplicationModule(appContext: Context) : CommonsApplicationModu
val mockDbOpenHelper: DBOpenHelper = mock()
val nearbyPlaces: NearbyPlaces = mock()
val lruCache: LruCache<String, String> = mock()
val gson: Gson = Gson()
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 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

View file

@ -3,6 +3,7 @@ package fr.free.nrw.commons.mwapi
import android.content.SharedPreferences
import android.os.Build
import android.preference.PreferenceManager
import com.google.gson.Gson
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.TestCommonsApplication
import okhttp3.mockwebserver.MockResponse
@ -26,12 +27,14 @@ class ApacheHttpClientMediaWikiApiTest {
private lateinit var testObject: ApacheHttpClientMediaWikiApi
private lateinit var server: MockWebServer
private lateinit var sharedPreferences: SharedPreferences
private lateinit var categoryPreferences: SharedPreferences
@Before
fun setUp() {
server = MockWebServer()
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 + "/")
}