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 8293890a46..5160dc496a 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 int getTotalMediaCount() { } 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 fcfcf47eed..22b5a662be 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 int getTotalMediaCount() { 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 1bb846701e..001f817b33 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 int getTotalMediaCount() { 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 7354abee98..2b0741efd0 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 void clear() { */ @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 void retryUpload() { */ @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); } } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContract.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContract.java new file mode 100644 index 0000000000..775fa8efe0 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContract.java @@ -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, + 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 4d68500ca0..12c9ae6023 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.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.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 void onServiceConnected(ComponentName componentName, IBinder binder) { uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder) binder) .getService(); isUploadServiceConnected = true; - if (contributionsListFragment.getAdapter() != null) { - ((ContributionsListAdapter)contributionsListFragment.getAdapter()).setUploadService(uploadService); - } } @Override @@ -127,6 +118,8 @@ public void onServiceDisconnected(ComponentName componentName) { 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 View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 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 View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, }); 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 View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 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 void onAttach(Context context) { } /** - * 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 void onAuthCookieAcquired(Intent uploadServiceIntent) { 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 void onAuthCookieAcquired(Intent uploadServiceIntent) { 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 void onSaveInstanceState(Bundle outState) { @Override public void onResume() { super.onResume(); - + contributionsPresenter.onAttachView(this); firstLocationUpdate = true; locationManager.addLocationListener(this); @@ -624,11 +560,48 @@ private void fetchCampaigns() { } @Override - public void onEvent(String filename) { - for (int i=0;i { -class ContributionsListAdapter extends CursorAdapter { - - 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; - } + private Callback callback; - public void setUploadService(UploadService uploadService) { - this.uploadService = uploadService; + public ContributionsListAdapter(Callback callback) { + this.callback = callback; } + @NonNull @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; + 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 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); - } + position); + holder.init(position, displayableContribution); + } - @Override - public void onClick() { - ContributionsListAdapter.this.openMediaDetail(contribution); - } - }); - views.bindModel(context, displayableContribution); + @Override + public int getItemCount() { + return callback.getNumberOfContributions(); } - /** - * 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); - } + public interface Callback { - } + void retryUpload(Contribution contribution); - /** - * 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); - } + void deleteUpload(Contribution contribution); - } + void openMediaDetail(int contribution); - private void openMediaDetail(Contribution contribution){ - listener.onEvent(contribution.getFilename()); + int getNumberOfContributions(); - } - EventListener listener; + Contribution getContributionForPosition(int position); - public interface EventListener { - void onEvent(String filename); + 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 b5c9cc9f61..4c6d54641d 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 @@ 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())); } } @@ -128,38 +155,69 @@ private void animateFAB(boolean isFabOpen) { } /** - * Responsible to set progress bar invisible and visible - * @param isVisible True when contributions list should be hidden. + * Shows welcome message if user has no contributions yet i.e. new user. */ - public void changeProgressBarVisibility(boolean isVisible) { - this.progressBar.setVisibility(isVisible ? VISIBLE : GONE); + public void showWelcomeTip(boolean shouldShow) { + noContributionsYet.setVisibility(shouldShow ? VISIBLE : GONE); } /** - * Shows welcome message if user has no contributions yet i.e. new user. + * Responsible to set progress bar invisible and visible + * @param shouldShow True when contributions list should be hidden. */ - protected void showWelcomeTip(boolean noContributions) { - noContributionsYet.setVisibility(noContributions ? VISIBLE : GONE); + public void showProgress(boolean shouldShow) { + progressBar.setVisibility(shouldShow ? VISIBLE : GONE); } - public ListAdapter getAdapter() { - return contributionsList.getAdapter(); + public void showNoContributionsUI(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 - */ - public void setAdapter(ListAdapter adapter) { - this.contributionsList.setAdapter(adapter); - - if (ConfigUtils.isBetaFlavour()) { - //TODO: add betaSetUploadCount method - ((ContributionsFragment) getParentFragment()).betaSetUploadCount(adapter.getCount()); + 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(); + } + outState.putString(VISIBLE_ITEM_ID,findIdOfItemWithPosition(lastVisibleItemPosition)); + } + + @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 + */ + private String findIdOfItemWithPosition(int position) { + return callback.getContributionForPosition(position).getContentUri().getLastPathSegment(); + } + } 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 0000000000..6b488e4684 --- /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 0000000000..798b161eb2 --- /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 0000000000..75c9b17261 --- /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 0000000000..c6de9bf196 --- /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 6980579359..56946018c4 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 @@ import android.annotation.SuppressLint; import android.app.AlertDialog; import android.content.Intent; -import android.content.pm.PackageManager; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; @@ -385,13 +384,6 @@ public Fragment getItem(int position) { 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 17c7cfb3b8..28f0beca10 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 @@ 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 @@ private DisplayableContribution(Contribution 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 int getPosition() { 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/di/CommonsApplicationComponent.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java index 72793f2c83..22ac5a40e7 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 @@ 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 2d5dbd3318..8af1663afb 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 int getTotalMediaCount() { 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 void viewPagerNotifyDataSetChanged() { } } - /** - * 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 1bc88c5a84..f734de9457 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 int getTotalMediaCount() { } } - /** - * 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 void viewPagerNotifyDataSetChanged() { } } - /** - * 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 18349f5259..cf22a97c86 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.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.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 static MediaDetailFragment forMedia(int index, boolean editable, boolean 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 void onGlobalLayout() { @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 void onDestroyView() { 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 f34df4687d..dafef06946 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.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.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 void onPageSelected(int i) { 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/upload/FileUtils.java b/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java index 4f04253e91..9e4c9d3340 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.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.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 static boolean recursivelyCreateDirs(String dirPath) { } 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 48d656d466..82b9d69e6a 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 @@ /> - + + + /** + * 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