diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksActivity.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksActivity.java index 8293890a4..5160dc496 100644 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksActivity.java @@ -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) { - - } } diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.java index fcfcf47ee..22b5a662b 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.java @@ -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 */ diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesActivity.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesActivity.java index 1bb846701..001f817b3 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesActivity.java @@ -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 */ diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionViewHolder.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionViewHolder.java index 7354abee9..2b0741efd 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionViewHolder.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionViewHolder.java @@ -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 { +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 { - 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, + LoaderManager.LoaderCallbacks { + + Contribution getContributionsFromCursor(Cursor cursor); + + void deleteUpload(Contribution contribution); + + Media getItemAtPosition(int i); + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java index 4d68500ca..12c9ae602 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java @@ -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, - 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 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 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 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 { -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); } } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java index b5c9cc9f6..f37debe7b 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java @@ -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; + } + } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsLocalDataSource.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsLocalDataSource.java new file mode 100644 index 000000000..6b488e468 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsLocalDataSource.java @@ -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); + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsModule.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsModule.java new file mode 100644 index 000000000..798b161eb --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsModule.java @@ -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); +} diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsPresenter.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsPresenter.java new file mode 100644 index 000000000..75c9b1726 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsPresenter.java @@ -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 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 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 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 not + * 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); + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsRepository.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsRepository.java new file mode 100644 index 000000000..c6de9bf19 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsRepository.java @@ -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); + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java index 7e212c46f..aa15861aa 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java @@ -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 diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/model/DisplayableContribution.java b/app/src/main/java/fr/free/nrw/commons/contributions/model/DisplayableContribution.java index 17c7cfb3b..28f0beca1 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/model/DisplayableContribution.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/model/DisplayableContribution.java @@ -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(); - } } diff --git a/app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.java b/app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.java index fe91010eb..a5d7e7ef7 100644 --- a/app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.java +++ b/app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.java @@ -59,7 +59,7 @@ public class DeleteHelper { * @return */ public Single 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(); } diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java index 72793f2c8..22ac5a40e 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java +++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java @@ -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 { void inject(CommonsApplication application); diff --git a/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java b/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java index 2d5dbd331..8af1663af 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java @@ -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 diff --git a/app/src/main/java/fr/free/nrw/commons/explore/categories/ExploreActivity.java b/app/src/main/java/fr/free/nrw/commons/explore/categories/ExploreActivity.java index 1bc88c5a8..f734de945 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/categories/ExploreActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/categories/ExploreActivity.java @@ -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. diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java index 18349f525..cf22a97c8 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java @@ -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(); } diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java index f34df4687..dafef0694 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java @@ -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) diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java index e829e946f..061351c1f 100644 --- a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java +++ b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java @@ -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; diff --git a/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java b/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java index 4f04253e9..9e4c9d334 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java @@ -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; + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_contributions_list.xml b/app/src/main/res/layout/fragment_contributions_list.xml index 48d656d46..82b9d69e6 100644 --- a/app/src/main/res/layout/fragment_contributions_list.xml +++ b/app/src/main/res/layout/fragment_contributions_list.xml @@ -20,21 +20,16 @@ /> - + لماذا يجب حذف %1$s؟ %1$s رُفِع بواسطة: %2$s لغة الوصف الافتراضية + تجري محاولة ترشيح %1$s للحذف + ترشيح للحذف + نجاح + تم بنجاح ترشيح %1$s للحذف. + فشل + لا يمكن طلب الحذف. + سيلفي + ضبابية + كلام فارغ + أخرى + اضغط على الصورة + صورة عشوائية من الإنترنت + شعار + أخرى + لأنها diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index fb0697df6..b252247ee 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -370,9 +370,16 @@ Vis ulæste Der opstod en fejl under udvælgelse af billeder Vent venligst… - Spring over dette billede + Spring over dette billede Ophavsret Kameramodel Serienumre Del app via... + Ingen kategorier blev fundet + Et selvportræt + Sløret + Vrøvl + Andet + Vilkårligt billede fra internettet + Andet diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 968f56107..fc9775f29 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -11,6 +11,7 @@ * ManosHacker * Nikosgranturismogt * Nikosguard +* Panos78 * Protnet * Tgkarounos --> @@ -268,17 +269,17 @@ Ο εντοπισμός δεν έχει αλλάξει. Ο τόπος δεν είναι διαθέσιμος. Απαιτείται άδεια για την εμφάνιση λίστας κοντινών σημείων - Λάβετε κατευθύνσεις - Ανάγνωση άρθρου + Λήψη κατευθύνσεων + Ανάγνωση άρθρου Καλωσήρθατε στο Wikimedia Commons, %1$s! Είμαστε χαρούμενοι που είστε εδώ. Ο %1$s άφησε ένα μήνυμα στην σελίδα συζήτησής σας Ευχαριστούμε που κάνατε μια επεξεργασία Ο %1$s σας ανέφερε στο %2$s. Μεταβολή προβολής - Κατευθύνσεις - Βικιδεδομένα - Βικιπαίδεια - Κοινά + Κατευθύνσεις + Δεδομένα wiki + Βικιπαίδεια + Κοινά <u>Βαθμολογήστε μας</u> <u>Συχνές ερωτήσεις</u> Παράβλεψη εισαγωγής @@ -310,8 +311,8 @@ Πρόσφατα αναζητημένα ερωτήματα Συνέβη σφάλμα κατά τη φόρτωση κατηγοριών. Συνέβη σφάλμα κατά τη φόρτωση υποκατηγοριών. - Μέσα ενημέρωσης - Κατηγορίες + Μέσα + Κατηγορίες Η εικόνα προστέθηκε επιτυχώς στο %1$s στο Wikidata! Αποτυχία ενημέρωσης της αντιστοιχούσας οντότητας του Wikidata! Ρύθμιση ως ταπετσαρία @@ -344,10 +345,10 @@ Το ιστορικό αναζήτησης διεγράφη Ορίστε για Αφαίρεση Κατορθώματα - Στατιστικά + Στατιστικά Ευχαριστίες που έχουν ληφθεί Προβεβλημμένες εικόνες - Επίπεδο + Επίπεδο Εικόνες που ανέβηκαν Εικόνες που δεν ανεστράφησαν Εικόνες που χρησιμοποιήθηκαν diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index c8f5c604d..5cea16d2b 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -1,5 +1,6 @@ Explorar @@ -188,6 +189,7 @@ Pidiendo permiso de almacenamiento Permiso necesario: lectura de almacenamiento externo. La aplicación no puede acceder a la galería sin él. Permiso necesario: escritura en almacenamiento externo. La aplicación no puede acceder a la cámara o la galería sin este. + Pidiendo Permiso de Ubicación Permiso opcional: obtener la ubicación actual para sugerir categorías Aceptar Lugares cercanos @@ -528,6 +530,7 @@ Omitir esta imagen La descarga falló!. No podemos descargar el archivo sin el permiso de almacenamiento externo. Gestionar etiquetas EXIF + Seleccionar qué etiquetas EXIF se mantendrán en la subida Autor Derechos de autor Ubicación @@ -535,9 +538,21 @@ Modelo de lente Números de serie Programa + Sube fotos a Wikimedia Commons desde tu celular, descarga la aplicación de Commons: %1$s + Compartir la aplicación vía... Información de la imagen No se encontró ninguna categoría Se canceló la carga No hay datos sobre el título o la descripción anteriores de la imagen ¿Por qué debe borrarse %1$s? + %1$s fue subida por: %2$s + Idioma por defecto de la descripción + Un autorretrato + Borrosa + Sinsentido + Otra + Fotografía para prensa + Foto cualquiera tomada de internet + Logo + Otra diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 098d226f1..f19522054 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -2,6 +2,7 @@ @@ -545,6 +547,16 @@ Téléversement annulé Il n’y a pas de données pour le titre ou la description de l’image précédente Pourquoi %1$s devrait-il être supprimé ? - %1$s est téléversé par : %2$s + %1$s est téléversé par&nbsp;: %2$s Langue de description par défaut + Tentative de signalement de %1$s pour la suppression + Échec + Un selfie + Flou + Non-sens + Autre + Photo de presse + Photo aléatoire sur internet + Logo + Autre diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 52ba80198..98189acce 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -481,4 +481,7 @@ Perché %1$s dovrebbe essere cancellato? %1$s è stato caricato da: %2$s Lingua predefinita descrizione + Altro + Logo + Altro diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index ce10669c6..91feabf1e 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -1,5 +1,6 @@ diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 701048996..87c8fa0bf 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -291,17 +291,17 @@ Розташування не змінено Місцезнаходження недоступне Потрібний дозвіл для показу списку місць поблизу - Показати на мапі у зовнішній програмі - ЧИТАТИ СТАТТЮ + Показати на мапі у зовнішній програмі + Читати статтю Вітаємо у Вікісховищі, %1$s! Раді вас бачити. %1$s залишив повідомлення на вашій сторінці обговорення Дякуємо за редагування %1$s згадав вас на %2$s. Перемкнути режим перегляду - НАПРЯМКИ - ВІКІДАНІ - ВІКІПЕДІЯ - ВІКІСХОВИЩЕ + Напрямки + Вікідані + Вікіпедія + Вікісховище <u>Оцініть нас</u> Часті запитання Пропустити інструкцію @@ -335,10 +335,10 @@ Недавні запити пошуку Сталася помилка під час завантаження категорій. Сталася помилка під час завантаження підкатегорій. - МЕДІАФАЙЛИ - КАТЕГОРІЇ - ОБРАНЕ - ЗАВАНТАЖЕННЯ З МОБІЛЬНОГО + Медіафайли + Категорії + Обране + Завантаження з мобільного Зображення успішно додано до сторінки %1$s у Вікіданих! Не вдалось оновити відповідну сторінку Вікіданих! Поставити шпалерами екрану @@ -372,13 +372,13 @@ Вилучити цей пошук? Історія пошуку очищена Номінувати на вилучення - ВИЛУЧИТИ + Вилучити Досягнення - СТАТИСТИКА + Статистика Отримані подяки Вибрані зображення Зображення місць поблизу - РІВЕНЬ + Рівень Завантажені зображення Не відхилені зображення Використані зображення @@ -526,7 +526,7 @@ Натисніть, щоб використати назву та опис, які ви вводили для свого попереднього зображення, і змінити їх під поточне Приклади добрих зображень для завантаження у Вікісховище Приклади зображень, які не слід завантажувати - ПРОПУСТИТИ ЦЕ ЗОБРАЖЕННЯ + Пропустити це зображення Завантаження не вдалося. Ми не змогли завантажити файл без доступу до зовнішнього носія. Робота з EXIF-тегами Вкажіть, які EXIF-теги мають бути збережені при завантаженні файлів @@ -544,5 +544,14 @@ Завантаження скасовано Відсутній заголовок або опис попереднього зображення Чому %1$s має бути видалено? + %1$s завантажено з допомогою: %2$s Усталена мова описів + Селфі + Розмито + Безглуздя + Інше + Фото з новин + Випадкове фото з інтернет + Логотип + Інше diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index cbcc197b2..a8bd0c696 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -536,4 +536,12 @@ 為何應刪除%1$s? %1$s 是由 %2$s 所上傳 預設描述語言 + 自拍 + 糢糊 + 無意義 + 其它 + 攝影作品 + 來自網路的隨意照片 + 標誌 + 其它 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 4385be5b4..802ecc0f4 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -26,7 +26,7 @@ 外观 一般 反馈 - 隐私政策 + 隐私 位置 共享资源 @@ -277,6 +277,7 @@ 提交删除 此图片已被提交删除。 <u>查看网页获取详情</u> + 提名%1$s删除。 提名删除:%1$s 在浏览器中预览 忽略 @@ -425,8 +426,8 @@ 我意识到这对我的隐私不利 我改变了主意,我不想再让公众看到它了 对不起,这幅图对百科全书没什么意思 - 我自己上传的在 - 欢迎使用共享资源!\n\n通过触摸相机或画廊图标以上来上传您的首个媒体。 + 由我自己上传在%1$s,使用于%2$d个条目。 + 欢迎使用共享资源!\n\n通过点击添加按钮以上传您的首个媒体。 世界各地 美洲 欧洲 @@ -471,8 +472,11 @@ 该文件不在收录范围内,原因是 该文件侵犯版权,原因是 否,分类错误 + 看起来没问题 否,不在收录范围内 + 看起来没问题 否,侵犯版权 + 看起来没问题 是,为什么不呢 下一张图片 @@ -503,4 +507,10 @@ 找不到分类 取消上传 %1$s为何应被删除? + 自拍 + 模糊 + 无意义 + 其他 + 标志 + 其他 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 08cad0620..9fe8836aa 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -560,12 +560,19 @@ Upload your first media by tapping on the add button. Why should %1$s be deleted? %1$s is uploaded by: %2$s Default description language - A selfie - Blurry - Nonsense - Other - Press photo - Random photo from internet - Logo - Other + Trying to nominate %1$s for deletion + Nominating for deletion + Success + Successfully nominated %1$s for deletion. + Failed + Could not request deletion. + A selfie + Blurry + Nonsense + Other + Press photo + Random photo from internet + Logo + Other + Because it is diff --git a/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionsPresenterTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionsPresenterTest.kt new file mode 100644 index 000000000..97befe98c --- /dev/null +++ b/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionsPresenterTest.kt @@ -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 + + /** + * 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() + } + + +} \ No newline at end of file diff --git a/app/src/test/kotlin/fr/free/nrw/commons/mwapi/OkHttpJsonApiClientTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/mwapi/OkHttpJsonApiClientTest.kt index e9766e5ee..822277028 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/mwapi/OkHttpJsonApiClientTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/mwapi/OkHttpJsonApiClientTest.kt @@ -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 + } + } \ No newline at end of file