mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Feature/refactor contributions (#3046)
* * Refactored ContributionsListFragment to use RecyclerView * Added ContributionsPresenter * Extracted out the cursor to presenter * Probable fix for #3028 * Improved the logic for cache in ContributionViewHolder * Some more refactoring * While displaying images in ContributionsList, check if status is not completed && local uri exists, use that uri to show image * typo correction in LocalDataSource * Fixed formatting in ContributionsPresenter * retain adapter position when orientation changes * retain child position with its id * Made ContributionViewHolder not implement ViewHolder * Code formatting, review suggested changes * initialise the rv layout managers only when needed * added test cases for ContributionPresenter * removed not needed semi colon * added more java docs and code formatting
This commit is contained in:
parent
108e28c89a
commit
60b1eb1957
22 changed files with 814 additions and 592 deletions
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,35 +1,34 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.collection.LruCache;
|
||||
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
import fr.free.nrw.commons.MediaDataExtractor;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.ViewHolder;
|
||||
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
|
||||
import fr.free.nrw.commons.contributions.model.DisplayableContribution;
|
||||
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||
import fr.free.nrw.commons.upload.FileUtils;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ContributionViewHolder implements ViewHolder<DisplayableContribution> {
|
||||
public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final Callback callback;
|
||||
@BindView(R.id.contributionImage)
|
||||
SimpleDraweeView imageView;
|
||||
@BindView(R.id.contributionTitle) TextView titleView;
|
||||
|
|
@ -47,15 +46,18 @@ public class ContributionViewHolder implements ViewHolder<DisplayableContributio
|
|||
|
||||
private DisplayableContribution contribution;
|
||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
private int position;
|
||||
|
||||
ContributionViewHolder(View parent) {
|
||||
ContributionViewHolder(View parent, Callback callback) {
|
||||
super(parent);
|
||||
ButterKnife.bind(this, parent);
|
||||
this.callback=callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindModel(Context context, DisplayableContribution contribution) {
|
||||
ApplicationlessInjection.getInstance(context)
|
||||
public void init(int position, DisplayableContribution contribution) {
|
||||
ApplicationlessInjection.getInstance(itemView.getContext())
|
||||
.getCommonsApplicationComponent().inject(this);
|
||||
this.position=position;
|
||||
this.contribution = contribution;
|
||||
fetchAndDisplayThumbnail(contribution);
|
||||
titleView.setText(contribution.getDisplayTitle());
|
||||
|
|
@ -104,19 +106,39 @@ public class ContributionViewHolder implements ViewHolder<DisplayableContributio
|
|||
* @param contribution
|
||||
*/
|
||||
private void fetchAndDisplayThumbnail(DisplayableContribution contribution) {
|
||||
if (!StringUtils.isBlank(thumbnailCache.get(contribution.getFilename()))) {
|
||||
imageView.setImageURI(thumbnailCache.get(contribution.getFilename()));
|
||||
String keyForLRUCache = getKeyForLRUCache(contribution.getContentUri());
|
||||
String cacheUrl = thumbnailCache.get(keyForLRUCache);
|
||||
if (!StringUtils.isBlank(cacheUrl)) {
|
||||
imageView.setImageURI(cacheUrl);
|
||||
return;
|
||||
}
|
||||
Timber.d("Fetching thumbnail for %s", contribution.getFilename());
|
||||
Disposable disposable = mediaDataExtractor.getMediaFromFileName(contribution.getFilename())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(media -> {
|
||||
thumbnailCache.put(contribution.getFilename(), media.getThumbUrl());
|
||||
imageView.setImageURI(media.getThumbUrl());
|
||||
});
|
||||
compositeDisposable.add(disposable);
|
||||
|
||||
imageView.setBackground(null);
|
||||
if ((contribution.getState() != Contribution.STATE_COMPLETED) && FileUtils.fileExists(
|
||||
contribution.getLocalUri())) {
|
||||
imageView.setImageURI(contribution.getLocalUri());
|
||||
} else {
|
||||
Timber.d("Fetching thumbnail for %s", contribution.getFilename());
|
||||
Disposable disposable = mediaDataExtractor
|
||||
.getMediaFromFileName(contribution.getFilename())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(media -> {
|
||||
thumbnailCache.put(keyForLRUCache, media.getThumbUrl());
|
||||
imageView.setImageURI(media.getThumbUrl());
|
||||
});
|
||||
compositeDisposable.add(disposable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns image key for the LRU cache, basically the id of the image, (the content uri is the ""+/id)
|
||||
* @param contentUri
|
||||
* @return
|
||||
*/
|
||||
private String getKeyForLRUCache(Uri contentUri) {
|
||||
return contentUri.getLastPathSegment();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
|
|
@ -128,10 +150,7 @@ public class ContributionViewHolder implements ViewHolder<DisplayableContributio
|
|||
*/
|
||||
@OnClick(R.id.retryButton)
|
||||
public void retryUpload() {
|
||||
DisplayableContribution.ContributionActions actions = contribution.getContributionActions();
|
||||
if (actions != null) {
|
||||
actions.retryUpload();
|
||||
}
|
||||
callback.retryUpload(contribution);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -139,17 +158,11 @@ public class ContributionViewHolder implements ViewHolder<DisplayableContributio
|
|||
*/
|
||||
@OnClick(R.id.cancelButton)
|
||||
public void deleteUpload() {
|
||||
DisplayableContribution.ContributionActions actions = contribution.getContributionActions();
|
||||
if (actions != null) {
|
||||
actions.deleteUpload();
|
||||
}
|
||||
callback.deleteUpload(contribution);
|
||||
}
|
||||
|
||||
@OnClick(R.id.contributionImage)
|
||||
public void imageClicked(){
|
||||
DisplayableContribution.ContributionActions actions = contribution.getContributionActions();
|
||||
if (actions != null) {
|
||||
actions.onClick();
|
||||
}
|
||||
callback.openMediaDetail(position);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import android.database.Cursor;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import fr.free.nrw.commons.BasePresenter;
|
||||
import fr.free.nrw.commons.Media;
|
||||
|
||||
/**
|
||||
* The contract for Contributions View & Presenter
|
||||
*/
|
||||
public class ContributionsContract {
|
||||
|
||||
public interface View {
|
||||
|
||||
void showWelcomeTip(boolean numberOfUploads);
|
||||
|
||||
void showProgress(boolean shouldShow);
|
||||
|
||||
void showNoContributionsUI(boolean shouldShow);
|
||||
|
||||
void setUploadCount(int count);
|
||||
|
||||
void onDataSetChanged();
|
||||
}
|
||||
|
||||
public interface UserActionListener extends BasePresenter<ContributionsContract.View>,
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
Contribution getContributionsFromCursor(Cursor cursor);
|
||||
|
||||
void deleteUpload(Contribution contribution);
|
||||
|
||||
Media getItemAtPosition(int i);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +1,28 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
|
||||
import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
|
||||
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.database.Cursor;
|
||||
import android.database.DataSetObserver;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Adapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.cursoradapter.widget.CursorAdapter;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.CursorLoader;
|
||||
import androidx.loader.content.Loader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.HandlerService;
|
||||
|
|
@ -40,12 +32,15 @@ import fr.free.nrw.commons.campaigns.Campaign;
|
|||
import fr.free.nrw.commons.campaigns.CampaignView;
|
||||
import fr.free.nrw.commons.campaigns.CampaignsPresenter;
|
||||
import fr.free.nrw.commons.campaigns.ICampaignsView;
|
||||
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
|
||||
import fr.free.nrw.commons.contributions.ContributionsListFragment.SourceRefresher;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||
import fr.free.nrw.commons.location.LocationUpdateListener;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment.MediaDetailProvider;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
||||
import fr.free.nrw.commons.nearby.NearbyController;
|
||||
|
|
@ -55,29 +50,26 @@ import fr.free.nrw.commons.settings.Prefs;
|
|||
import fr.free.nrw.commons.upload.UploadService;
|
||||
import fr.free.nrw.commons.utils.ConfigUtils;
|
||||
import fr.free.nrw.commons.utils.DialogUtil;
|
||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||
import fr.free.nrw.commons.utils.PermissionUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.util.ArrayList;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static fr.free.nrw.commons.contributions.ContributionDao.Table.ALL_FIELDS;
|
||||
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI;
|
||||
import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
|
||||
import static fr.free.nrw.commons.settings.Prefs.UPLOADS_SHOWING;
|
||||
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
|
||||
|
||||
public class ContributionsFragment
|
||||
extends CommonsDaggerSupportFragment
|
||||
implements LoaderManager.LoaderCallbacks<Cursor>,
|
||||
MediaDetailPagerFragment.MediaDetailProvider,
|
||||
FragmentManager.OnBackStackChangedListener,
|
||||
ContributionsListFragment.SourceRefresher,
|
||||
LocationUpdateListener,
|
||||
ICampaignsView,
|
||||
ContributionsListAdapter.EventListener{
|
||||
implements
|
||||
MediaDetailProvider,
|
||||
OnBackStackChangedListener,
|
||||
SourceRefresher,
|
||||
LocationUpdateListener,
|
||||
ICampaignsView, ContributionsContract.View {
|
||||
@Inject @Named("default_preferences") JsonKvStore store;
|
||||
@Inject ContributionDao contributionDao;
|
||||
@Inject MediaWikiApi mediaWikiApi;
|
||||
|
|
@ -99,6 +91,8 @@ public class ContributionsFragment
|
|||
@BindView(R.id.card_view_nearby) public NearbyNotificationCardView nearbyNotificationCardView;
|
||||
@BindView(R.id.campaigns_view) CampaignView campaignView;
|
||||
|
||||
@Inject ContributionsPresenter contributionsPresenter;
|
||||
|
||||
private LatLng curLatLng;
|
||||
|
||||
private boolean firstLocationUpdate = true;
|
||||
|
|
@ -116,9 +110,6 @@ public class ContributionsFragment
|
|||
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder) binder)
|
||||
.getService();
|
||||
isUploadServiceConnected = true;
|
||||
if (contributionsListFragment.getAdapter() != null) {
|
||||
((ContributionsListAdapter)contributionsListFragment.getAdapter()).setUploadService(uploadService);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -127,6 +118,8 @@ public class ContributionsFragment
|
|||
Timber.e(new RuntimeException("UploadService died but the rest of the process did not!"));
|
||||
}
|
||||
};
|
||||
private boolean shouldShowMediaDetailsFragment;
|
||||
private int numberOfContributions;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
|
|
@ -140,6 +133,7 @@ public class ContributionsFragment
|
|||
View view = inflater.inflate(R.layout.fragment_contributions, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
presenter.onAttachView(this);
|
||||
contributionsPresenter.onAttachView(this);
|
||||
campaignView.setVisibility(View.GONE);
|
||||
checkBoxView = View.inflate(getActivity(), R.layout.nearby_permission_dialog, null);
|
||||
checkBox = (CheckBox) checkBoxView.findViewById(R.id.never_ask_again);
|
||||
|
|
@ -151,16 +145,19 @@ public class ContributionsFragment
|
|||
});
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
mediaDetailPagerFragment = (MediaDetailPagerFragment)getChildFragmentManager().findFragmentByTag(MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
|
||||
contributionsListFragment = (ContributionsListFragment) getChildFragmentManager().findFragmentByTag(CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
mediaDetailPagerFragment = (MediaDetailPagerFragment) getChildFragmentManager()
|
||||
.findFragmentByTag(MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
|
||||
contributionsListFragment = (ContributionsListFragment) getChildFragmentManager()
|
||||
.findFragmentByTag(CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
shouldShowMediaDetailsFragment = savedInstanceState.getBoolean("mediaDetailsVisible");
|
||||
}
|
||||
|
||||
if (savedInstanceState.getBoolean("mediaDetailsVisible")) {
|
||||
setMediaDetailPagerFragment();
|
||||
} else {
|
||||
setContributionsListFragment();
|
||||
}
|
||||
} else {
|
||||
setContributionsListFragment();
|
||||
initFragments();
|
||||
|
||||
if(shouldShowMediaDetailsFragment){
|
||||
showMediaDetailPagerFragment();
|
||||
}else{
|
||||
showContributionsListFragment();
|
||||
}
|
||||
|
||||
if (!ConfigUtils.isBetaFlavour()) {
|
||||
|
|
@ -191,6 +188,65 @@ public class ContributionsFragment
|
|||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialose the ContributionsListFragment and MediaDetailPagerFragment fragment
|
||||
*/
|
||||
private void initFragments() {
|
||||
if (null == contributionsListFragment) {
|
||||
contributionsListFragment = new ContributionsListFragment();
|
||||
}
|
||||
|
||||
contributionsListFragment.setCallback(new Callback() {
|
||||
@Override
|
||||
public void retryUpload(Contribution contribution) {
|
||||
ContributionsFragment.this.retryUpload(contribution);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteUpload(Contribution contribution) {
|
||||
contributionsPresenter.deleteUpload(contribution);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openMediaDetail(int position) {
|
||||
showDetail(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumberOfContributions() {
|
||||
return numberOfContributions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Contribution getContributionForPosition(int position) {
|
||||
return (Contribution) contributionsPresenter.getItemAtPosition(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int findItemPositionWithId(String id) {
|
||||
return contributionsPresenter.getChildPositionWithId(id);
|
||||
}
|
||||
});
|
||||
|
||||
if(null==mediaDetailPagerFragment){
|
||||
mediaDetailPagerFragment=new MediaDetailPagerFragment();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replaces the root frame layout with the given fragment
|
||||
* @param fragment
|
||||
* @param tag
|
||||
*/
|
||||
private void showFragment(Fragment fragment, String tag) {
|
||||
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
|
||||
transaction.replace(R.id.root_frame, fragment, tag);
|
||||
transaction.addToBackStack(CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
transaction.commit();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
|
|
@ -209,132 +265,45 @@ public class ContributionsFragment
|
|||
}
|
||||
|
||||
/**
|
||||
* Replace FrameLayout with ContributionsListFragment, user will see contributions list.
|
||||
* Creates new one if null.
|
||||
* Replace FrameLayout with ContributionsListFragment, user will see contributions list. Creates
|
||||
* new one if null.
|
||||
*/
|
||||
public void setContributionsListFragment() {
|
||||
public void showContributionsListFragment() {
|
||||
// show tabs on contribution list is visible
|
||||
((MainActivity)getActivity()).showTabs();
|
||||
((MainActivity) getActivity()).showTabs();
|
||||
// show nearby card view on contributions list is visible
|
||||
if (nearbyNotificationCardView != null) {
|
||||
if (store.getBoolean("displayNearbyCardView", true)) {
|
||||
if (nearbyNotificationCardView.cardViewVisibilityState == NearbyNotificationCardView.CardViewVisibilityState.READY) {
|
||||
if (nearbyNotificationCardView.cardViewVisibilityState
|
||||
== NearbyNotificationCardView.CardViewVisibilityState.READY) {
|
||||
nearbyNotificationCardView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
nearbyNotificationCardView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
// Create if null
|
||||
if (getContributionsListFragment() == null) {
|
||||
contributionsListFragment = new ContributionsListFragment();
|
||||
}
|
||||
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
|
||||
// When this container fragment is created, we fill it with our ContributionsListFragment
|
||||
transaction.replace(R.id.root_frame, contributionsListFragment, CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
transaction.addToBackStack(CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
transaction.commit();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
showFragment(contributionsListFragment, CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace FrameLayout with MediaDetailPagerFragment, user will see details of selected media.
|
||||
* Creates new one if null.
|
||||
*/
|
||||
public void setMediaDetailPagerFragment() {
|
||||
public void showMediaDetailPagerFragment() {
|
||||
// hide tabs on media detail view is visible
|
||||
((MainActivity)getActivity()).hideTabs();
|
||||
// hide nearby card view on media detail is visible
|
||||
nearbyNotificationCardView.setVisibility(View.GONE);
|
||||
|
||||
// Create if null
|
||||
if (getMediaDetailPagerFragment() == null) {
|
||||
mediaDetailPagerFragment = new MediaDetailPagerFragment();
|
||||
}
|
||||
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
|
||||
// When this container fragment is created, we fill it with our MediaDetailPagerFragment
|
||||
//transaction.addToBackStack(null);
|
||||
transaction.add(R.id.root_frame, mediaDetailPagerFragment, MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
|
||||
transaction.addToBackStack(MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
|
||||
transaction.commit();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
showFragment(mediaDetailPagerFragment,MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Just getter method of ContributionsListFragment child of ContributionsFragment
|
||||
* @return contributionsListFragment, if any created
|
||||
*/
|
||||
public ContributionsListFragment getContributionsListFragment() {
|
||||
return contributionsListFragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Just getter method of MediaDetailPagerFragment child of ContributionsFragment
|
||||
* @return mediaDetailsFragment, if any created
|
||||
*/
|
||||
public MediaDetailPagerFragment getMediaDetailPagerFragment() {
|
||||
return mediaDetailPagerFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
((MainActivity)getActivity()).initBackButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
||||
int uploads = store.getInt(UPLOADS_SHOWING, 100);
|
||||
return new CursorLoader(getActivity(), BASE_URI, //TODO find out the reason we pass activity here
|
||||
ALL_FIELDS, "", null,
|
||||
ContributionDao.CONTRIBUTION_SORT + "LIMIT " + uploads);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
|
||||
if (contributionsListFragment != null) {
|
||||
contributionsListFragment.changeProgressBarVisibility(false);
|
||||
|
||||
if (contributionsListFragment.getAdapter() == null) {
|
||||
contributionsListFragment.setAdapter(new ContributionsListAdapter(getActivity().getApplicationContext(),
|
||||
cursor, 0, contributionDao, this));
|
||||
} else {
|
||||
((CursorAdapter) contributionsListFragment.getAdapter()).swapCursor(cursor);
|
||||
}
|
||||
|
||||
contributionsListFragment.showWelcomeTip(cursor.getCount() == 0);
|
||||
notifyAndMigrateDataSetObservers();
|
||||
((ContributionsListAdapter)contributionsListFragment.getAdapter()).setUploadService(uploadService);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> cursorLoader) {
|
||||
((CursorAdapter) contributionsListFragment.getAdapter()).swapCursor(null);
|
||||
}
|
||||
|
||||
private void notifyAndMigrateDataSetObservers() {
|
||||
Adapter adapter = contributionsListFragment.getAdapter();
|
||||
|
||||
// First, move the observers over to the adapter now that we have it.
|
||||
for (DataSetObserver observer : observersWaitingForLoad) {
|
||||
adapter.registerDataSetObserver(observer);
|
||||
}
|
||||
observersWaitingForLoad.clear();
|
||||
|
||||
// Now fire off a first notification...
|
||||
for (DataSetObserver observer : observersWaitingForLoad) {
|
||||
observer.onChanged();
|
||||
}
|
||||
|
||||
if (ConfigUtils.isBetaFlavour()) {
|
||||
betaSetUploadCount(getTotalMediaCount());
|
||||
} else {
|
||||
setUploadCount();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when onAuthCookieAcquired is called on authenticated parent activity
|
||||
* @param uploadServiceIntent
|
||||
|
|
@ -345,7 +314,7 @@ public class ContributionsFragment
|
|||
if (getActivity() != null) { // If fragment is attached to parent activity
|
||||
getActivity().bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
|
||||
isUploadServiceConnected = true;
|
||||
getActivity().getSupportLoaderManager().initLoader(0, null, ContributionsFragment.this);
|
||||
getActivity().getSupportLoaderManager().initLoader(0, null, contributionsPresenter);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -358,57 +327,24 @@ public class ContributionsFragment
|
|||
public void showDetail(int i) {
|
||||
if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment.isVisible()) {
|
||||
mediaDetailPagerFragment = new MediaDetailPagerFragment();
|
||||
setMediaDetailPagerFragment();
|
||||
showMediaDetailPagerFragment();
|
||||
}
|
||||
mediaDetailPagerFragment.showImage(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshSource() {
|
||||
getActivity().getSupportLoaderManager().restartLoader(0, null, this);
|
||||
getActivity().getSupportLoaderManager().restartLoader(0, null, contributionsPresenter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Media getMediaAtPosition(int i) {
|
||||
if (contributionsListFragment.getAdapter() == null) {
|
||||
// not yet ready to return data
|
||||
return null;
|
||||
} else {
|
||||
return contributionDao.fromCursor((Cursor) contributionsListFragment.getAdapter().getItem(i));
|
||||
}
|
||||
return contributionsPresenter.getItemAtPosition(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalMediaCount() {
|
||||
if (contributionsListFragment.getAdapter() == null) {
|
||||
return 0;
|
||||
}
|
||||
return contributionsListFragment.getAdapter().getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDatasetChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDataSetObserver(DataSetObserver observer) {
|
||||
Adapter adapter = contributionsListFragment.getAdapter();
|
||||
if (adapter == null) {
|
||||
observersWaitingForLoad.add(observer);
|
||||
} else {
|
||||
adapter.registerDataSetObserver(observer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||
Adapter adapter = contributionsListFragment.getAdapter();
|
||||
if (adapter == null) {
|
||||
observersWaitingForLoad.remove(observer);
|
||||
} else {
|
||||
adapter.unregisterDataSetObserver(observer);
|
||||
}
|
||||
return numberOfContributions;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
|
|
@ -454,7 +390,7 @@ public class ContributionsFragment
|
|||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
contributionsPresenter.onAttachView(this);
|
||||
firstLocationUpdate = true;
|
||||
locationManager.addLocationListener(this);
|
||||
|
||||
|
|
@ -624,11 +560,48 @@ public class ContributionsFragment
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(String filename) {
|
||||
for (int i=0;i<getTotalMediaCount();i++){
|
||||
if (getMediaAtPosition(i).getFilename().equals(filename))
|
||||
showDetail(i);
|
||||
public void showWelcomeTip(boolean shouldShow) {
|
||||
contributionsListFragment.showWelcomeTip(shouldShow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showProgress(boolean shouldShow) {
|
||||
contributionsListFragment.showProgress(shouldShow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showNoContributionsUI(boolean shouldShow) {
|
||||
contributionsListFragment.showNoContributionsUI(shouldShow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUploadCount(int count) {
|
||||
this.numberOfContributions=count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataSetChanged() {
|
||||
contributionsListFragment.onDataSetChanged();
|
||||
mediaDetailPagerFragment.onDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry upload when it is failed
|
||||
*
|
||||
* @param contribution contribution to be retried
|
||||
*/
|
||||
private void retryUpload(Contribution contribution) {
|
||||
if (NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
if (contribution.getState() == STATE_FAILED && null != uploadService) {
|
||||
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, contribution);
|
||||
Timber.d("Restarting for %s", contribution.toString());
|
||||
} else {
|
||||
Timber.d("Skipping re-upload for non-failed %s", contribution.toString());
|
||||
}
|
||||
} else {
|
||||
ViewUtil.showLongToast(getContext(), R.string.this_function_needs_network_connection);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,121 +1,55 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.cursoradapter.widget.CursorAdapter;
|
||||
import android.database.DataSetObserver;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.model.DisplayableContribution;
|
||||
import fr.free.nrw.commons.upload.UploadService;
|
||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
|
||||
public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionViewHolder> {
|
||||
|
||||
class ContributionsListAdapter extends CursorAdapter {
|
||||
private Callback callback;
|
||||
|
||||
private final ContributionDao contributionDao;
|
||||
private UploadService uploadService;
|
||||
|
||||
public ContributionsListAdapter(Context context,
|
||||
Cursor c,
|
||||
int flags,
|
||||
ContributionDao contributionDao, EventListener listener) {
|
||||
super(context, c, flags);
|
||||
this.contributionDao = contributionDao;
|
||||
this.listener=listener;
|
||||
public ContributionsListAdapter(Callback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public void setUploadService(UploadService uploadService) {
|
||||
this.uploadService = uploadService;
|
||||
@NonNull
|
||||
@Override
|
||||
public ContributionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
ContributionViewHolder viewHolder = new ContributionViewHolder(
|
||||
LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.layout_contribution, parent, false), callback);
|
||||
return viewHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
|
||||
View parent = LayoutInflater.from(context)
|
||||
.inflate(R.layout.layout_contribution, viewGroup, false);
|
||||
parent.setTag(new ContributionViewHolder(parent));
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
final ContributionViewHolder views = (ContributionViewHolder)view.getTag();
|
||||
final Contribution contribution = contributionDao.fromCursor(cursor);
|
||||
|
||||
Timber.d("Cursor position is %d", cursor.getPosition());
|
||||
public void onBindViewHolder(@NonNull ContributionViewHolder holder, int position) {
|
||||
final Contribution contribution = callback.getContributionForPosition(position);
|
||||
DisplayableContribution displayableContribution = new DisplayableContribution(contribution,
|
||||
cursor.getPosition(),
|
||||
new DisplayableContribution.ContributionActions() {
|
||||
@Override
|
||||
public void retryUpload() {
|
||||
ContributionsListAdapter.this.retryUpload(view.getContext(), contribution);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteUpload() {
|
||||
ContributionsListAdapter.this.deleteUpload(view.getContext(), contribution);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
ContributionsListAdapter.this.openMediaDetail(contribution);
|
||||
}
|
||||
});
|
||||
views.bindModel(context, displayableContribution);
|
||||
position);
|
||||
holder.init(position, displayableContribution);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry upload when it is failed
|
||||
* @param contribution contribution to be retried
|
||||
*/
|
||||
private void retryUpload(@NonNull Context context, Contribution contribution) {
|
||||
if (NetworkUtils.isInternetConnectionEstablished(context)) {
|
||||
if (contribution.getState() == STATE_FAILED
|
||||
&& uploadService!= null) {
|
||||
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, contribution);
|
||||
Timber.d("Restarting for %s", contribution.toString());
|
||||
} else {
|
||||
Timber.d("Skipping re-upload for non-failed %s", contribution.toString());
|
||||
}
|
||||
} else {
|
||||
ViewUtil.showLongToast(context, R.string.this_function_needs_network_connection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return callback.getNumberOfContributions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a failed upload attempt
|
||||
* @param contribution contribution to be deleted
|
||||
*/
|
||||
private void deleteUpload(@NonNull Context context, Contribution contribution) {
|
||||
if (NetworkUtils.isInternetConnectionEstablished(context)) {
|
||||
if (contribution.getState() == STATE_FAILED) {
|
||||
Timber.d("Deleting failed contrib %s", contribution.toString());
|
||||
contributionDao.delete(contribution);
|
||||
} else {
|
||||
Timber.d("Skipping deletion for non-failed contrib %s", contribution.toString());
|
||||
}
|
||||
} else {
|
||||
ViewUtil.showLongToast(context, R.string.this_function_needs_network_connection);
|
||||
}
|
||||
public interface Callback {
|
||||
|
||||
}
|
||||
void retryUpload(Contribution contribution);
|
||||
|
||||
private void openMediaDetail(Contribution contribution){
|
||||
listener.onEvent(contribution.getFilename());
|
||||
void deleteUpload(Contribution contribution);
|
||||
|
||||
}
|
||||
EventListener listener;
|
||||
void openMediaDetail(int contribution);
|
||||
|
||||
public interface EventListener {
|
||||
void onEvent(String filename);
|
||||
int getNumberOfContributions();
|
||||
|
||||
Contribution getContributionForPosition(int position);
|
||||
|
||||
int findItemPositionWithId(String lastVisibleItemID);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,70 @@ 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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import static fr.free.nrw.commons.contributions.ContributionDao.Table.ALL_FIELDS;
|
||||
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI;
|
||||
import static fr.free.nrw.commons.settings.Prefs.UPLOADS_SHOWING;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.DataSetObserver;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.loader.content.CursorLoader;
|
||||
import androidx.loader.content.Loader;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.contributions.ContributionsContract.UserActionListener;
|
||||
import javax.inject.Inject;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* The presenter class for Contributions
|
||||
*/
|
||||
public class ContributionsPresenter extends DataSetObserver implements UserActionListener {
|
||||
|
||||
private final ContributionsRepository repository;
|
||||
private ContributionsContract.View view;
|
||||
private Cursor cursor;
|
||||
|
||||
@Inject
|
||||
Context context;
|
||||
|
||||
@Inject
|
||||
ContributionsPresenter(ContributionsRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachView(ContributionsContract.View view) {
|
||||
this.view = view;
|
||||
if (null != cursor) {
|
||||
try {
|
||||
cursor.registerDataSetObserver(this);
|
||||
} catch (IllegalStateException e) {//Cursor might be already registered
|
||||
Timber.d(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachView() {
|
||||
this.view = null;
|
||||
if (null != cursor) {
|
||||
try {
|
||||
cursor.unregisterDataSetObserver(this);
|
||||
} catch (Exception e) {//Cursor might not be already registered
|
||||
Timber.d(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, @Nullable Bundle args) {
|
||||
int preferredNumberOfUploads = repository.get(UPLOADS_SHOWING);
|
||||
return new CursorLoader(context, BASE_URI,
|
||||
ALL_FIELDS, "", null,
|
||||
ContributionDao.CONTRIBUTION_SORT + "LIMIT "
|
||||
+ (preferredNumberOfUploads>0?preferredNumberOfUploads:100));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor cursor) {
|
||||
view.showProgress(false);
|
||||
if (null != cursor && cursor.getCount() > 0) {
|
||||
view.showWelcomeTip(false);
|
||||
view.showNoContributionsUI(false);
|
||||
view.setUploadCount(cursor.getCount());
|
||||
} else {
|
||||
view.showWelcomeTip(true);
|
||||
view.showNoContributionsUI(true);
|
||||
}
|
||||
swapCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
|
||||
this.cursor = null;
|
||||
//On LoadFinished is not guaranteed to be called
|
||||
view.showProgress(false);
|
||||
view.showWelcomeTip(true);
|
||||
view.showNoContributionsUI(true);
|
||||
swapCursor(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contribution from the repository
|
||||
*/
|
||||
@Override
|
||||
public Contribution getContributionsFromCursor(Cursor cursor) {
|
||||
return repository.getContributionFromCursor(cursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a failed contribution from the local db
|
||||
* @param contribution
|
||||
*/
|
||||
@Override
|
||||
public void deleteUpload(Contribution contribution) {
|
||||
repository.deleteContributionFromDB(contribution);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a contribution at the specified cursor position
|
||||
* @param i
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Media getItemAtPosition(int i) {
|
||||
if (null != cursor && cursor.moveToPosition(i)) {
|
||||
return getContributionsFromCursor(cursor);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contribution position with id
|
||||
*/
|
||||
public int getChildPositionWithId(String id) {
|
||||
int position = 0;
|
||||
cursor.moveToFirst();
|
||||
while (null != cursor && cursor.moveToNext()) {
|
||||
if (getContributionsFromCursor(cursor).getContentUri().getLastPathSegment()
|
||||
.equals(id)) {
|
||||
position = cursor.getPosition();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged() {
|
||||
super.onChanged();
|
||||
view.onDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInvalidated() {
|
||||
super.onInvalidated();
|
||||
//Not letting the view know of this as of now, TODO discuss how to handle this and maybe show a proper ui for this
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap in a new Cursor, returning the old Cursor. The returned old Cursor is <em>not</em>
|
||||
* closed.
|
||||
*
|
||||
* @param newCursor The new cursor to be used.
|
||||
* @return Returns the previously set Cursor, or null if there was not one. If the given new
|
||||
* Cursor is the same instance is the previously set Cursor, null is also returned.
|
||||
*/
|
||||
private void swapCursor(Cursor newCursor) {
|
||||
try {
|
||||
if (newCursor == cursor) {
|
||||
return;
|
||||
}
|
||||
Cursor oldCursor = cursor;
|
||||
if (oldCursor != null) {
|
||||
oldCursor.unregisterDataSetObserver(this);
|
||||
}
|
||||
cursor = newCursor;
|
||||
if (newCursor != null) {
|
||||
newCursor.registerDataSetObserver(this);
|
||||
}
|
||||
view.onDataSetChanged();
|
||||
} catch (IllegalStateException e) {//Cursor might [not] be already registered/unregistered
|
||||
Timber.e(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
|
|
@ -385,13 +384,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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package fr.free.nrw.commons.di;
|
||||
|
||||
import fr.free.nrw.commons.contributions.ContributionsModule;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Component;
|
||||
|
|
@ -28,7 +29,7 @@ import fr.free.nrw.commons.widget.PicOfDayAppWidget;
|
|||
ActivityBuilderModule.class,
|
||||
FragmentBuilderModule.class,
|
||||
ServiceBuilderModule.class,
|
||||
ContentProviderBuilderModule.class, UploadModule.class
|
||||
ContentProviderBuilderModule.class, UploadModule.class, ContributionsModule.class
|
||||
})
|
||||
public interface CommonsApplicationComponent extends AndroidInjector<ApplicationlessInjection> {
|
||||
void inject(CommonsApplication application);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,21 +20,16 @@
|
|||
/>
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:id="@+id/loadingContributionsProgressBar"
|
||||
/>
|
||||
<GridView
|
||||
android:id="@+id/loadingContributionsProgressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/contributionsList"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:stretchMode="columnWidth"
|
||||
android:columnWidth="240dp"
|
||||
android:numColumns="auto_fit"
|
||||
android:listSelector="@null"
|
||||
android:fadingEdge="none"
|
||||
android:fastScrollEnabled="true"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
package fr.free.nrw.commons.contributions
|
||||
|
||||
import android.database.Cursor
|
||||
import androidx.loader.content.CursorLoader
|
||||
import androidx.loader.content.Loader
|
||||
import com.nhaarman.mockito_kotlin.verify
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
/**
|
||||
* The unit test class for ContributionsPresenter
|
||||
*/
|
||||
class ContributionsPresenterTest {
|
||||
@Mock
|
||||
internal var repository: ContributionsRepository? = null
|
||||
@Mock
|
||||
internal var view: ContributionsContract.View? = null
|
||||
|
||||
private var contributionsPresenter: ContributionsPresenter? = null
|
||||
|
||||
private lateinit var cursor: Cursor
|
||||
|
||||
lateinit var contribution: Contribution
|
||||
|
||||
lateinit var loader: Loader<Cursor>
|
||||
|
||||
/**
|
||||
* initial setup
|
||||
*/
|
||||
@Before
|
||||
@Throws(Exception::class)
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
cursor = Mockito.mock(Cursor::class.java)
|
||||
contribution = Mockito.mock(Contribution::class.java)
|
||||
contributionsPresenter = ContributionsPresenter(repository)
|
||||
loader = Mockito.mock(CursorLoader::class.java)
|
||||
contributionsPresenter?.onAttachView(view)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test presenter actions onGetContributionFromCursor
|
||||
*/
|
||||
@Test
|
||||
fun testGetContributionFromCursor() {
|
||||
contributionsPresenter?.getContributionsFromCursor(cursor)
|
||||
verify(repository)?.getContributionFromCursor(cursor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test presenter actions onDeleteContribution
|
||||
*/
|
||||
@Test
|
||||
fun testDeleteContribution() {
|
||||
contributionsPresenter?.deleteUpload(contribution)
|
||||
verify(repository)?.deleteContributionFromDB(contribution)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test presenter actions on loaderFinished and has non zero media objects
|
||||
*/
|
||||
@Test
|
||||
fun testOnLoaderFinishedNonZeroContributions() {
|
||||
Mockito.`when`(cursor.count).thenReturn(1)
|
||||
contributionsPresenter?.onLoadFinished(loader, cursor)
|
||||
verify(view)?.showProgress(false)
|
||||
verify(view)?.showWelcomeTip(false)
|
||||
verify(view)?.showNoContributionsUI(false)
|
||||
verify(view)?.setUploadCount(cursor.count)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test presenter actions on loaderFinished and has Zero media objects
|
||||
*/
|
||||
@Test
|
||||
fun testOnLoaderFinishedZeroContributions() {
|
||||
Mockito.`when`(cursor.count).thenReturn(0)
|
||||
contributionsPresenter?.onLoadFinished(loader, cursor)
|
||||
verify(view)?.showProgress(false)
|
||||
verify(view)?.showWelcomeTip(true)
|
||||
verify(view)?.showNoContributionsUI(true)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test presenter actions on loader reset
|
||||
*/
|
||||
@Test
|
||||
fun testOnLoaderReset() {
|
||||
contributionsPresenter?.onLoaderReset(loader)
|
||||
verify(view)?.showProgress(false)
|
||||
verify(view)?.showWelcomeTip(true)
|
||||
verify(view)?.showNoContributionsUI(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test presenter actions on loader change
|
||||
*/
|
||||
@Test
|
||||
fun testOnChanged() {
|
||||
contributionsPresenter?.onChanged()
|
||||
verify(view)?.onDataSetChanged()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue