Merge remote-tracking branch 'upstream/master' into refactorNearbyClassesMVP

This commit is contained in:
neslihanturan 2019-07-10 12:45:23 +03:00
commit 52e9ef9245
45 changed files with 1128 additions and 679 deletions

View file

@ -135,31 +135,4 @@ public class BookmarksActivity extends NavigationBaseActivity
}
return adapter.getMediaAdapter().getCount();
}
/**
* This method is never called but it was in MediaDetailProvider Interface
* so it needs to be overrided.
*/
@Override
public void notifyDatasetChanged() {
}
/**
* This method is never called but it was in MediaDetailProvider Interface
* so it needs to be overrided.
*/
@Override
public void registerDataSetObserver(DataSetObserver observer) {
}
/**
* This method is never called but it was in MediaDetailProvider Interface
* so it needs to be overrided.
*/
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
}
}

View file

@ -173,32 +173,6 @@ public class CategoryDetailsActivity extends NavigationBaseActivity
return categoryImagesListFragment.getAdapter().getCount();
}
/**
* This method is never called but it was in MediaDetailProvider Interface
* so it needs to be overrided.
*/
@Override
public void notifyDatasetChanged() {
}
/**
* This method is never called but it was in MediaDetailProvider Interface
* so it needs to be overrided.
*/
@Override
public void registerDataSetObserver(DataSetObserver observer) {
}
/**
* This method is never called but it was in MediaDetailProvider Interface
* so it needs to be overrided.
*/
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
}
/**
* This method inflates the menu in the toolbar
*/

View file

@ -180,33 +180,6 @@ public class CategoryImagesActivity
return categoryImagesListFragment.getAdapter().getCount();
}
/**
* This method is never called but it was in MediaDetailProvider Interface
* so it needs to be overrided.
*/
@Override
public void notifyDatasetChanged() {
}
/**
* This method is never called but it was in MediaDetailProvider Interface
* so it needs to be overrided.
*/
@Override
public void registerDataSetObserver(DataSetObserver observer) {
}
/**
* This method is never called but it was in MediaDetailProvider Interface
* so it needs to be overrided.
*/
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
}
/**
* This method inflates the menu in the toolbar
*/

View file

@ -1,35 +1,34 @@
package fr.free.nrw.commons.contributions;
import android.content.Context;
import android.net.Uri;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.collection.LruCache;
import com.facebook.drawee.view.SimpleDraweeView;
import org.apache.commons.lang3.StringUtils;
import javax.inject.Inject;
import javax.inject.Named;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import com.facebook.drawee.view.SimpleDraweeView;
import fr.free.nrw.commons.MediaDataExtractor;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.ViewHolder;
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
import fr.free.nrw.commons.contributions.model.DisplayableContribution;
import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.upload.FileUtils;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.commons.lang3.StringUtils;
import timber.log.Timber;
public class ContributionViewHolder implements ViewHolder<DisplayableContribution> {
public class ContributionViewHolder extends RecyclerView.ViewHolder {
private final Callback callback;
@BindView(R.id.contributionImage)
SimpleDraweeView imageView;
@BindView(R.id.contributionTitle) TextView titleView;
@ -47,15 +46,18 @@ public class ContributionViewHolder implements ViewHolder<DisplayableContributio
private DisplayableContribution contribution;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private int position;
ContributionViewHolder(View parent) {
ContributionViewHolder(View parent, Callback callback) {
super(parent);
ButterKnife.bind(this, parent);
this.callback=callback;
}
@Override
public void bindModel(Context context, DisplayableContribution contribution) {
ApplicationlessInjection.getInstance(context)
public void init(int position, DisplayableContribution contribution) {
ApplicationlessInjection.getInstance(itemView.getContext())
.getCommonsApplicationComponent().inject(this);
this.position=position;
this.contribution = contribution;
fetchAndDisplayThumbnail(contribution);
titleView.setText(contribution.getDisplayTitle());
@ -104,19 +106,39 @@ public class ContributionViewHolder implements ViewHolder<DisplayableContributio
* @param contribution
*/
private void fetchAndDisplayThumbnail(DisplayableContribution contribution) {
if (!StringUtils.isBlank(thumbnailCache.get(contribution.getFilename()))) {
imageView.setImageURI(thumbnailCache.get(contribution.getFilename()));
String keyForLRUCache = getKeyForLRUCache(contribution.getContentUri());
String cacheUrl = thumbnailCache.get(keyForLRUCache);
if (!StringUtils.isBlank(cacheUrl)) {
imageView.setImageURI(cacheUrl);
return;
}
Timber.d("Fetching thumbnail for %s", contribution.getFilename());
Disposable disposable = mediaDataExtractor.getMediaFromFileName(contribution.getFilename())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(media -> {
thumbnailCache.put(contribution.getFilename(), media.getThumbUrl());
imageView.setImageURI(media.getThumbUrl());
});
compositeDisposable.add(disposable);
imageView.setBackground(null);
if ((contribution.getState() != Contribution.STATE_COMPLETED) && FileUtils.fileExists(
contribution.getLocalUri())) {
imageView.setImageURI(contribution.getLocalUri());
} else {
Timber.d("Fetching thumbnail for %s", contribution.getFilename());
Disposable disposable = mediaDataExtractor
.getMediaFromFileName(contribution.getFilename())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(media -> {
thumbnailCache.put(keyForLRUCache, media.getThumbUrl());
imageView.setImageURI(media.getThumbUrl());
});
compositeDisposable.add(disposable);
}
}
/**
* Returns image key for the LRU cache, basically the id of the image, (the content uri is the ""+/id)
* @param contentUri
* @return
*/
private String getKeyForLRUCache(Uri contentUri) {
return contentUri.getLastPathSegment();
}
public void clear() {
@ -128,10 +150,7 @@ public class ContributionViewHolder implements ViewHolder<DisplayableContributio
*/
@OnClick(R.id.retryButton)
public void retryUpload() {
DisplayableContribution.ContributionActions actions = contribution.getContributionActions();
if (actions != null) {
actions.retryUpload();
}
callback.retryUpload(contribution);
}
/**
@ -139,17 +158,11 @@ public class ContributionViewHolder implements ViewHolder<DisplayableContributio
*/
@OnClick(R.id.cancelButton)
public void deleteUpload() {
DisplayableContribution.ContributionActions actions = contribution.getContributionActions();
if (actions != null) {
actions.deleteUpload();
}
callback.deleteUpload(contribution);
}
@OnClick(R.id.contributionImage)
public void imageClicked(){
DisplayableContribution.ContributionActions actions = contribution.getContributionActions();
if (actions != null) {
actions.onClick();
}
callback.openMediaDetail(position);
}
}

View file

@ -0,0 +1,35 @@
package fr.free.nrw.commons.contributions;
import android.database.Cursor;
import androidx.loader.app.LoaderManager;
import fr.free.nrw.commons.BasePresenter;
import fr.free.nrw.commons.Media;
/**
* The contract for Contributions View & Presenter
*/
public class ContributionsContract {
public interface View {
void showWelcomeTip(boolean numberOfUploads);
void showProgress(boolean shouldShow);
void showNoContributionsUI(boolean shouldShow);
void setUploadCount(int count);
void onDataSetChanged();
}
public interface UserActionListener extends BasePresenter<ContributionsContract.View>,
LoaderManager.LoaderCallbacks<Cursor> {
Contribution getContributionsFromCursor(Cursor cursor);
void deleteUpload(Contribution contribution);
Media getItemAtPosition(int i);
}
}

View file

@ -1,36 +1,28 @@
package fr.free.nrw.commons.contributions;
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Bundle;
import android.os.IBinder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.CheckBox;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.cursoradapter.widget.CursorAdapter;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
import androidx.fragment.app.FragmentTransaction;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import java.util.ArrayList;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.HandlerService;
@ -40,12 +32,15 @@ import fr.free.nrw.commons.campaigns.Campaign;
import fr.free.nrw.commons.campaigns.CampaignView;
import fr.free.nrw.commons.campaigns.CampaignsPresenter;
import fr.free.nrw.commons.campaigns.ICampaignsView;
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
import fr.free.nrw.commons.contributions.ContributionsListFragment.SourceRefresher;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.location.LocationUpdateListener;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.media.MediaDetailPagerFragment.MediaDetailProvider;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
import fr.free.nrw.commons.nearby.NearbyController;
@ -55,29 +50,26 @@ import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.UploadService;
import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.utils.DialogUtil;
import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
import static fr.free.nrw.commons.contributions.ContributionDao.Table.ALL_FIELDS;
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI;
import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
import static fr.free.nrw.commons.settings.Prefs.UPLOADS_SHOWING;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
public class ContributionsFragment
extends CommonsDaggerSupportFragment
implements LoaderManager.LoaderCallbacks<Cursor>,
MediaDetailPagerFragment.MediaDetailProvider,
FragmentManager.OnBackStackChangedListener,
ContributionsListFragment.SourceRefresher,
LocationUpdateListener,
ICampaignsView,
ContributionsListAdapter.EventListener{
implements
MediaDetailProvider,
OnBackStackChangedListener,
SourceRefresher,
LocationUpdateListener,
ICampaignsView, ContributionsContract.View {
@Inject @Named("default_preferences") JsonKvStore store;
@Inject ContributionDao contributionDao;
@Inject MediaWikiApi mediaWikiApi;
@ -99,6 +91,8 @@ public class ContributionsFragment
@BindView(R.id.card_view_nearby) public NearbyNotificationCardView nearbyNotificationCardView;
@BindView(R.id.campaigns_view) CampaignView campaignView;
@Inject ContributionsPresenter contributionsPresenter;
private LatLng curLatLng;
private boolean firstLocationUpdate = true;
@ -116,9 +110,6 @@ public class ContributionsFragment
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder) binder)
.getService();
isUploadServiceConnected = true;
if (contributionsListFragment.getAdapter() != null) {
((ContributionsListAdapter)contributionsListFragment.getAdapter()).setUploadService(uploadService);
}
}
@Override
@ -127,6 +118,8 @@ public class ContributionsFragment
Timber.e(new RuntimeException("UploadService died but the rest of the process did not!"));
}
};
private boolean shouldShowMediaDetailsFragment;
private int numberOfContributions;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
@ -140,6 +133,7 @@ public class ContributionsFragment
View view = inflater.inflate(R.layout.fragment_contributions, container, false);
ButterKnife.bind(this, view);
presenter.onAttachView(this);
contributionsPresenter.onAttachView(this);
campaignView.setVisibility(View.GONE);
checkBoxView = View.inflate(getActivity(), R.layout.nearby_permission_dialog, null);
checkBox = (CheckBox) checkBoxView.findViewById(R.id.never_ask_again);
@ -151,16 +145,19 @@ public class ContributionsFragment
});
if (savedInstanceState != null) {
mediaDetailPagerFragment = (MediaDetailPagerFragment)getChildFragmentManager().findFragmentByTag(MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
contributionsListFragment = (ContributionsListFragment) getChildFragmentManager().findFragmentByTag(CONTRIBUTION_LIST_FRAGMENT_TAG);
mediaDetailPagerFragment = (MediaDetailPagerFragment) getChildFragmentManager()
.findFragmentByTag(MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
contributionsListFragment = (ContributionsListFragment) getChildFragmentManager()
.findFragmentByTag(CONTRIBUTION_LIST_FRAGMENT_TAG);
shouldShowMediaDetailsFragment = savedInstanceState.getBoolean("mediaDetailsVisible");
}
if (savedInstanceState.getBoolean("mediaDetailsVisible")) {
setMediaDetailPagerFragment();
} else {
setContributionsListFragment();
}
} else {
setContributionsListFragment();
initFragments();
if(shouldShowMediaDetailsFragment){
showMediaDetailPagerFragment();
}else{
showContributionsListFragment();
}
if (!ConfigUtils.isBetaFlavour()) {
@ -191,6 +188,65 @@ public class ContributionsFragment
return view;
}
/**
* Initialose the ContributionsListFragment and MediaDetailPagerFragment fragment
*/
private void initFragments() {
if (null == contributionsListFragment) {
contributionsListFragment = new ContributionsListFragment();
}
contributionsListFragment.setCallback(new Callback() {
@Override
public void retryUpload(Contribution contribution) {
ContributionsFragment.this.retryUpload(contribution);
}
@Override
public void deleteUpload(Contribution contribution) {
contributionsPresenter.deleteUpload(contribution);
}
@Override
public void openMediaDetail(int position) {
showDetail(position);
}
@Override
public int getNumberOfContributions() {
return numberOfContributions;
}
@Override
public Contribution getContributionForPosition(int position) {
return (Contribution) contributionsPresenter.getItemAtPosition(position);
}
@Override
public int findItemPositionWithId(String id) {
return contributionsPresenter.getChildPositionWithId(id);
}
});
if(null==mediaDetailPagerFragment){
mediaDetailPagerFragment=new MediaDetailPagerFragment();
}
}
/**
* Replaces the root frame layout with the given fragment
* @param fragment
* @param tag
*/
private void showFragment(Fragment fragment, String tag) {
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.replace(R.id.root_frame, fragment, tag);
transaction.addToBackStack(CONTRIBUTION_LIST_FRAGMENT_TAG);
transaction.commit();
getChildFragmentManager().executePendingTransactions();
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
@ -209,132 +265,45 @@ public class ContributionsFragment
}
/**
* Replace FrameLayout with ContributionsListFragment, user will see contributions list.
* Creates new one if null.
* Replace FrameLayout with ContributionsListFragment, user will see contributions list. Creates
* new one if null.
*/
public void setContributionsListFragment() {
public void showContributionsListFragment() {
// show tabs on contribution list is visible
((MainActivity)getActivity()).showTabs();
((MainActivity) getActivity()).showTabs();
// show nearby card view on contributions list is visible
if (nearbyNotificationCardView != null) {
if (store.getBoolean("displayNearbyCardView", true)) {
if (nearbyNotificationCardView.cardViewVisibilityState == NearbyNotificationCardView.CardViewVisibilityState.READY) {
if (nearbyNotificationCardView.cardViewVisibilityState
== NearbyNotificationCardView.CardViewVisibilityState.READY) {
nearbyNotificationCardView.setVisibility(View.VISIBLE);
}
} else {
nearbyNotificationCardView.setVisibility(View.GONE);
}
}
// Create if null
if (getContributionsListFragment() == null) {
contributionsListFragment = new ContributionsListFragment();
}
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
// When this container fragment is created, we fill it with our ContributionsListFragment
transaction.replace(R.id.root_frame, contributionsListFragment, CONTRIBUTION_LIST_FRAGMENT_TAG);
transaction.addToBackStack(CONTRIBUTION_LIST_FRAGMENT_TAG);
transaction.commit();
getChildFragmentManager().executePendingTransactions();
showFragment(contributionsListFragment, CONTRIBUTION_LIST_FRAGMENT_TAG);
}
/**
* Replace FrameLayout with MediaDetailPagerFragment, user will see details of selected media.
* Creates new one if null.
*/
public void setMediaDetailPagerFragment() {
public void showMediaDetailPagerFragment() {
// hide tabs on media detail view is visible
((MainActivity)getActivity()).hideTabs();
// hide nearby card view on media detail is visible
nearbyNotificationCardView.setVisibility(View.GONE);
// Create if null
if (getMediaDetailPagerFragment() == null) {
mediaDetailPagerFragment = new MediaDetailPagerFragment();
}
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
// When this container fragment is created, we fill it with our MediaDetailPagerFragment
//transaction.addToBackStack(null);
transaction.add(R.id.root_frame, mediaDetailPagerFragment, MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
transaction.addToBackStack(MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
transaction.commit();
getChildFragmentManager().executePendingTransactions();
showFragment(mediaDetailPagerFragment,MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
}
/**
* Just getter method of ContributionsListFragment child of ContributionsFragment
* @return contributionsListFragment, if any created
*/
public ContributionsListFragment getContributionsListFragment() {
return contributionsListFragment;
}
/**
* Just getter method of MediaDetailPagerFragment child of ContributionsFragment
* @return mediaDetailsFragment, if any created
*/
public MediaDetailPagerFragment getMediaDetailPagerFragment() {
return mediaDetailPagerFragment;
}
@Override
public void onBackStackChanged() {
((MainActivity)getActivity()).initBackButton();
}
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
int uploads = store.getInt(UPLOADS_SHOWING, 100);
return new CursorLoader(getActivity(), BASE_URI, //TODO find out the reason we pass activity here
ALL_FIELDS, "", null,
ContributionDao.CONTRIBUTION_SORT + "LIMIT " + uploads);
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
if (contributionsListFragment != null) {
contributionsListFragment.changeProgressBarVisibility(false);
if (contributionsListFragment.getAdapter() == null) {
contributionsListFragment.setAdapter(new ContributionsListAdapter(getActivity().getApplicationContext(),
cursor, 0, contributionDao, this));
} else {
((CursorAdapter) contributionsListFragment.getAdapter()).swapCursor(cursor);
}
contributionsListFragment.showWelcomeTip(cursor.getCount() == 0);
notifyAndMigrateDataSetObservers();
((ContributionsListAdapter)contributionsListFragment.getAdapter()).setUploadService(uploadService);
}
}
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
((CursorAdapter) contributionsListFragment.getAdapter()).swapCursor(null);
}
private void notifyAndMigrateDataSetObservers() {
Adapter adapter = contributionsListFragment.getAdapter();
// First, move the observers over to the adapter now that we have it.
for (DataSetObserver observer : observersWaitingForLoad) {
adapter.registerDataSetObserver(observer);
}
observersWaitingForLoad.clear();
// Now fire off a first notification...
for (DataSetObserver observer : observersWaitingForLoad) {
observer.onChanged();
}
if (ConfigUtils.isBetaFlavour()) {
betaSetUploadCount(getTotalMediaCount());
} else {
setUploadCount();
}
}
/**
* Called when onAuthCookieAcquired is called on authenticated parent activity
* @param uploadServiceIntent
@ -345,7 +314,7 @@ public class ContributionsFragment
if (getActivity() != null) { // If fragment is attached to parent activity
getActivity().bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
isUploadServiceConnected = true;
getActivity().getSupportLoaderManager().initLoader(0, null, ContributionsFragment.this);
getActivity().getSupportLoaderManager().initLoader(0, null, contributionsPresenter);
}
}
@ -358,57 +327,24 @@ public class ContributionsFragment
public void showDetail(int i) {
if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment.isVisible()) {
mediaDetailPagerFragment = new MediaDetailPagerFragment();
setMediaDetailPagerFragment();
showMediaDetailPagerFragment();
}
mediaDetailPagerFragment.showImage(i);
}
@Override
public void refreshSource() {
getActivity().getSupportLoaderManager().restartLoader(0, null, this);
getActivity().getSupportLoaderManager().restartLoader(0, null, contributionsPresenter);
}
@Override
public Media getMediaAtPosition(int i) {
if (contributionsListFragment.getAdapter() == null) {
// not yet ready to return data
return null;
} else {
return contributionDao.fromCursor((Cursor) contributionsListFragment.getAdapter().getItem(i));
}
return contributionsPresenter.getItemAtPosition(i);
}
@Override
public int getTotalMediaCount() {
if (contributionsListFragment.getAdapter() == null) {
return 0;
}
return contributionsListFragment.getAdapter().getCount();
}
@Override
public void notifyDatasetChanged() {
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
Adapter adapter = contributionsListFragment.getAdapter();
if (adapter == null) {
observersWaitingForLoad.add(observer);
} else {
adapter.registerDataSetObserver(observer);
}
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
Adapter adapter = contributionsListFragment.getAdapter();
if (adapter == null) {
observersWaitingForLoad.remove(observer);
} else {
adapter.unregisterDataSetObserver(observer);
}
return numberOfContributions;
}
@SuppressWarnings("ConstantConditions")
@ -454,7 +390,7 @@ public class ContributionsFragment
@Override
public void onResume() {
super.onResume();
contributionsPresenter.onAttachView(this);
firstLocationUpdate = true;
locationManager.addLocationListener(this);
@ -624,11 +560,48 @@ public class ContributionsFragment
}
@Override
public void onEvent(String filename) {
for (int i=0;i<getTotalMediaCount();i++){
if (getMediaAtPosition(i).getFilename().equals(filename))
showDetail(i);
public void showWelcomeTip(boolean shouldShow) {
contributionsListFragment.showWelcomeTip(shouldShow);
}
@Override
public void showProgress(boolean shouldShow) {
contributionsListFragment.showProgress(shouldShow);
}
@Override
public void showNoContributionsUI(boolean shouldShow) {
contributionsListFragment.showNoContributionsUI(shouldShow);
}
@Override
public void setUploadCount(int count) {
this.numberOfContributions=count;
}
@Override
public void onDataSetChanged() {
contributionsListFragment.onDataSetChanged();
mediaDetailPagerFragment.onDataSetChanged();
}
/**
* Retry upload when it is failed
*
* @param contribution contribution to be retried
*/
private void retryUpload(Contribution contribution) {
if (NetworkUtils.isInternetConnectionEstablished(getContext())) {
if (contribution.getState() == STATE_FAILED && null != uploadService) {
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, contribution);
Timber.d("Restarting for %s", contribution.toString());
} else {
Timber.d("Skipping re-upload for non-failed %s", contribution.toString());
}
} else {
ViewUtil.showLongToast(getContext(), R.string.this_function_needs_network_connection);
}
}
}

View file

@ -1,121 +1,55 @@
package fr.free.nrw.commons.contributions;
import android.content.Context;
import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.cursoradapter.widget.CursorAdapter;
import android.database.DataSetObserver;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.model.DisplayableContribution;
import fr.free.nrw.commons.upload.UploadService;
import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import timber.log.Timber;
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionViewHolder> {
class ContributionsListAdapter extends CursorAdapter {
private Callback callback;
private final ContributionDao contributionDao;
private UploadService uploadService;
public ContributionsListAdapter(Context context,
Cursor c,
int flags,
ContributionDao contributionDao, EventListener listener) {
super(context, c, flags);
this.contributionDao = contributionDao;
this.listener=listener;
public ContributionsListAdapter(Callback callback) {
this.callback = callback;
}
public void setUploadService(UploadService uploadService) {
this.uploadService = uploadService;
@NonNull
@Override
public ContributionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ContributionViewHolder viewHolder = new ContributionViewHolder(
LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_contribution, parent, false), callback);
return viewHolder;
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
View parent = LayoutInflater.from(context)
.inflate(R.layout.layout_contribution, viewGroup, false);
parent.setTag(new ContributionViewHolder(parent));
return parent;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
final ContributionViewHolder views = (ContributionViewHolder)view.getTag();
final Contribution contribution = contributionDao.fromCursor(cursor);
Timber.d("Cursor position is %d", cursor.getPosition());
public void onBindViewHolder(@NonNull ContributionViewHolder holder, int position) {
final Contribution contribution = callback.getContributionForPosition(position);
DisplayableContribution displayableContribution = new DisplayableContribution(contribution,
cursor.getPosition(),
new DisplayableContribution.ContributionActions() {
@Override
public void retryUpload() {
ContributionsListAdapter.this.retryUpload(view.getContext(), contribution);
}
@Override
public void deleteUpload() {
ContributionsListAdapter.this.deleteUpload(view.getContext(), contribution);
}
@Override
public void onClick() {
ContributionsListAdapter.this.openMediaDetail(contribution);
}
});
views.bindModel(context, displayableContribution);
position);
holder.init(position, displayableContribution);
}
/**
* Retry upload when it is failed
* @param contribution contribution to be retried
*/
private void retryUpload(@NonNull Context context, Contribution contribution) {
if (NetworkUtils.isInternetConnectionEstablished(context)) {
if (contribution.getState() == STATE_FAILED
&& uploadService!= null) {
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, contribution);
Timber.d("Restarting for %s", contribution.toString());
} else {
Timber.d("Skipping re-upload for non-failed %s", contribution.toString());
}
} else {
ViewUtil.showLongToast(context, R.string.this_function_needs_network_connection);
}
@Override
public int getItemCount() {
return callback.getNumberOfContributions();
}
/**
* Delete a failed upload attempt
* @param contribution contribution to be deleted
*/
private void deleteUpload(@NonNull Context context, Contribution contribution) {
if (NetworkUtils.isInternetConnectionEstablished(context)) {
if (contribution.getState() == STATE_FAILED) {
Timber.d("Deleting failed contrib %s", contribution.toString());
contributionDao.delete(contribution);
} else {
Timber.d("Skipping deletion for non-failed contrib %s", contribution.toString());
}
} else {
ViewUtil.showLongToast(context, R.string.this_function_needs_network_connection);
}
public interface Callback {
}
void retryUpload(Contribution contribution);
private void openMediaDetail(Contribution contribution){
listener.onEvent(contribution.getFilename());
void deleteUpload(Contribution contribution);
}
EventListener listener;
void openMediaDetail(int contribution);
public interface EventListener {
void onEvent(String filename);
int getNumberOfContributions();
Contribution getContributionForPosition(int position);
int findItemPositionWithId(String lastVisibleItemID);
}
}

View file

@ -1,34 +1,33 @@
package fr.free.nrw.commons.contributions;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import android.content.res.Configuration;
import android.os.Bundle;
import androidx.annotation.Nullable;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ListAdapter;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.content.res.Configuration;
import android.widget.LinearLayout;
import javax.inject.Inject;
import javax.inject.Named;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.LayoutManager;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.utils.ConfigUtils;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import javax.inject.Inject;
import javax.inject.Named;
/**
* Created by root on 01.06.2018.
@ -36,8 +35,9 @@ import static android.view.View.VISIBLE;
public class ContributionsListFragment extends CommonsDaggerSupportFragment {
private static final String VISIBLE_ITEM_ID = "visible_item_id";
@BindView(R.id.contributionsList)
GridView contributionsList;
RecyclerView rvContributionsList;
@BindView(R.id.loadingContributionsProgressBar)
ProgressBar progressBar;
@BindView(R.id.fab_plus)
@ -62,29 +62,56 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
private boolean isFabOpen = false;
private ContributionsListAdapter adapter;
private Callback callback;
private String lastVisibleItemID;
private int SPAN_COUNT=3;
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_contributions_list, container, false);
ButterKnife.bind(this, view);
changeProgressBarVisibility(true);
initAdapter();
return view;
}
public void setCallback(Callback callback) {
this.callback = callback;
}
private void initAdapter() {
adapter = new ContributionsListAdapter(callback);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initRecyclerView();
initializeAnimations();
setListeners();
}
private void initRecyclerView() {
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
rvContributionsList.setLayoutManager(new GridLayoutManager(getContext(),SPAN_COUNT));
} else {
rvContributionsList.setLayoutManager(new LinearLayoutManager(getContext()));
}
rvContributionsList.setAdapter(adapter);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// check orientation
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
fab_layout.setOrientation(LinearLayout.HORIZONTAL);
rvContributionsList.setLayoutManager(new GridLayoutManager(getContext(),SPAN_COUNT));
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
fab_layout.setOrientation(LinearLayout.VERTICAL);
rvContributionsList.setLayoutManager(new LinearLayoutManager(getContext()));
}
}
@ -127,39 +154,78 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
}
}
/**
* Responsible to set progress bar invisible and visible
* @param isVisible True when contributions list should be hidden.
*/
public void changeProgressBarVisibility(boolean isVisible) {
this.progressBar.setVisibility(isVisible ? VISIBLE : GONE);
}
/**
* Shows welcome message if user has no contributions yet i.e. new user.
*/
protected void showWelcomeTip(boolean noContributions) {
noContributionsYet.setVisibility(noContributions ? VISIBLE : GONE);
}
public ListAdapter getAdapter() {
return contributionsList.getAdapter();
public void showWelcomeTip(boolean shouldShow) {
noContributionsYet.setVisibility(shouldShow ? VISIBLE : GONE);
}
/**
* Sets adapter to contributions list. If beta mode, sets upload count for beta explicitly.
* @param adapter List adapter for uploads of contributor
* Responsible to set progress bar invisible and visible
* @param shouldShow True when contributions list should be hidden.
*/
public void setAdapter(ListAdapter adapter) {
this.contributionsList.setAdapter(adapter);
public void showProgress(boolean shouldShow) {
progressBar.setVisibility(shouldShow ? VISIBLE : GONE);
}
if (ConfigUtils.isBetaFlavour()) {
//TODO: add betaSetUploadCount method
((ContributionsFragment) getParentFragment()).betaSetUploadCount(adapter.getCount());
public void showNoContributionsUI(boolean shouldShow) {
noContributionsYet.setVisibility(shouldShow?VISIBLE:GONE);
}
public void onDataSetChanged() {
if (null != adapter) {
adapter.notifyDataSetChanged();
//Restoring last visible item position in cases of orientation change
if (null != lastVisibleItemID) {
int itemPositionWithId = callback.findItemPositionWithId(lastVisibleItemID);
rvContributionsList.scrollToPosition(itemPositionWithId);
lastVisibleItemID = null;//Reset the lastVisibleItemID once we have used it
}
}
}
public interface SourceRefresher {
void refreshSource();
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
LayoutManager layoutManager = rvContributionsList.getLayoutManager();
int lastVisibleItemPosition=0;
if(layoutManager instanceof LinearLayoutManager){
lastVisibleItemPosition= ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition();
}else if(layoutManager instanceof GridLayoutManager){
lastVisibleItemPosition=((GridLayoutManager)layoutManager).findLastCompletelyVisibleItemPosition();
}
String idOfItemWithPosition = findIdOfItemWithPosition(lastVisibleItemPosition);
if (null != idOfItemWithPosition) {
outState.putString(VISIBLE_ITEM_ID, idOfItemWithPosition);
}
}
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if(null!=savedInstanceState){
lastVisibleItemID =savedInstanceState.getString(VISIBLE_ITEM_ID, null);
}
}
/**
* Gets the id of the contribution from the db
* @param position
* @return
*/
@Nullable
private String findIdOfItemWithPosition(int position) {
Contribution contributionForPosition = callback.getContributionForPosition(position);
if (null != contributionForPosition) {
return contributionForPosition.getContentUri().getLastPathSegment();
}
return null;
}
}

View file

@ -0,0 +1,47 @@
package fr.free.nrw.commons.contributions;
import android.database.Cursor;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import javax.inject.Inject;
import javax.inject.Named;
/**
* The LocalDataSource class for Contributions
*/
class ContributionsLocalDataSource {
private final ContributionDao contributionsDao;
private final JsonKvStore defaultKVStore;
@Inject
public ContributionsLocalDataSource(
@Named("default_preferences") JsonKvStore defaultKVStore,
ContributionDao contributionDao) {
this.defaultKVStore = defaultKVStore;
this.contributionsDao = contributionDao;
}
/**
* Fetch default number of contributions to be show, based on user preferences
*/
public int get(String key) {
return defaultKVStore.getInt(key);
}
/**
* Get contribution object from cursor
* @param cursor
* @return
*/
public Contribution getContributionFromCursor(Cursor cursor) {
return contributionsDao.fromCursor(cursor);
}
/**
* Remove a contribution from the contributions table
* @param contribution
*/
public void deleteContribution(Contribution contribution) {
contributionsDao.delete(contribution);
}
}

View file

@ -0,0 +1,15 @@
package fr.free.nrw.commons.contributions;
import dagger.Binds;
import dagger.Module;
/**
* The Dagger Module for contributions related presenters and (some other objects maybe in future)
*/
@Module
public abstract class ContributionsModule {
@Binds
public abstract ContributionsContract.UserActionListener bindsContibutionsPresenter(
ContributionsPresenter presenter);
}

View file

@ -0,0 +1,180 @@
package fr.free.nrw.commons.contributions;
import static fr.free.nrw.commons.contributions.ContributionDao.Table.ALL_FIELDS;
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI;
import static fr.free.nrw.commons.settings.Prefs.UPLOADS_SHOWING;
import android.content.Context;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.contributions.ContributionsContract.UserActionListener;
import javax.inject.Inject;
import timber.log.Timber;
/**
* The presenter class for Contributions
*/
public class ContributionsPresenter extends DataSetObserver implements UserActionListener {
private final ContributionsRepository repository;
private ContributionsContract.View view;
private Cursor cursor;
@Inject
Context context;
@Inject
ContributionsPresenter(ContributionsRepository repository) {
this.repository = repository;
}
@Override
public void onAttachView(ContributionsContract.View view) {
this.view = view;
if (null != cursor) {
try {
cursor.registerDataSetObserver(this);
} catch (IllegalStateException e) {//Cursor might be already registered
Timber.d(e);
}
}
}
@Override
public void onDetachView() {
this.view = null;
if (null != cursor) {
try {
cursor.unregisterDataSetObserver(this);
} catch (Exception e) {//Cursor might not be already registered
Timber.d(e);
}
}
}
@NonNull
@Override
public Loader<Cursor> onCreateLoader(int id, @Nullable Bundle args) {
int preferredNumberOfUploads = repository.get(UPLOADS_SHOWING);
return new CursorLoader(context, BASE_URI,
ALL_FIELDS, "", null,
ContributionDao.CONTRIBUTION_SORT + "LIMIT "
+ (preferredNumberOfUploads>0?preferredNumberOfUploads:100));
}
@Override
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor cursor) {
view.showProgress(false);
if (null != cursor && cursor.getCount() > 0) {
view.showWelcomeTip(false);
view.showNoContributionsUI(false);
view.setUploadCount(cursor.getCount());
} else {
view.showWelcomeTip(true);
view.showNoContributionsUI(true);
}
swapCursor(cursor);
}
@Override
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
this.cursor = null;
//On LoadFinished is not guaranteed to be called
view.showProgress(false);
view.showWelcomeTip(true);
view.showNoContributionsUI(true);
swapCursor(null);
}
/**
* Get contribution from the repository
*/
@Override
public Contribution getContributionsFromCursor(Cursor cursor) {
return repository.getContributionFromCursor(cursor);
}
/**
* Delete a failed contribution from the local db
* @param contribution
*/
@Override
public void deleteUpload(Contribution contribution) {
repository.deleteContributionFromDB(contribution);
}
/**
* Returns a contribution at the specified cursor position
* @param i
* @return
*/
@Nullable
@Override
public Media getItemAtPosition(int i) {
if (null != cursor && cursor.moveToPosition(i)) {
return getContributionsFromCursor(cursor);
}
return null;
}
/**
* Get contribution position with id
*/
public int getChildPositionWithId(String id) {
int position = 0;
cursor.moveToFirst();
while (null != cursor && cursor.moveToNext()) {
if (getContributionsFromCursor(cursor).getContentUri().getLastPathSegment()
.equals(id)) {
position = cursor.getPosition();
break;
}
}
return position;
}
@Override
public void onChanged() {
super.onChanged();
view.onDataSetChanged();
}
@Override
public void onInvalidated() {
super.onInvalidated();
//Not letting the view know of this as of now, TODO discuss how to handle this and maybe show a proper ui for this
}
/**
* Swap in a new Cursor, returning the old Cursor. The returned old Cursor is <em>not</em>
* closed.
*
* @param newCursor The new cursor to be used.
* @return Returns the previously set Cursor, or null if there was not one. If the given new
* Cursor is the same instance is the previously set Cursor, null is also returned.
*/
private void swapCursor(Cursor newCursor) {
try {
if (newCursor == cursor) {
return;
}
Cursor oldCursor = cursor;
if (oldCursor != null) {
oldCursor.unregisterDataSetObserver(this);
}
cursor = newCursor;
if (newCursor != null) {
newCursor.registerDataSetObserver(this);
}
view.onDataSetChanged();
} catch (IllegalStateException e) {//Cursor might [not] be already registered/unregistered
Timber.e(e);
}
}
}

View file

@ -0,0 +1,42 @@
package fr.free.nrw.commons.contributions;
import android.database.Cursor;
import javax.inject.Inject;
/**
* The repository class for contributions
*/
public class ContributionsRepository {
private ContributionsLocalDataSource localDataSource;
@Inject
public ContributionsRepository(ContributionsLocalDataSource localDataSource) {
this.localDataSource = localDataSource;
}
/**
* Fetch default number of contributions to be show, based on user preferences
*/
public int get(String uploadsShowing) {
return localDataSource.get(uploadsShowing);
}
/**
* Get contribution object from cursor from LocalDataSource
* @param cursor
* @return
*/
public Contribution getContributionFromCursor(Cursor cursor) {
return localDataSource.getContributionFromCursor(cursor);
}
/**
* Deletes a failed upload from DB
* @param contribution
*/
public void deleteContributionFromDB(Contribution contribution) {
localDataSource.deleteContribution(contribution);
}
}

View file

@ -3,7 +3,6 @@ package fr.free.nrw.commons.contributions;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
@ -386,13 +385,6 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag
case 0:
ContributionsFragment retainedContributionsFragment = getContributionsFragment(0);
if (retainedContributionsFragment != null) {
// ContributionsFragment is parent of ContributionsListFragment and
// MediaDetailsFragment. If below decides which child will be visible.
if (isContributionsListFragment) {
retainedContributionsFragment.setContributionsListFragment();
} else {
retainedContributionsFragment.setMediaDetailPagerFragment();
}
return retainedContributionsFragment;
} else {
// If we reach here, retainedContributionsFragment is null

View file

@ -4,9 +4,7 @@ import fr.free.nrw.commons.contributions.Contribution;
public class DisplayableContribution extends Contribution {
private int position;
private ContributionActions contributionActions;
private DisplayableContribution(Contribution contribution,
public DisplayableContribution(Contribution contribution,
int position) {
super(contribution.getContentUri(),
contribution.getFilename(),
@ -27,13 +25,6 @@ public class DisplayableContribution extends Contribution {
this.position = position;
}
public DisplayableContribution(Contribution contribution,
int position,
ContributionActions contributionActions) {
this(contribution, position);
this.contributionActions = contributionActions;
}
public int getPosition() {
return position;
}
@ -41,16 +32,4 @@ public class DisplayableContribution extends Contribution {
public void setPosition(int position) {
this.position = position;
}
public ContributionActions getContributionActions() {
return contributionActions;
}
public interface ContributionActions {
void retryUpload();
void deleteUpload();
void onClick();
}
}

View file

@ -59,7 +59,7 @@ public class DeleteHelper {
* @return
*/
public Single<Boolean> makeDeletion(Context context, Media media, String reason) {
viewUtil.showShortToast(context, "Trying to nominate " + media.getDisplayTitle() + " for deletion");
viewUtil.showShortToast(context, context.getString((R.string.delete_helper_make_deletion_toast), media.getDisplayTitle()));
return Single.fromCallable(() -> delete(media, reason))
.flatMap(result -> Single.fromCallable(() ->
showDeletionNotification(context, media, result)));
@ -123,14 +123,14 @@ public class DeleteHelper {
private boolean showDeletionNotification(Context context, Media media, boolean result) {
String message;
String title = "Nominating for Deletion";
String title = context.getString(R.string.delete_helper_show_deletion_title);
if (result) {
title += ": Success";
message = "Successfully nominated " + media.getDisplayTitle() + " deletion.";
title += ": " + context.getString(R.string.delete_helper_show_deletion_title_success);
message = context.getString((R.string.delete_helper_show_deletion_message_if),media.getDisplayTitle());
} else {
title += ": Failed";
message = "Could not request deletion.";
title += ": " + context.getString(R.string.delete_helper_show_deletion_title_failed);
message = context.getString(R.string.delete_helper_show_deletion_message_else) ;
}
String urlForDelete = BuildConfig.COMMONS_URL + "/wiki/Commons:Deletion_requests/" + media.getFilename();
@ -162,15 +162,15 @@ public class DeleteHelper {
if (problem == ReviewController.DeleteReason.SPAM) {
reasonList[0] = context.getResources().getString(R.string.delete_reason_spam_selfie);
reasonList[1] = context.getResources().getString(R.string.delete_reason_spam_blurry);
reasonList[2] = context.getResources().getString(R.string.delete_reason_spam_nonsense);
reasonList[3] = context.getResources().getString(R.string.delete_reason_spam_other);
reasonList[0] = context.getString(R.string.delete_helper_ask_spam_selfie);
reasonList[1] = context.getString(R.string.delete_helper_ask_spam_blurry);
reasonList[2] = context.getString(R.string.delete_helper_ask_spam_nonsense);
reasonList[3] = context.getString(R.string.delete_helper_ask_spam_other);
} else if (problem == ReviewController.DeleteReason.COPYRIGHT_VIOLATION) {
reasonList[0] = context.getResources().getString(R.string.delete_reason_copyright_pressphoto);
reasonList[1] = context.getResources().getString(R.string.delete_reason_copyright_internetphoto);
reasonList[2] = context.getResources().getString(R.string.delete_reason_copyright_logo);
reasonList[3] = context.getResources().getString(R.string.delete_reason_copyright_other);
reasonList[0] = context.getString(R.string.delete_helper_ask_reason_copyright_press_photo);
reasonList[1] = context.getString(R.string.delete_helper_ask_reason_copyright_internet_photo);
reasonList[2] = context.getString(R.string.delete_helper_ask_reason_copyright_logo);
reasonList[3] = context.getString(R.string.delete_helper_ask_reason_copyright_other);
}
alert.setMultiChoiceItems(reasonList, checkedItems, (dialogInterface, position, isChecked) -> {
@ -181,9 +181,9 @@ public class DeleteHelper {
}
});
alert.setPositiveButton("OK", (dialogInterface, i) -> {
alert.setPositiveButton(context.getString(R.string.ok), (dialogInterface, i) -> {
String reason = "Because it is ";
String reason = context.getString(R.string.delete_helper_ask_alert_set_positive_button_reason) + " ";
for (int j = 0; j < mUserReason.size(); j++) {
reason = reason + reasonList[mUserReason.get(j)];
if (j != mUserReason.size() - 1) {
@ -203,7 +203,7 @@ public class DeleteHelper {
});
});
alert.setNegativeButton("Cancel", (dialog, which) -> reviewCallback.onFailure());
alert.setNegativeButton(context.getString(R.string.cancel), (dialog, which) -> reviewCallback.onFailure());
AlertDialog d = alert.create();
d.show();
}

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.di;
import fr.free.nrw.commons.contributions.ContributionsModule;
import javax.inject.Singleton;
import dagger.Component;
@ -28,7 +29,7 @@ import fr.free.nrw.commons.widget.PicOfDayAppWidget;
ActivityBuilderModule.class,
FragmentBuilderModule.class,
ServiceBuilderModule.class,
ContentProviderBuilderModule.class, UploadModule.class
ContentProviderBuilderModule.class, UploadModule.class, ContributionsModule.class
})
public interface CommonsApplicationComponent extends AndroidInjector<ApplicationlessInjection> {
void inject(CommonsApplication application);

View file

@ -143,14 +143,6 @@ public class SearchActivity extends NavigationBaseActivity implements MediaDetai
return searchImageFragment.getTotalImagesCount();
}
/**
* This method is never called but it was in MediaDetailProvider Interface
* so it needs to be overrided.
*/
@Override
public void notifyDatasetChanged() {
}
/**
* This method is called on success of API call for image Search.
* The viewpager will notified that number of items have changed.
@ -161,24 +153,6 @@ public class SearchActivity extends NavigationBaseActivity implements MediaDetai
}
}
/**
* This method is never called but it was in MediaDetailProvider Interface
* so it needs to be overrided.
*/
@Override
public void registerDataSetObserver(DataSetObserver observer) {
}
/**
* This method is never called but it was in MediaDetailProvider Interface
* so it needs to be overrided.
*/
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
}
/**
* Open media detail pager fragment on click of image in search results
* @param index item index that should be opened

View file

@ -141,14 +141,6 @@ public class ExploreActivity
}
}
/**
* This method is never called but it was in MediaDetailProvider Interface
* so it needs to be overrided.
*/
@Override
public void notifyDatasetChanged() {
}
/**
* This method is called on success of API call for featured images or mobile uploads.
* The viewpager will notified that number of items have changed.
@ -159,23 +151,6 @@ public class ExploreActivity
}
}
/**
* This method is never called but it was in MediaDetailProvider Interface
* so it needs to be overrided.
*/
@Override
public void registerDataSetObserver(DataSetObserver observer) {
}
/**
* This method is never called but it was in MediaDetailProvider Interface
* so it needs to be overrided.
*/
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
}
/**
* This method is called on backPressed of anyFragment in the activity.

View file

@ -1,5 +1,8 @@
package fr.free.nrw.commons.media;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Intent;
@ -21,25 +24,13 @@ import android.widget.ScrollView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.SimpleDraweeView;
import com.facebook.imagepipeline.request.ImageRequest;
import org.apache.commons.lang3.StringUtils;
import org.wikipedia.util.DateUtil;
import org.wikipedia.util.StringUtil;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import javax.inject.Inject;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.MediaDataExtractor;
import fr.free.nrw.commons.R;
@ -57,11 +48,15 @@ import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import javax.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.wikipedia.util.DateUtil;
import org.wikipedia.util.StringUtil;
import timber.log.Timber;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
public class MediaDetailFragment extends CommonsDaggerSupportFragment {
private boolean editable;
@ -134,7 +129,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
private boolean categoriesPresent = false;
private ViewTreeObserver.OnGlobalLayoutListener layoutListener; // for layout stuff, only used once!
private ViewTreeObserver.OnScrollChangedListener scrollListener;
private DataSetObserver dataObserver;
//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;
@ -232,34 +226,15 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
@Override
public void onResume() {
super.onResume();
if(getParentFragment()!=null && getParentFragment().getParentFragment()!=null) {
if (getParentFragment() != null && getParentFragment().getParentFragment() != null) {
//Added a check because, not necessarily, the parent fragment will have a parent fragment, say
// in the case when MediaDetailPagerFragment is directly started by the CategoryImagesActivity
((ContributionsFragment) (getParentFragment().getParentFragment())).nearbyNotificationCardView
.setVisibility(View.GONE);
((ContributionsFragment) (getParentFragment()
.getParentFragment())).nearbyNotificationCardView
.setVisibility(View.GONE);
}
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");
dataObserver = new DataSetObserver() {
@Override
public void onChanged() {
if (!isAdded()) {
return;
}
Timber.d("MediaDetailFragment ready to display delayed details!");
detailProvider.unregisterDataSetObserver(dataObserver);
dataObserver = null;
media=detailProvider.getMediaAtPosition(index);
displayMediaDetails();
}
};
detailProvider.registerDataSetObserver(dataObserver);
} else {
Timber.d("MediaDetailFragment ready to display details");
displayMediaDetails();
}
displayMediaDetails();
}
private void displayMediaDetails() {
@ -300,10 +275,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
getView().getViewTreeObserver().removeOnScrollChangedListener(scrollListener);
scrollListener = null;
}
if (dataObserver != null) {
detailProvider.unregisterDataSetObserver(dataObserver);
dataObserver = null;
}
compositeDisposable.clear();
super.onDestroyView();
}

View file

@ -1,9 +1,12 @@
package fr.free.nrw.commons.media;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.content.Context.DOWNLOAD_SERVICE;
import static fr.free.nrw.commons.Utils.handleWebUrl;
import android.annotation.SuppressLint;
import android.app.DownloadManager;
import android.content.Intent;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
@ -15,11 +18,6 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import javax.inject.Inject;
import javax.inject.Named;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
@ -44,12 +42,10 @@ import fr.free.nrw.commons.utils.ImageUtils;
import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.content.Context.DOWNLOAD_SERVICE;
import static fr.free.nrw.commons.Utils.handleWebUrl;
public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment implements ViewPager.OnPageChangeListener {
@Inject MediaWikiApi mwApi;
@ -351,16 +347,16 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
public void onPageScrollStateChanged(int i) {
}
public void onDataSetChanged() {
if (null != adapter) {
adapter.notifyDataSetChanged();
}
}
public interface MediaDetailProvider {
Media getMediaAtPosition(int i);
int getTotalMediaCount();
void notifyDatasetChanged();
void registerDataSetObserver(DataSetObserver observer);
void unregisterDataSetObserver(DataSetObserver observer);
}
//FragmentStatePagerAdapter allows user to swipe across collection of images (no. of images undetermined)

View file

@ -1,28 +1,10 @@
package fr.free.nrw.commons.mwapi;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.lang3.StringUtils;
import org.wikipedia.dataclient.mwapi.MwQueryPage;
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.achievements.FeaturedImages;
import fr.free.nrw.commons.achievements.FeedbackResponse;
@ -38,10 +20,23 @@ import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.wikidata.model.GetWikidataEditCountResponse;
import io.reactivex.Observable;
import io.reactivex.Single;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.apache.commons.lang3.StringUtils;
import org.wikipedia.dataclient.mwapi.MwQueryPage;
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
import timber.log.Timber;
/**
@ -98,8 +93,16 @@ public class OkHttpJsonApiClient {
return Single.fromCallable(() -> {
Response response = okHttpClient.newCall(request).execute();
if (response != null && response.isSuccessful()) {
if(!TextUtils.isEmpty(response.body().string().trim())){
return Integer.parseInt(response.body().string().trim());
ResponseBody responseBody = response.body();
if (null != responseBody) {
String responseBodyString = responseBody.string().trim();
if (!TextUtils.isEmpty(responseBodyString)) {
try {
return Integer.parseInt(responseBodyString);
} catch (NumberFormatException e) {
Timber.e(e);
}
}
}
}
return 0;

View file

@ -4,7 +4,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.webkit.MimeTypeMap;
import androidx.exifinterface.media.ExifInterface;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
@ -15,8 +15,6 @@ import java.io.InputStreamReader;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import androidx.exifinterface.media.ExifInterface;
import timber.log.Timber;
public class FileUtils {
@ -164,4 +162,17 @@ public class FileUtils {
}
return true;
}
/**
* Check if file exists in local dirs
*/
public static boolean fileExists(Uri localUri) {
try {
File file = new File(localUri.getPath());
return file.exists();
} catch (Exception e) {
Timber.d(e);
return false;
}
}
}

View file

@ -20,21 +20,16 @@
/>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:id="@+id/loadingContributionsProgressBar"
/>
<GridView
android:id="@+id/loadingContributionsProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/contributionsList"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:stretchMode="columnWidth"
android:columnWidth="240dp"
android:numColumns="auto_fit"
android:listSelector="@null"
android:fadingEdge="none"
android:fastScrollEnabled="true"
/>
<LinearLayout

View file

@ -537,4 +537,19 @@
<string name="dialog_box_text_nomination">لماذا يجب حذف %1$s؟</string>
<string name="review_is_uploaded_by">%1$s رُفِع بواسطة: %2$s</string>
<string name="default_description_language">لغة الوصف الافتراضية</string>
<string name="delete_helper_make_deletion_toast">تجري محاولة ترشيح %1$s للحذف</string>
<string name="delete_helper_show_deletion_title">ترشيح للحذف</string>
<string name="delete_helper_show_deletion_title_success">نجاح</string>
<string name="delete_helper_show_deletion_message_if">تم بنجاح ترشيح %1$s للحذف.</string>
<string name="delete_helper_show_deletion_title_failed">فشل</string>
<string name="delete_helper_show_deletion_message_else">لا يمكن طلب الحذف.</string>
<string name="delete_helper_ask_spam_selfie">سيلفي</string>
<string name="delete_helper_ask_spam_blurry">ضبابية</string>
<string name="delete_helper_ask_spam_nonsense">كلام فارغ</string>
<string name="delete_helper_ask_spam_other">أخرى</string>
<string name="delete_helper_ask_reason_copyright_press_photo">اضغط على الصورة</string>
<string name="delete_helper_ask_reason_copyright_internet_photo">صورة عشوائية من الإنترنت</string>
<string name="delete_helper_ask_reason_copyright_logo">شعار</string>
<string name="delete_helper_ask_reason_copyright_other">أخرى</string>
<string name="delete_helper_ask_alert_set_positive_button_reason">لأنها</string>
</resources>

View file

@ -370,9 +370,16 @@
<string name="menu_option_unread">Vis ulæste</string>
<string name="error_occurred_in_picking_images">Der opstod en fejl under udvælgelse af billeder</string>
<string name="please_wait">Vent venligst…</string>
<string name="skip_image" fuzzy="true">Spring over dette billede</string>
<string name="skip_image">Spring over dette billede</string>
<string name="exif_tag_name_copyright">Ophavsret</string>
<string name="exif_tag_name_cameraModel">Kameramodel</string>
<string name="exif_tag_name_serialNumbers">Serienumre</string>
<string name="share_via">Del app via...</string>
<string name="no_categories_found">Ingen kategorier blev fundet</string>
<string name="delete_helper_ask_spam_selfie">Et selvportræt</string>
<string name="delete_helper_ask_spam_blurry">Sløret</string>
<string name="delete_helper_ask_spam_nonsense">Vrøvl</string>
<string name="delete_helper_ask_spam_other">Andet</string>
<string name="delete_helper_ask_reason_copyright_internet_photo">Vilkårligt billede fra internettet</string>
<string name="delete_helper_ask_reason_copyright_other">Andet</string>
</resources>

View file

@ -11,6 +11,7 @@
* ManosHacker
* Nikosgranturismogt
* Nikosguard
* Panos78
* Protnet
* Tgkarounos
-->
@ -268,17 +269,17 @@
<string name="nearby_location_has_not_changed">Ο εντοπισμός δεν έχει αλλάξει.</string>
<string name="nearby_location_not_available">Ο τόπος δεν είναι διαθέσιμος.</string>
<string name="location_permission_rationale_nearby">Απαιτείται άδεια για την εμφάνιση λίστας κοντινών σημείων</string>
<string name="get_directions" fuzzy="true">Λάβετε κατευθύνσεις</string>
<string name="read_article" fuzzy="true">Ανάγνωση άρθρου</string>
<string name="get_directions">Λήψη κατευθύνσεων</string>
<string name="read_article">Ανάγνωση άρθρου</string>
<string name="notifications_welcome">Καλωσήρθατε στο Wikimedia Commons, %1$s! Είμαστε χαρούμενοι που είστε εδώ.</string>
<string name="notifications_talk_page_message">Ο %1$s άφησε ένα μήνυμα στην σελίδα συζήτησής σας</string>
<string name="notifications_thank_you_edit">Ευχαριστούμε που κάνατε μια επεξεργασία</string>
<string name="notifications_mention">Ο %1$s σας ανέφερε στο %2$s.</string>
<string name="toggle_view_button">Μεταβολή προβολής</string>
<string name="nearby_directions" fuzzy="true">Κατευθύνσεις</string>
<string name="nearby_wikidata" fuzzy="true">Βικιδεδομένα</string>
<string name="nearby_wikipedia" fuzzy="true">Βικιπαίδεια</string>
<string name="nearby_commons" fuzzy="true">Κοινά</string>
<string name="nearby_directions">Κατευθύνσεις</string>
<string name="nearby_wikidata">Δεδομένα wiki</string>
<string name="nearby_wikipedia">Βικιπαίδεια</string>
<string name="nearby_commons">Κοινά</string>
<string name="about_rate_us">&lt;u&gt;Βαθμολογήστε μας&lt;/u&gt;</string>
<string name="about_faq">&lt;u&gt;Συχνές ερωτήσεις&lt;/u&gt;</string>
<string name="welcome_skip_button">Παράβλεψη εισαγωγής</string>
@ -310,8 +311,8 @@
<string name="provider_searches">Πρόσφατα αναζητημένα ερωτήματα</string>
<string name="error_loading_categories">Συνέβη σφάλμα κατά τη φόρτωση κατηγοριών.</string>
<string name="error_loading_subcategories">Συνέβη σφάλμα κατά τη φόρτωση υποκατηγοριών.</string>
<string name="search_tab_title_media" fuzzy="true">Μέσα ενημέρωσης</string>
<string name="search_tab_title_categories" fuzzy="true">Κατηγορίες</string>
<string name="search_tab_title_media">Μέσα</string>
<string name="search_tab_title_categories">Κατηγορίες</string>
<string name="successful_wikidata_edit">Η εικόνα προστέθηκε επιτυχώς στο %1$s στο Wikidata!</string>
<string name="wikidata_edit_failure">Αποτυχία ενημέρωσης της αντιστοιχούσας οντότητας του Wikidata!</string>
<string name="menu_set_wallpaper">Ρύθμιση ως ταπετσαρία</string>
@ -344,10 +345,10 @@
<string name="search_history_deleted">Το ιστορικό αναζήτησης διεγράφη</string>
<string name="nominate_delete">Ορίστε για Αφαίρεση</string>
<string name="Achievements">Κατορθώματα</string>
<string name="statistics" fuzzy="true">Στατιστικά</string>
<string name="statistics">Στατιστικά</string>
<string name="statistics_thanks">Ευχαριστίες που έχουν ληφθεί</string>
<string name="statistics_featured">Προβεβλημμένες εικόνες</string>
<string name="level" fuzzy="true">Επίπεδο</string>
<string name="level">Επίπεδο</string>
<string name="images_uploaded">Εικόνες που ανέβηκαν</string>
<string name="image_reverts">Εικόνες που δεν ανεστράφησαν</string>
<string name="images_used_by_wiki">Εικόνες που χρησιμοποιήθηκαν</string>

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Authors:
* Abijeet Patro
* Metraduk
* Mirin
* Robin van der Vliet
@ -529,4 +530,12 @@
<string name="dialog_box_text_nomination">Kial %1$s foriĝu?</string>
<string name="review_is_uploaded_by">%1$s estas alŝutita de: %2$s</string>
<string name="default_description_language">Implicita priskriba lingvo</string>
<string name="delete_helper_ask_spam_selfie">Memfoto</string>
<string name="delete_helper_ask_spam_blurry">Malklara</string>
<string name="delete_helper_ask_spam_nonsense">Sensencaĵo</string>
<string name="delete_helper_ask_spam_other">Alia</string>
<string name="delete_helper_ask_reason_copyright_press_photo">Gazetara foo</string>
<string name="delete_helper_ask_reason_copyright_internet_photo">Hazarda foto el Interreto</string>
<string name="delete_helper_ask_reason_copyright_logo">Emblemo</string>
<string name="delete_helper_ask_reason_copyright_other">Alia</string>
</resources>

View file

@ -22,6 +22,7 @@
* Mirzali
* Tiberius1701
* Vivaelcelta
* Wizardeck
-->
<resources>
<string name="title_activity_explore">Explorar</string>
@ -188,6 +189,7 @@
<string name="storage_permission_title">Pidiendo permiso de almacenamiento</string>
<string name="read_storage_permission_rationale">Permiso necesario: lectura de almacenamiento externo. La aplicación no puede acceder a la galería sin él.</string>
<string name="write_storage_permission_rationale">Permiso necesario: escritura en almacenamiento externo. La aplicación no puede acceder a la cámara o la galería sin este.</string>
<string name="location_permission_title">Pidiendo Permiso de Ubicación</string>
<string name="location_permission_rationale">Permiso opcional: obtener la ubicación actual para sugerir categorías</string>
<string name="ok">Aceptar</string>
<string name="title_activity_nearby">Lugares cercanos</string>
@ -528,6 +530,7 @@
<string name="skip_image">Omitir esta imagen</string>
<string name="download_failed_we_cannot_download_the_file_without_storage_permission">La descarga falló!. No podemos descargar el archivo sin el permiso de almacenamiento externo.</string>
<string name="manage_exif_tags">Gestionar etiquetas EXIF</string>
<string name="manage_exif_tags_summary">Seleccionar qué etiquetas EXIF se mantendrán en la subida</string>
<string name="exif_tag_name_author">Autor</string>
<string name="exif_tag_name_copyright">Derechos de autor</string>
<string name="exif_tag_name_location">Ubicación</string>
@ -535,9 +538,21 @@
<string name="exif_tag_name_lensModel">Modelo de lente</string>
<string name="exif_tag_name_serialNumbers">Números de serie</string>
<string name="exif_tag_name_software">Programa</string>
<string name="share_text">Sube fotos a Wikimedia Commons desde tu celular, descarga la aplicación de Commons: %1$s</string>
<string name="share_via">Compartir la aplicación vía...</string>
<string name="image_info">Información de la imagen</string>
<string name="no_categories_found">No se encontró ninguna categoría</string>
<string name="upload_cancelled">Se canceló la carga</string>
<string name="previous_image_title_description_not_found">No hay datos sobre el título o la descripción anteriores de la imagen</string>
<string name="dialog_box_text_nomination">¿Por qué debe borrarse %1$s?</string>
<string name="review_is_uploaded_by">%1$s fue subida por: %2$s</string>
<string name="default_description_language">Idioma por defecto de la descripción</string>
<string name="delete_helper_ask_spam_selfie">Un autorretrato</string>
<string name="delete_helper_ask_spam_blurry">Borrosa</string>
<string name="delete_helper_ask_spam_nonsense">Sinsentido</string>
<string name="delete_helper_ask_spam_other">Otra</string>
<string name="delete_helper_ask_reason_copyright_press_photo">Fotografía para prensa</string>
<string name="delete_helper_ask_reason_copyright_internet_photo">Foto cualquiera tomada de internet</string>
<string name="delete_helper_ask_reason_copyright_logo">Logo</string>
<string name="delete_helper_ask_reason_copyright_other">Otra</string>
</resources>

View file

@ -2,6 +2,7 @@
<!-- Authors:
* Abijeet Patro
* Cyclicus
* Derugon
* Fitoschido
* Friday83260
* Gomoko
@ -21,6 +22,7 @@
* Tpt
* Urhixidur
* VIGNERON
* Verdy p
* Wladek92
* Y-M D
-->
@ -545,6 +547,16 @@
<string name="upload_cancelled">Téléversement annulé</string>
<string name="previous_image_title_description_not_found">Il ny a pas de données pour le titre ou la description de limage précédente</string>
<string name="dialog_box_text_nomination">Pourquoi %1$s devrait-il être supprimé?</string>
<string name="review_is_uploaded_by">%1$s est téléversé par : %2$s</string>
<string name="review_is_uploaded_by">%1$s est téléversé par&amp;nbsp;: %2$s</string>
<string name="default_description_language">Langue de description par défaut</string>
<string name="delete_helper_make_deletion_toast">Tentative de signalement de %1$s pour la suppression</string>
<string name="delete_helper_show_deletion_title_failed">Échec</string>
<string name="delete_helper_ask_spam_selfie">Un selfie</string>
<string name="delete_helper_ask_spam_blurry">Flou</string>
<string name="delete_helper_ask_spam_nonsense">Non-sens</string>
<string name="delete_helper_ask_spam_other">Autre</string>
<string name="delete_helper_ask_reason_copyright_press_photo">Photo de presse</string>
<string name="delete_helper_ask_reason_copyright_internet_photo">Photo aléatoire sur internet</string>
<string name="delete_helper_ask_reason_copyright_logo">Logo</string>
<string name="delete_helper_ask_reason_copyright_other">Autre</string>
</resources>

View file

@ -481,4 +481,7 @@
<string name="dialog_box_text_nomination">Perché %1$s dovrebbe essere cancellato?</string>
<string name="review_is_uploaded_by">%1$s è stato caricato da: %2$s</string>
<string name="default_description_language">Lingua predefinita descrizione</string>
<string name="delete_helper_ask_spam_other">Altro</string>
<string name="delete_helper_ask_reason_copyright_logo">Logo</string>
<string name="delete_helper_ask_reason_copyright_other">Altro</string>
</resources>

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Authors:
* Abijeet Patro
* CYAN
* Dlsrks1021
* Doyoon1995
@ -468,4 +469,9 @@
<string name="no_categories_found">분류가 없습니다</string>
<string name="upload_cancelled">업로드 취소됨</string>
<string name="default_description_language">기본 설명 언어</string>
<string name="delete_helper_ask_spam_selfie">셀카</string>
<string name="delete_helper_ask_spam_other">기타</string>
<string name="delete_helper_ask_reason_copyright_internet_photo">인터넷의 임의 사진</string>
<string name="delete_helper_ask_reason_copyright_logo">로고</string>
<string name="delete_helper_ask_reason_copyright_other">기타</string>
</resources>

View file

@ -241,9 +241,9 @@
<string name="notifications_thank_you_edit">Merci datt Dir eng Ännerung gemaach hutt</string>
<string name="notifications_mention">%1$s huet Iech op %2$s ernimmt.</string>
<string name="nearby_directions" fuzzy="true">RICHTUNGEN</string>
<string name="nearby_wikidata" fuzzy="true">WIKIDATA</string>
<string name="nearby_wikipedia" fuzzy="true">WIKIPEDIA</string>
<string name="nearby_commons" fuzzy="true">COMMONS</string>
<string name="nearby_wikidata">Wikidata</string>
<string name="nearby_wikipedia">Wikipedia</string>
<string name="nearby_commons">Commons</string>
<string name="about_rate_us">&lt;u&gt;Bewäert eis&lt;/u&gt;</string>
<string name="about_faq">&lt;u&gt;FAQ&lt;/u&gt;</string>
<string name="no_internet">Internet net disponibel</string>
@ -267,7 +267,7 @@
<string name="search_commons">Op Commons sichen</string>
<string name="title_activity_search">Sichen</string>
<string name="search_tab_title_media" fuzzy="true">MEDIEN</string>
<string name="search_tab_title_categories" fuzzy="true">KATEGORIEN</string>
<string name="search_tab_title_categories">Kategorien</string>
<string name="explore_tab_title_mobile" fuzzy="true">MOBIL EROPGELUEDEN</string>
<string name="wikidata_edit_failure">Déi entspriechend Wikidata-Entitéit konnt net aktualiséiert ginn!</string>
<string name="menu_set_wallpaper">Als Hannergrondbild festleeën</string>
@ -280,9 +280,9 @@
<string name="wrong">Falsch Äntwert</string>
<string name="add_description">+ Beschreiwung derbäisetzen</string>
<string name="nominate_delete">Nominéiere fir ze Läschen</string>
<string name="delete" fuzzy="true">LÄSCHEN</string>
<string name="delete">Läschen</string>
<string name="Achievements">Realisatiounen</string>
<string name="statistics" fuzzy="true">STATISTIKEN</string>
<string name="statistics">Statistiken</string>
<string name="statistics_thanks">Merci\'e kritt</string>
<string name="statistics_featured">Bemierkenswäert Biller</string>
<string name="level" fuzzy="true">NIVEAU</string>
@ -345,4 +345,5 @@
<string name="exif_tag_name_software">Software</string>
<string name="no_categories_found">Keng Kategorie fonnt.</string>
<string name="upload_cancelled">Eroplueden ofgebrach</string>
<string name="delete_helper_ask_reason_copyright_logo">Logo</string>
</resources>

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Authors:
* Abijeet Patro
* Admresdeserv.
* Papuass
* Silraks
@ -48,7 +49,7 @@
<string name="title_activity_settings">Iestatījumi</string>
<string name="title_activity_signup">Reģistrēties</string>
<string name="menu_about">Par</string>
<string name="about_improve" fuzzy="true">&lt;a href=\"https://github.com/commons-app/apps-android-commons\"&gt;Izejas kods&lt;/a&gt; un &lt;a href=\"https://commons-app.github.io/\"&gt;tīmekļa vietne&lt;/a&gt; GitHub. Izveido jaunu &lt;a href=\"https://github.com/commons-app/apps-android-commons/issues\"&gt;GitHub pieteikumu&lt;/a&gt; kļūdas ziņojumam vai ieteikumam.</string>
<string name="about_improve">&lt;a href=\"https://github.com/commons-app/apps-android-commons\"&gt;Izejas kods&lt;/a&gt; un &lt;a href=\"https://commons-app.github.io/\"&gt;tīmekļa vietne&lt;/a&gt; GitHub. Izveido jaunu &lt;a href=\"%1$s\"&gt;GitHub pieteikumu&lt;/a&gt; kļūdas ziņojumam vai ieteikumam.</string>
<string name="about_privacy_policy" fuzzy="true">&lt;a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\"&gt;Privātuma politika&lt;/a&gt;</string>
<string name="title_activity_about">Par</string>
<string name="menu_feedback">Nosūtīt atsauksmes (pa e-pastu)</string>
@ -105,15 +106,20 @@
<string name="give_permission">Atļaut</string>
<string name="use_external_storage">Izmantot ārējo krātuvi</string>
<string name="skip_login">Izlaist</string>
<string name="read_article">LASĪT RAKSTU</string>
<string name="get_directions">Saņemt norādes</string>
<string name="read_article">Lasīt rakstu</string>
<string name="notifications_thank_you_edit">Paldies par labojumu</string>
<string name="toggle_view_button">Pārslēgt skatu</string>
<string name="nearby_directions">Norādes</string>
<string name="about_translate_title">Valodas</string>
<string name="about_translate_proceed">Turpināt</string>
<string name="about_translate_cancel">Atcelt</string>
<string name="showcase_view_got_it_button">Sapratu!</string>
<string name="search_tab_title_categories">Kategorijas</string>
<string name="question">Jautājums</string>
<string name="add_description">+ Pievienot aprakstu</string>
<string name="delete">Dzēst</string>
<string name="statistics">Statistika</string>
<string name="submit">Iesniegt</string>
<string name="navigation_item_bookmarks">Grāmatzīmes</string>
<string name="title_activity_bookmarks">Grāmatzīmes</string>
@ -125,6 +131,23 @@
<string name="yes_submit">Jā, iesniegt</string>
<string name="nearby_card_permission_title">Atļaujas pieprasījums</string>
<string name="never_ask_again">Nekad vairs šo nejautāt</string>
<string name="nominate_for_deletion_done">Pabeigts</string>
<string name="send_thank_send">Sūta pateicību</string>
<string name="send_thank_notification_title">Sūta pateicību</string>
<string name="menu_option_archived">Skatīt arhivētos</string>
<string name="menu_option_unread">Skatīt nelasītos</string>
<string name="skip_image">Izlaist šo attēlu</string>
<string name="exif_tag_name_author">Autors</string>
<string name="exif_tag_name_copyright">Autortiesības</string>
<string name="exif_tag_name_location">Atrašanās vieta</string>
<string name="exif_tag_name_cameraModel">Fotoaparāta modelis</string>
<string name="exif_tag_name_lensModel">Objektīva modelis</string>
<string name="exif_tag_name_serialNumbers">Sērijas numuri</string>
<string name="exif_tag_name_software">Programmatūra</string>
<string name="delete_helper_show_deletion_title_failed">Neizdevās</string>
<string name="delete_helper_ask_spam_selfie">Pašbilde</string>
<string name="delete_helper_ask_spam_blurry">Miglains</string>
<string name="delete_helper_ask_spam_other">Cits</string>
<string name="delete_helper_ask_reason_copyright_internet_photo">Nejaušs foto no interneta</string>
<string name="delete_helper_ask_reason_copyright_logo">Logotips</string>
</resources>

View file

@ -527,4 +527,19 @@
<string name="dialog_box_text_nomination">Зошто сметате дека %1$s треба да се избрише?</string>
<string name="review_is_uploaded_by">%1$s е подигната од: %2$s</string>
<string name="default_description_language">Стандарден јазик на описот</string>
<string name="delete_helper_make_deletion_toast">Се обидувам да ја предложам %1$s за бришење</string>
<string name="delete_helper_show_deletion_title">Предлагам за бришење</string>
<string name="delete_helper_show_deletion_title_success">Успеа</string>
<string name="delete_helper_show_deletion_message_if">Успешно предложена %1$s за бришење.</string>
<string name="delete_helper_show_deletion_title_failed">Не успеа</string>
<string name="delete_helper_show_deletion_message_else">Не можев да побарам бришење.</string>
<string name="delete_helper_ask_spam_selfie">Самослик</string>
<string name="delete_helper_ask_spam_blurry">Матно</string>
<string name="delete_helper_ask_spam_nonsense">Бесмислено</string>
<string name="delete_helper_ask_spam_other">Друго</string>
<string name="delete_helper_ask_reason_copyright_press_photo">Новинарска слика</string>
<string name="delete_helper_ask_reason_copyright_internet_photo">Произволна слика од семрежјето</string>
<string name="delete_helper_ask_reason_copyright_logo">Лого</string>
<string name="delete_helper_ask_reason_copyright_other">Друго</string>
<string name="delete_helper_ask_alert_set_positive_button_reason">Бидејќи е</string>
</resources>

View file

@ -540,4 +540,12 @@
<string name="dialog_box_text_nomination">Por que %1$s deve ser excluído?</string>
<string name="review_is_uploaded_by">%1$s é carregado por: %2$s</string>
<string name="default_description_language">Idioma de descrição padrão</string>
<string name="delete_helper_ask_spam_selfie">Uma selfie</string>
<string name="delete_helper_ask_spam_blurry">Embaçado</string>
<string name="delete_helper_ask_spam_nonsense">Absurdo</string>
<string name="delete_helper_ask_spam_other">Outro</string>
<string name="delete_helper_ask_reason_copyright_press_photo">Divulgação</string>
<string name="delete_helper_ask_reason_copyright_internet_photo">Foto aleatória da internet</string>
<string name="delete_helper_ask_reason_copyright_logo">Logótipo</string>
<string name="delete_helper_ask_reason_copyright_other">Outro</string>
</resources>

View file

@ -287,14 +287,14 @@
<string name="nearby_location_has_not_changed">A localização não foi alterada.</string>
<string name="nearby_location_not_available">A localização não está disponível.</string>
<string name="location_permission_rationale_nearby">É necessária a permissão para mostrar uma lista dos sítios aqui perto</string>
<string name="get_directions" fuzzy="true">OBTER INDICAÇÕES</string>
<string name="get_directions">Obter indicações</string>
<string name="read_article">Ler artigo</string>
<string name="notifications_welcome">Bem-vindo à wiki Wikimedia Commons, %1$s! É um prazer tê-lo aqui.</string>
<string name="notifications_talk_page_message">%1$s deixou uma mensagem na sua página de discussão</string>
<string name="notifications_thank_you_edit">Obrigado por ter realizado uma edição</string>
<string name="notifications_mention">%1$s fez menção a si em %2$s.</string>
<string name="toggle_view_button">Alternar modo de visionamento</string>
<string name="nearby_directions" fuzzy="true">INDICAÇÕES</string>
<string name="nearby_directions">Indicações</string>
<string name="nearby_wikidata">Wikidata</string>
<string name="nearby_wikipedia">Wikipédia</string>
<string name="nearby_commons">Commons</string>
@ -334,7 +334,7 @@
<string name="search_tab_title_media">Multimédia</string>
<string name="search_tab_title_categories">Categorias</string>
<string name="explore_tab_title_featured">Destacadas</string>
<string name="explore_tab_title_mobile" fuzzy="true">CARREGADA VIA TELEMÓVEL</string>
<string name="explore_tab_title_mobile">Carregada via telemóvel</string>
<string name="successful_wikidata_edit">Imagem adicionada a %1$s na wiki Wikidata!</string>
<string name="wikidata_edit_failure">Falha ao atualizar a entidade Wikidata correspondente!</string>
<string name="menu_set_wallpaper">Definir como imagem de fundo</string>
@ -520,7 +520,7 @@
<string name="previous_button_tooltip_message">Clique para reutilizar o título e a descrição que inseriu na sua fotografia anterior e adequá-los à atual</string>
<string name="welcome_do_upload_content_description">Exemplos de imagens que podem ser carregadas na wiki Commons</string>
<string name="welcome_dont_upload_content_description">Exemplos de imagens que não devem ser carregadas</string>
<string name="skip_image" fuzzy="true">SALTAR ESTA IMAGEM</string>
<string name="skip_image">Saltar esta imagem</string>
<string name="download_failed_we_cannot_download_the_file_without_storage_permission">O descarregamento falhou! Não podemos descarregar o ficheiro sem permissão de armazenagem externa.</string>
<string name="manage_exif_tags">Gerir etiquetas EXIF</string>
<string name="manage_exif_tags_summary">Selecionar as etiquetas EXIF a manter nos carregamentos</string>
@ -538,5 +538,14 @@
<string name="upload_cancelled">Carregamento cancelado</string>
<string name="previous_image_title_description_not_found">Não há dados para o título ou descrição da imagem anterior</string>
<string name="dialog_box_text_nomination">Porque deve %1$s ser eliminado?</string>
<string name="review_is_uploaded_by">%1$s é carregada por: %2$s</string>
<string name="default_description_language">Língua de descrição padrão</string>
<string name="delete_helper_ask_spam_selfie">Um autorretrato</string>
<string name="delete_helper_ask_spam_blurry">Desfocada</string>
<string name="delete_helper_ask_spam_nonsense">Sem sentido</string>
<string name="delete_helper_ask_spam_other">Outro</string>
<string name="delete_helper_ask_reason_copyright_press_photo">Fotografia de imprensa</string>
<string name="delete_helper_ask_reason_copyright_internet_photo">Fotografia aleatória da Internet</string>
<string name="delete_helper_ask_reason_copyright_logo">Logótipo</string>
<string name="delete_helper_ask_reason_copyright_other">Outro</string>
</resources>

View file

@ -547,4 +547,12 @@
<string name="dialog_box_text_nomination">Почему %1$s должно быть удалено?</string>
<string name="review_is_uploaded_by">%1$s загружено с помощью: %2$s</string>
<string name="default_description_language">Язык описаний по умолчанию</string>
<string name="delete_helper_ask_spam_selfie">Селфи</string>
<string name="delete_helper_ask_spam_blurry">Размыто</string>
<string name="delete_helper_ask_spam_nonsense">Бессмыслица</string>
<string name="delete_helper_ask_spam_other">Другое</string>
<string name="delete_helper_ask_reason_copyright_press_photo">Новостное фото</string>
<string name="delete_helper_ask_reason_copyright_internet_photo">Случайное фото из интернет</string>
<string name="delete_helper_ask_reason_copyright_logo">Логотип</string>
<string name="delete_helper_ask_reason_copyright_other">Другое</string>
</resources>

View file

@ -532,4 +532,12 @@
<string name="dialog_box_text_nomination">Varför bör %1$s raderas?</string>
<string name="review_is_uploaded_by">%1$s laddas upp av: %2$s</string>
<string name="default_description_language">Standardspråk för beskrivning</string>
<string name="delete_helper_ask_spam_selfie">Ett självporträtt</string>
<string name="delete_helper_ask_spam_blurry">Suddig</string>
<string name="delete_helper_ask_spam_nonsense">Strunt</string>
<string name="delete_helper_ask_spam_other">Annat</string>
<string name="delete_helper_ask_reason_copyright_press_photo">Pressfoto</string>
<string name="delete_helper_ask_reason_copyright_internet_photo">Slumpbild från Internet</string>
<string name="delete_helper_ask_reason_copyright_logo">Logotyp</string>
<string name="delete_helper_ask_reason_copyright_other">Annat</string>
</resources>

View file

@ -15,6 +15,7 @@
* Sayginer
* Sucsuzz
* TmY e12
* ToprakM
* Trockya
* VikipediBilgini
-->

View file

@ -291,17 +291,17 @@
<string name="nearby_location_has_not_changed">Розташування не змінено</string>
<string name="nearby_location_not_available">Місцезнаходження недоступне</string>
<string name="location_permission_rationale_nearby">Потрібний дозвіл для показу списку місць поблизу</string>
<string name="get_directions" fuzzy="true">Показати на мапі у зовнішній програмі</string>
<string name="read_article" fuzzy="true">ЧИТАТИ СТАТТЮ</string>
<string name="get_directions">Показати на мапі у зовнішній програмі</string>
<string name="read_article">Читати статтю</string>
<string name="notifications_welcome">Вітаємо у Вікісховищі, %1$s! Раді вас бачити.</string>
<string name="notifications_talk_page_message">%1$s залишив повідомлення на вашій сторінці обговорення</string>
<string name="notifications_thank_you_edit">Дякуємо за редагування</string>
<string name="notifications_mention">%1$s згадав вас на %2$s.</string>
<string name="toggle_view_button">Перемкнути режим перегляду</string>
<string name="nearby_directions" fuzzy="true">НАПРЯМКИ</string>
<string name="nearby_wikidata" fuzzy="true">ВІКІДАНІ</string>
<string name="nearby_wikipedia" fuzzy="true">ВІКІПЕДІЯ</string>
<string name="nearby_commons" fuzzy="true">ВІКІСХОВИЩЕ</string>
<string name="nearby_directions">Напрямки</string>
<string name="nearby_wikidata">Вікідані</string>
<string name="nearby_wikipedia">Вікіпедія</string>
<string name="nearby_commons">Вікісховище</string>
<string name="about_rate_us">&lt;u&gt;Оцініть нас&lt;/u&gt;</string>
<string name="about_faq">Часті запитання</string>
<string name="welcome_skip_button">Пропустити інструкцію</string>
@ -335,10 +335,10 @@
<string name="provider_searches">Недавні запити пошуку</string>
<string name="error_loading_categories">Сталася помилка під час завантаження категорій.</string>
<string name="error_loading_subcategories">Сталася помилка під час завантаження підкатегорій.</string>
<string name="search_tab_title_media" fuzzy="true">МЕДІАФАЙЛИ</string>
<string name="search_tab_title_categories" fuzzy="true">КАТЕГОРІЇ</string>
<string name="explore_tab_title_featured" fuzzy="true">ОБРАНЕ</string>
<string name="explore_tab_title_mobile" fuzzy="true">ЗАВАНТАЖЕННЯ З МОБІЛЬНОГО</string>
<string name="search_tab_title_media">Медіафайли</string>
<string name="search_tab_title_categories">Категорії</string>
<string name="explore_tab_title_featured">Обране</string>
<string name="explore_tab_title_mobile">Завантаження з мобільного</string>
<string name="successful_wikidata_edit">Зображення успішно додано до сторінки %1$s у Вікіданих!</string>
<string name="wikidata_edit_failure">Не вдалось оновити відповідну сторінку Вікіданих!</string>
<string name="menu_set_wallpaper">Поставити шпалерами екрану</string>
@ -372,13 +372,13 @@
<string name="delete_search_dialog">Вилучити цей пошук?</string>
<string name="search_history_deleted">Історія пошуку очищена</string>
<string name="nominate_delete">Номінувати на вилучення</string>
<string name="delete" fuzzy="true">ВИЛУЧИТИ</string>
<string name="delete">Вилучити</string>
<string name="Achievements">Досягнення</string>
<string name="statistics" fuzzy="true">СТАТИСТИКА</string>
<string name="statistics">Статистика</string>
<string name="statistics_thanks">Отримані подяки</string>
<string name="statistics_featured">Вибрані зображення</string>
<string name="statistics_wikidata_edits">Зображення місць поблизу</string>
<string name="level" fuzzy="true">РІВЕНЬ</string>
<string name="level">Рівень</string>
<string name="images_uploaded">Завантажені зображення</string>
<string name="image_reverts">Не відхилені зображення</string>
<string name="images_used_by_wiki">Використані зображення</string>
@ -526,7 +526,7 @@
<string name="previous_button_tooltip_message">Натисніть, щоб використати назву та опис, які ви вводили для свого попереднього зображення, і змінити їх під поточне</string>
<string name="welcome_do_upload_content_description">Приклади добрих зображень для завантаження у Вікісховище</string>
<string name="welcome_dont_upload_content_description">Приклади зображень, які не слід завантажувати</string>
<string name="skip_image" fuzzy="true">ПРОПУСТИТИ ЦЕ ЗОБРАЖЕННЯ</string>
<string name="skip_image">Пропустити це зображення</string>
<string name="download_failed_we_cannot_download_the_file_without_storage_permission">Завантаження не вдалося. Ми не змогли завантажити файл без доступу до зовнішнього носія.</string>
<string name="manage_exif_tags">Робота з EXIF-тегами</string>
<string name="manage_exif_tags_summary">Вкажіть, які EXIF-теги мають бути збережені при завантаженні файлів</string>
@ -544,5 +544,14 @@
<string name="upload_cancelled">Завантаження скасовано</string>
<string name="previous_image_title_description_not_found">Відсутній заголовок або опис попереднього зображення</string>
<string name="dialog_box_text_nomination">Чому %1$s має бути видалено?</string>
<string name="review_is_uploaded_by">%1$s завантажено з допомогою: %2$s</string>
<string name="default_description_language">Усталена мова описів</string>
<string name="delete_helper_ask_spam_selfie">Селфі</string>
<string name="delete_helper_ask_spam_blurry">Розмито</string>
<string name="delete_helper_ask_spam_nonsense">Безглуздя</string>
<string name="delete_helper_ask_spam_other">Інше</string>
<string name="delete_helper_ask_reason_copyright_press_photo">Фото з новин</string>
<string name="delete_helper_ask_reason_copyright_internet_photo">Випадкове фото з інтернет</string>
<string name="delete_helper_ask_reason_copyright_logo">Логотип</string>
<string name="delete_helper_ask_reason_copyright_other">Інше</string>
</resources>

View file

@ -536,4 +536,12 @@
<string name="dialog_box_text_nomination">為何應刪除%1$s</string>
<string name="review_is_uploaded_by">%1$s 是由 %2$s 所上傳</string>
<string name="default_description_language">預設描述語言</string>
<string name="delete_helper_ask_spam_selfie">自拍</string>
<string name="delete_helper_ask_spam_blurry">糢糊</string>
<string name="delete_helper_ask_spam_nonsense">無意義</string>
<string name="delete_helper_ask_spam_other">其它</string>
<string name="delete_helper_ask_reason_copyright_press_photo">攝影作品</string>
<string name="delete_helper_ask_reason_copyright_internet_photo">來自網路的隨意照片</string>
<string name="delete_helper_ask_reason_copyright_logo">標誌</string>
<string name="delete_helper_ask_reason_copyright_other">其它</string>
</resources>

View file

@ -26,7 +26,7 @@
<string name="preference_category_appearance">外观</string>
<string name="preference_category_general">一般</string>
<string name="preference_category_feedback">反馈</string>
<string name="preference_category_privacy">隐私政策</string>
<string name="preference_category_privacy">隐私</string>
<string name="preference_category_location">位置</string>
<string name="app_name">共享资源</string>
<string name="bullet"></string>
@ -277,6 +277,7 @@
<string name="nominate_deletion">提交删除</string>
<string name="nominated_for_deletion">此图片已被提交删除。</string>
<string name="nominated_see_more">&lt;u&gt;查看网页获取详情&lt;/u&gt;</string>
<string name="nominating_file_for_deletion">提名%1$s删除。</string>
<string name="nominating_for_deletion_status">提名删除:%1$s</string>
<string name="view_browser">在浏览器中预览</string>
<string name="skip_login">忽略</string>
@ -425,8 +426,8 @@
<string name="deletion_reason_bad_for_my_privacy">我意识到这对我的隐私不利</string>
<string name="deletion_reason_no_longer_want_public">我改变了主意,我不想再让公众看到它了</string>
<string name="deletion_reason_not_interesting">对不起,这幅图对百科全书没什么意思</string>
<string name="uploaded_by_myself" fuzzy="true">我自己上传</string>
<string name="no_uploads" fuzzy="true">欢迎使用共享资源!\n\n通过触摸相机或画廊图标以上来上传您的首个媒体。</string>
<string name="uploaded_by_myself">我自己上传在%1$s使用于%2$d个条目。</string>
<string name="no_uploads">欢迎使用共享资源!\n\n通过点击添加按钮以上传您的首个媒体。</string>
<string name="desc_language_Worldwide">世界各地</string>
<string name="desc_language_America">美洲</string>
<string name="desc_language_Europe">欧洲</string>
@ -471,8 +472,11 @@
<string name="review_spam_report_question">该文件不在收录范围内,原因是</string>
<string name="review_c_violation_report_question">该文件侵犯版权,原因是</string>
<string name="review_category_yes_button_text">否,分类错误</string>
<string name="review_category_no_button_text">看起来没问题</string>
<string name="review_spam_yes_button_text">否,不在收录范围内</string>
<string name="review_spam_no_button_text">看起来没问题</string>
<string name="review_copyright_yes_button_text">否,侵犯版权</string>
<string name="review_copyright_no_button_text">看起来没问题</string>
<string name="review_thanks_yes_button_text">是,为什么不呢</string>
<string name="review_thanks_no_button_text">下一张图片</string>
<plurals name="receiving_shared_content">
@ -503,4 +507,10 @@
<string name="no_categories_found">找不到分类</string>
<string name="upload_cancelled">取消上传</string>
<string name="dialog_box_text_nomination">%1$s为何应被删除</string>
<string name="delete_helper_ask_spam_selfie">自拍</string>
<string name="delete_helper_ask_spam_blurry">模糊</string>
<string name="delete_helper_ask_spam_nonsense">无意义</string>
<string name="delete_helper_ask_spam_other">其他</string>
<string name="delete_helper_ask_reason_copyright_logo">标志</string>
<string name="delete_helper_ask_reason_copyright_other">其他</string>
</resources>

View file

@ -560,12 +560,19 @@ Upload your first media by tapping on the add button.</string>
<string name="dialog_box_text_nomination">Why should %1$s be deleted?</string>
<string name="review_is_uploaded_by">%1$s is uploaded by: %2$s</string>
<string name="default_description_language">Default description language</string>
<string name="delete_reason_spam_selfie">A selfie</string>
<string name="delete_reason_spam_blurry">Blurry</string>
<string name="delete_reason_spam_nonsense">Nonsense</string>
<string name="delete_reason_spam_other">Other</string>
<string name="delete_reason_copyright_pressphoto">Press photo</string>
<string name="delete_reason_copyright_internetphoto">Random photo from internet</string>
<string name="delete_reason_copyright_logo">Logo</string>
<string name="delete_reason_copyright_other">Other</string>
<string name="delete_helper_make_deletion_toast">Trying to nominate %1$s for deletion</string>
<string name="delete_helper_show_deletion_title">Nominating for deletion</string>
<string name="delete_helper_show_deletion_title_success">Success</string>
<string name="delete_helper_show_deletion_message_if">Successfully nominated %1$s for deletion.</string>
<string name="delete_helper_show_deletion_title_failed">Failed</string>
<string name="delete_helper_show_deletion_message_else">Could not request deletion.</string>
<string name="delete_helper_ask_spam_selfie">A selfie</string>
<string name="delete_helper_ask_spam_blurry">Blurry</string>
<string name="delete_helper_ask_spam_nonsense">Nonsense</string>
<string name="delete_helper_ask_spam_other">Other</string>
<string name="delete_helper_ask_reason_copyright_press_photo">Press photo</string>
<string name="delete_helper_ask_reason_copyright_internet_photo">Random photo from internet</string>
<string name="delete_helper_ask_reason_copyright_logo">Logo</string>
<string name="delete_helper_ask_reason_copyright_other">Other</string>
<string name="delete_helper_ask_alert_set_positive_button_reason">Because it is</string>
</resources>

View file

@ -0,0 +1,110 @@
package fr.free.nrw.commons.contributions
import android.database.Cursor
import androidx.loader.content.CursorLoader
import androidx.loader.content.Loader
import com.nhaarman.mockito_kotlin.verify
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
/**
* The unit test class for ContributionsPresenter
*/
class ContributionsPresenterTest {
@Mock
internal var repository: ContributionsRepository? = null
@Mock
internal var view: ContributionsContract.View? = null
private var contributionsPresenter: ContributionsPresenter? = null
private lateinit var cursor: Cursor
lateinit var contribution: Contribution
lateinit var loader: Loader<Cursor>
/**
* initial setup
*/
@Before
@Throws(Exception::class)
fun setUp() {
MockitoAnnotations.initMocks(this)
cursor = Mockito.mock(Cursor::class.java)
contribution = Mockito.mock(Contribution::class.java)
contributionsPresenter = ContributionsPresenter(repository)
loader = Mockito.mock(CursorLoader::class.java)
contributionsPresenter?.onAttachView(view)
}
/**
* Test presenter actions onGetContributionFromCursor
*/
@Test
fun testGetContributionFromCursor() {
contributionsPresenter?.getContributionsFromCursor(cursor)
verify(repository)?.getContributionFromCursor(cursor)
}
/**
* Test presenter actions onDeleteContribution
*/
@Test
fun testDeleteContribution() {
contributionsPresenter?.deleteUpload(contribution)
verify(repository)?.deleteContributionFromDB(contribution)
}
/**
* Test presenter actions on loaderFinished and has non zero media objects
*/
@Test
fun testOnLoaderFinishedNonZeroContributions() {
Mockito.`when`(cursor.count).thenReturn(1)
contributionsPresenter?.onLoadFinished(loader, cursor)
verify(view)?.showProgress(false)
verify(view)?.showWelcomeTip(false)
verify(view)?.showNoContributionsUI(false)
verify(view)?.setUploadCount(cursor.count)
}
/**
* Test presenter actions on loaderFinished and has Zero media objects
*/
@Test
fun testOnLoaderFinishedZeroContributions() {
Mockito.`when`(cursor.count).thenReturn(0)
contributionsPresenter?.onLoadFinished(loader, cursor)
verify(view)?.showProgress(false)
verify(view)?.showWelcomeTip(true)
verify(view)?.showNoContributionsUI(true)
}
/**
* Test presenter actions on loader reset
*/
@Test
fun testOnLoaderReset() {
contributionsPresenter?.onLoaderReset(loader)
verify(view)?.showProgress(false)
verify(view)?.showWelcomeTip(true)
verify(view)?.showNoContributionsUI(true)
}
/**
* Test presenter actions on loader change
*/
@Test
fun testOnChanged() {
contributionsPresenter?.onChanged()
verify(view)?.onDataSetChanged()
}
}

View file

@ -1,7 +1,6 @@
package fr.free.nrw.commons.mwapi
import com.google.gson.Gson
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.kvstore.JsonKvStore
@ -344,6 +343,15 @@ class OkHttpJsonApiClientTest {
return it
}
/**
* Check request params with encoded path
*/
private fun assertBasicRequestParameters(server: MockWebServer, method: String,encodedPath: String): RecordedRequest = server.takeRequest().let {
Assert.assertEquals(encodedPath, it.requestUrl.encodedPath())
Assert.assertEquals(method, it.method)
return it
}
/**
* Parse query params
@ -354,4 +362,37 @@ class OkHttpJsonApiClientTest {
}
}
/**
* Test getUploadCount posititive and negative cases
*/
@Test
fun testGetUploadCount(){
//Positive
assertEquals(testBaseCasesAndGetUploadCount(true), 20)
//Negative
assertEquals(testBaseCasesAndGetUploadCount(false), 0)
}
/**
* Test getUploadCount base cases
*/
private fun testBaseCasesAndGetUploadCount(shouldAddResponse: Boolean): Int? {
val mockResponse = MockResponse()
mockResponse.setResponseCode(200)
if(shouldAddResponse) {
val responseBody = "20"
mockResponse.setBody(responseBody)
}
toolsForgeServer.enqueue(mockResponse)
val uploadCount=testObject.getUploadCount("ashishkumar294").blockingGet()
assertBasicRequestParameters(toolsForgeServer, "GET","/uploadsbyuser.py").let { request ->
parseQueryParams(request).let { body ->
Assert.assertEquals("ashishkumar294", body["user"])
}
}
return uploadCount
}
}