Main screen ui changes, fixes #725 Main screen UI overhaul (#1922)

* Delete Contributions Activity content to rewrite it

* Add layout for new Contributions Activity design

* Bind views

* Override auth cookie required

* Add tabs and fragments method

* Create ContributionsFragment which will hold ContributionsListFragment and MediaDetailsFragment

* Add NearbyFragment which will hold NearbyMapFragment and NearbyListFragment

* Add ContributionsActivityPagerAdapter inner class to manage view pager

* Create strings will be written on tabs for contributions and nearby

* Create setTabAndViewPagerSynchronisation method to sycn view pager and tab layout. If user swipe pages, tabs will also change (and vice versa)

* Add theme dependent background color for Drawer Layout of activity_contributions layout file

* Add theme dependent background color for tabs in main

* Create Contributions Fragment structure which will hold Media Detail Fragment and Contributions List Fragment

* Inifilate contributions list fragment view

* Create variables and methods to reuse and create Media Detils Fragment and Contributions List Fragment which will be inside Contribution Fragment

* Override cursor loader methods

* set MediaDetilsView fragment or ContributionListFragment according to users state

* Show details of an image when item is clicked

* Add delete and retry functionality, note: not tested yet

* Override media count methods

* Implement onBack Pressed settings

* Register and unregister datasetObservers

* Add contributin list fragment

* Add contribution list layout with FABs for camera and galerry

* Make sure we called onAuthAcquired from fragment after is is attached

* Create ContributionListViewUtils class to change visibility of views according to MediaDetailsFragment visiblity or their loading state

* Make number of uploads visible if contribution list is visible and number of uploads is uploaded. Progress bar is visible if contribution list is visible and number of uploads are uploading. Both invisible if Media Details Fragment is visible

* Return parent fragment instead of parent activity

* GetPagerFragment instead of getActivity since currently ContributionsFragment take over responsibility from ContributionsActivity

* Add contribution number next to tab text for contribution, as discussed in thread

* Remove number of uploads from contributions fragment since we moved it text of tab layout

* Add unread notifications asynctask to check unread notifications on background

* Save latest time user notification activity viewed

* Add shared preferences provider for latest notification activity visit time

* Add shared preferences provider for latest notification activity visit time

* Change notification icon (add blue dot) whenever a notification comes

* Recover notifications state on come back to contributions list from media details fragment

* Add date with year parameter to Notification class, because we will use it on comparasion of dates

* Check if user visited noifications activity after last notification came

* Add ation to notification icon

* Add nearby custom card view class

* Add card view to activity

* Add a button which will be displayed when nearby permission is not granted thus closest point can't be displayed on main screen. Besides, theme dependent click styles are added to button

* Add button click and permission request logic. Not: solve why location manager is null

* Inject location manager to activity instead

* Make card view dismissable with swip

* Add preference to disable or display closest nearby location

* Add a bugfix to set visibility of nearby notification cardview

* Add UI modifier methods to display notifications

* Modify getFromWikidataQuery method, so that based on the restunClosestResult boolean, we get only the closest nearby place, instead of fetching bunch of nearby places each time

* Make inner class vaariables public to reach them out of package

* Temporarily comment out icon setter methods since it crashes under API19

* Inject location manager

* Register location manager accoring to permission is given, then call nearby card view updater methods

* Change method calls loadAttractionsFromLocation by considering new parameter to decide between closest nearby call or an usual nearby call

* Add progress bar to nearby cardview

* Fix notifications string

* Hide nearby card view when Media Details is visible

* Change tab on nerby card view click

* Add hardcoded strings to strings.xml

* Move nearby activity to new nearby frament

* Add fragments for nearby list and map into outer nearby fragment

* Change options menu item according to tab view

* Make nearby card view invisible on swipe to nearby tab

* Use retained nearby fragment

* Add action to list sheet button

* This commit caused contrib list become invisible thus,
Revert "Use retained nearby fragment"

This reverts commit 86b3633b23.

* Make sure retained fragments are used for -both- nearby and contrib fragments

* Remove unrelated part added because of confusion, sorry

* Make sure nearbyNotificationCardView visibility works corrent

* Move nearby methods from nerby activity to nearby fragment, and add a lastLocationDuplicate variable to distinguish first time location from nearby fragment and nearby notification card on contributions activity

* Change activity.findViewByID lines with parentFragment.view.findViewById

* Remove toolbar from nearby fragment, since contributions activity already has

* Disable view pager swipe, since using map is very hard with swipe option of view pager

* Place progress bar inside nearby card notification to center

* Make sure using retaied nearby map fragment and nearby list fragment inside nearby fragment

* Update nearby notification content on slight location updates too, if it is first update after on resume. This prevented very long time loading progress bar

* Add case for no nearest pleace found, to prevent bug

* Prevent a possible bug can be caused from activity already killed

* Add click actions to FAB buttons in contributions list fragment. And arrange FAB margins

* Try to use a new location manager instance instead of using single object for both nearby map and nearby notification card view. Because location manager has a state mechanism which is designed to be called from a single point. When I call same methods from both nearby card view notification and nearby map, next update time of map etc. gets confused.

* Set radius to initial value on getFromWikidataQuery (when it is called for getting closest result to be used in nearby card view notification). Normally, algorithm increase radius, this technique works for nearby map but when it comes to finding nearest point, it can return null

* Add an enum to make card view visibility more stable, however, still there is a bug.

* Prevent some more nearby card view visilbility bugs, however still there is a bug

* Add some nullchecks for precaution

* Check nearby permission and refresh nearby view if nearby tab selected, othervise do nothing

* Send user to contrib tab if permission is denied after masrhmallow

* Change nearby fragment background so that progress bar is visible now

* Reduce code duplicate

* request location and gps permission from contribution nearby car view too

* Make sure using retained fragments

* Make sure Contrib list fragment is retained on orientation change

* Fix NPE at options menu

* Make fragment flag fancier, define it per fragment instance, instead of activity

* Fix Service leak and onsavedInstance NPE errrors both occured on orientation change

* Refresh nearby map on orientation change

* Remove unused imports, organise logs and add comments for NearbyMapFragment class

* Remove all references of nearby activity, since we don't use it anymore

* Remove unused imports, organise logs and add comments for Nearby Controller

* Remove unused imports, organise logs and add comments for NearbyFragment

* Remove unused imports, organise logs and add comments for NearbyNotificationCardView

* Change class name from Contributions Activity to Main Activity. Remove unused imports, organise logs and add comments for MainAtivity

* Remove extra spaces

* Remove unused imports and logs

* Remove unused imports, organise logs and add comments for LocationServiceManager

* Remove unused imports, organise logs and add comments for NotificationsActivity

* Remove unused imports, organise logs and add comments for Contributions Fragment

* bug fix nearby notification card dismiss/restore issue

* Change display_nearby_notification_summary varibale with Tap here to see the nearest place that needs pictures

* Add nearest place notification card dismiss toast

* Fix mistake made on previous commit, while fixing conflicts

* Set no data yet message invisible after contributions list is loaded

* Change FAB margins, according to Josephine's review

* Change FAB margins, according to Josephine's review

* Change contributions list background to white, to make FAB more visible

* Add infobutton with popup window next to nearby tba, to explain what does this tab do

* Change hambuger icon to back arrow when media details activity visible

* On back button clicked from nearby fragment, switch back to contributions fragment, instead of closing the app

* Check nearby card view visibility on coming back from media details activity

* Change notification icon with default vector supplied by android vector repos. If we use the one I drawn on inkscape, produced vector is not compatible with API level 19 and below. I couldn't find a proper solution, and decided to change icon

* Fix a possible NPE, caused by loation manager has Main activity reference after it is destroyed

* Change hardcoded string with var from string xml

* Make sure you listen storage permissions from contribution list framgent FABs

* Make sure you listened storage permissions for Neaby fragment buttons too

* Check NPEs causing crashes. Now it does not crash after coming back from settings activity

* Make notification icon compatible with <API19 devices, by drawing and using .png images

* Change back icon arrow vector with png

* Attempt to solve location manager caused memory leak

* Fix memory leaks and optimize imports

* Merge 2.9 release
This commit is contained in:
neslihanturan 2018-11-10 18:26:01 +02:00 committed by Josephine Lim
parent 57e7c154b4
commit 02908a678b
67 changed files with 2949 additions and 1186 deletions

View file

@ -1,382 +0,0 @@
package fr.free.nrw.commons.contributions;
import android.accounts.Account;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Adapter;
import android.widget.AdapterView;
import java.util.ArrayList;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.ButterKnife;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.HandlerService;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AuthenticatedActivity;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.quiz.QuizChecker;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.UploadService;
import fr.free.nrw.commons.utils.ContributionUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
import static android.content.ContentResolver.requestSync;
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
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;
public class ContributionsActivity
extends AuthenticatedActivity
implements LoaderManager.LoaderCallbacks<Cursor>,
AdapterView.OnItemClickListener,
MediaDetailPagerFragment.MediaDetailProvider,
FragmentManager.OnBackStackChangedListener,
ContributionsListFragment.SourceRefresher {
@Inject MediaWikiApi mediaWikiApi;
@Inject SessionManager sessionManager;
@Inject @Named("default_preferences") SharedPreferences prefs;
@Inject ContributionDao contributionDao;
private Cursor allContributions;
private ContributionsListFragment contributionsList;
private MediaDetailPagerFragment mediaDetails;
private UploadService uploadService;
private boolean isUploadServiceConnected;
private ArrayList<DataSetObserver> observersWaitingForLoad = new ArrayList<>();
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private ServiceConnection uploadServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder) binder)
.getService();
isUploadServiceConnected = true;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
// this should never happen
Timber.e(new RuntimeException("UploadService died but the rest of the process did not!"));
}
};
@Override
protected void onDestroy() {
compositeDisposable.clear();
getSupportFragmentManager().removeOnBackStackChangedListener(this);
super.onDestroy();
if (isUploadServiceConnected) {
unbindService(uploadServiceConnection);
}
}
@Override
protected void onResume() {
super.onResume();
boolean isSettingsChanged = prefs.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
prefs.edit().putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false).apply();
if (isSettingsChanged) {
refreshSource();
}
}
@Override
protected void onAuthCookieAcquired(String authCookie) {
// Do a sync everytime we get here!
requestSync(sessionManager.getCurrentAccount(), BuildConfig.CONTRIBUTION_AUTHORITY, new Bundle());
Intent uploadServiceIntent = new Intent(this, UploadService.class);
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
startService(uploadServiceIntent);
bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
allContributions = contributionDao.loadAllContributions();
getSupportLoaderManager().initLoader(0, null, this);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contributions);
ButterKnife.bind(this);
// Activity can call methods in the fragment by acquiring a
// reference to the Fragment from FragmentManager, using findFragmentById()
FragmentManager supportFragmentManager = getSupportFragmentManager();
contributionsList = (ContributionsListFragment)supportFragmentManager
.findFragmentById(R.id.contributionsListFragment);
supportFragmentManager.addOnBackStackChangedListener(this);
if (savedInstanceState != null) {
mediaDetails = (MediaDetailPagerFragment)supportFragmentManager
.findFragmentById(R.id.contributionsFragmentContainer);
getSupportLoaderManager().initLoader(0, null, this);
}
requestAuthToken();
initDrawer();
setTitle(getString(R.string.title_activity_contributions));
if (checkAccount()) {
new QuizChecker(this,
sessionManager.getCurrentAccount().name,
mediaWikiApi);
}
if (!BuildConfig.FLAVOR.equalsIgnoreCase("beta")){
setUploadCount();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
boolean mediaDetailsVisible = mediaDetails != null && mediaDetails.isVisible();
outState.putBoolean("mediaDetailsVisible", mediaDetailsVisible);
}
/**
* Replace whatever is in the current contributionsFragmentContainer view with
* mediaDetailPagerFragment, and preserve previous state in back stack.
* Called when user selects a contribution.
*/
private void showDetail(int i) {
if (mediaDetails == null || !mediaDetails.isVisible()) {
mediaDetails = new MediaDetailPagerFragment();
FragmentManager supportFragmentManager = getSupportFragmentManager();
supportFragmentManager
.beginTransaction()
.replace(R.id.contributionsFragmentContainer, mediaDetails)
.addToBackStack(null)
.commit();
supportFragmentManager.executePendingTransactions();
}
mediaDetails.showImage(i);
}
public void retryUpload(int i) {
allContributions.moveToPosition(i);
Contribution c = contributionDao.fromCursor(allContributions);
if (c.getState() == STATE_FAILED) {
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, c);
Timber.d("Restarting for %s", c.toString());
} else {
Timber.d("Skipping re-upload for non-failed %s", c.toString());
}
}
public void deleteUpload(int i) {
allContributions.moveToPosition(i);
Contribution c = contributionDao.fromCursor(allContributions);
if (c.getState() == STATE_FAILED) {
Timber.d("Deleting failed contrib %s", c.toString());
// If upload fails and then user decides to cancel upload at all, which means contribution
// object will be deleted. So we have to delete temp file for that contribution.
ContributionUtils.removeTemporaryFile(c.getLocalUri());
contributionDao.delete(c);
} else {
Timber.d("Skipping deletion for non-failed contrib %s", c.toString());
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if (mediaDetails.isVisible()) {
getSupportFragmentManager().popBackStack();
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
protected void onAuthFailure() {
finish(); // If authentication failed, we just exit
}
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long item) {
showDetail(position);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
return super.onCreateOptionsMenu(menu);
}
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
int uploads = prefs.getInt(UPLOADS_SHOWING, 100);
return new CursorLoader(this, BASE_URI,
ALL_FIELDS, "", null,
ContributionDao.CONTRIBUTION_SORT + "LIMIT " + uploads);
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
contributionsList.changeProgressBarVisibility(false);
if (contributionsList.getAdapter() == null) {
contributionsList.setAdapter(new ContributionsListAdapter(getApplicationContext(),
cursor, 0, contributionDao));
} else {
((CursorAdapter) contributionsList.getAdapter()).swapCursor(cursor);
}
if (contributionsList.getAdapter().getCount()>0){
contributionsList.changeEmptyScreen(false);
}
contributionsList.clearSyncMessage();
notifyAndMigrateDataSetObservers();
}
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
((CursorAdapter) contributionsList.getAdapter()).swapCursor(null);
}
//FIXME: Potential cause of wrong image display bug
@Override
public Media getMediaAtPosition(int i) {
if (contributionsList.getAdapter() == null) {
// not yet ready to return data
return null;
} else {
return contributionDao.fromCursor((Cursor) contributionsList.getAdapter().getItem(i));
}
}
@Override
public int getTotalMediaCount() {
if (contributionsList.getAdapter() == null) {
return 0;
}
return contributionsList.getAdapter().getCount();
}
@SuppressWarnings("ConstantConditions")
private void setUploadCount() {
compositeDisposable.add(mediaWikiApi
.getUploadCount(sessionManager.getCurrentAccount().name)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::displayUploadCount,
t -> Timber.e(t, "Fetching upload count failed")
));
}
private void displayUploadCount(Integer uploadCount) {
if (isFinishing()
|| getSupportActionBar() == null
|| getResources() == null) {
return;
}
getSupportActionBar().setSubtitle(getResources()
.getQuantityString(R.plurals.contributions_subtitle,
uploadCount, uploadCount));
}
public void betaSetUploadCount(int betaUploadCount) {
displayUploadCount(betaUploadCount);
}
@Override
public void notifyDatasetChanged() {
// Do nothing for now
}
private void notifyAndMigrateDataSetObservers() {
Adapter adapter = contributionsList.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();
}
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
Adapter adapter = contributionsList.getAdapter();
if (adapter == null) {
observersWaitingForLoad.add(observer);
} else {
adapter.registerDataSetObserver(observer);
}
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
Adapter adapter = contributionsList.getAdapter();
if (adapter == null) {
observersWaitingForLoad.remove(observer);
} else {
adapter.unregisterDataSetObserver(observer);
}
}
/**
* to ensure user is logged in
* @return
*/
private boolean checkAccount() {
Account currentAccount = sessionManager.getCurrentAccount();
if (currentAccount == null) {
Timber.d("Current account is null");
ViewUtil.showLongToast(this, getResources().getString(R.string.user_not_logged_in));
sessionManager.forceLogin(this);
return false;
}
return true;
}
@Override
public void onBackStackChanged() {
initBackButton();
}
@Override
public void refreshSource() {
getSupportLoaderManager().restartLoader(0, null, this);
}
}

View file

@ -0,0 +1,626 @@
package fr.free.nrw.commons.contributions;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.app.LoaderManager;
import android.support.v4.widget.CursorAdapter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.AdapterView;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import javax.inject.Inject;
import javax.inject.Named;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.HandlerService;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
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.mwapi.MediaWikiApi;
import fr.free.nrw.commons.nearby.NearbyController;
import fr.free.nrw.commons.nearby.NearbyNoificationCardView;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.notification.NotificationController;
import fr.free.nrw.commons.notification.UnreadNotificationsCheckAsync;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.UploadService;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
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.location.LocationServiceManager.LOCATION_REQUEST;
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>,
AdapterView.OnItemClickListener,
MediaDetailPagerFragment.MediaDetailProvider,
FragmentManager.OnBackStackChangedListener,
ContributionsListFragment.SourceRefresher,
LocationUpdateListener
{
@Inject
@Named("default_preferences")
SharedPreferences prefs;
@Inject
ContributionDao contributionDao;
@Inject
MediaWikiApi mediaWikiApi;
@Inject
NotificationController notificationController;
@Inject
NearbyController nearbyController;
private ArrayList<DataSetObserver> observersWaitingForLoad = new ArrayList<>();
private Cursor allContributions;
private UploadService uploadService;
private boolean isUploadServiceConnected;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
CountDownLatch waitForContributionsListFragment = new CountDownLatch(1);
private ContributionsListFragment contributionsListFragment;
private MediaDetailPagerFragment mediaDetailPagerFragment;
public static final String CONTRIBUTION_LIST_FRAGMENT_TAG = "ContributionListFragmentTag";
public static final String MEDIA_DETAIL_PAGER_FRAGMENT_TAG = "MediaDetailFragmentTag";
public NearbyNoificationCardView nearbyNoificationCardView;
private Disposable placesDisposable;
private LatLng curLatLng;
private boolean firstLocationUpdate = true;
private LocationServiceManager locationManager;
private boolean isFragmentAttachedBefore = false;
/**
* Since we will need to use parent activity on onAuthCookieAcquired, we have to wait
* fragment to be attached. Latch will be responsible for this sync.
*/
private ServiceConnection uploadServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder) binder)
.getService();
isUploadServiceConnected = true;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
// this should never happen
Timber.e(new RuntimeException("UploadService died but the rest of the process did not!"));
}
};
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_contributions, container, false);
nearbyNoificationCardView = view.findViewById(R.id.card_view_nearby);
if (savedInstanceState != null) {
mediaDetailPagerFragment = (MediaDetailPagerFragment)getChildFragmentManager().findFragmentByTag(MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
contributionsListFragment = (ContributionsListFragment) getChildFragmentManager().findFragmentByTag(CONTRIBUTION_LIST_FRAGMENT_TAG);
if (savedInstanceState.getBoolean("mediaDetailsVisible")) {
setMediaDetailPagerFragment();
} else {
setContributionsListFragment();
}
} else {
setContributionsListFragment();
}
if(!BuildConfig.FLAVOR.equalsIgnoreCase("beta")){
setUploadCount();
}
return view;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
/*
- There are some operations we need auth, so we need to make sure isAuthCookieAcquired.
- And since we use same retained fragment doesn't want to make all network operations
all over again on same fragment attached to recreated activity, we do this network
operations on first time fragment atached to an activity. Then they will be retained
until fragment life time ends.
*/
if (((MainActivity)getActivity()).isAuthCookieAcquired && !isFragmentAttachedBefore) {
onAuthCookieAcquired(((MainActivity)getActivity()).uploadServiceIntent);
isFragmentAttachedBefore = true;
new UnreadNotificationsCheckAsync((MainActivity) getActivity(), notificationController).execute();
}
}
/**
* Replace FrameLayout with ContributionsListFragment, user will see contributions list.
* Creates new one if null.
*/
public void setContributionsListFragment() {
// show tabs on contribution list is visible
((MainActivity)getActivity()).showTabs();
// show nearby card view on contributions list is visible
if (nearbyNoificationCardView != null) {
if (prefs.getBoolean("displayNearbyCardView", true)) {
nearbyNoificationCardView.setVisibility(View.VISIBLE);
} else {
nearbyNoificationCardView.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();
}
/**
* Replace FrameLayout with MediaDetailPagerFragment, user will see details of selected media.
* Creates new one if null.
*/
public void setMediaDetailPagerFragment() {
// hide tabs on media detail view is visible
((MainActivity)getActivity()).hideTabs();
// hide nearby card view on media detail is visible
nearbyNoificationCardView.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();
}
/**
* 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 = prefs.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));
} else {
((CursorAdapter) contributionsListFragment.getAdapter()).swapCursor(cursor);
}
contributionsListFragment.clearSyncMessage();
notifyAndMigrateDataSetObservers();
}
}
@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();
}
}
/**
* Called when onAuthCookieAcquired is called on authenticated parent activity
* @param uploadServiceIntent
*/
public void onAuthCookieAcquired(Intent uploadServiceIntent) {
// Since we call onAuthCookieAcquired method from onAttach, isAdded is still false. So don't use it
if (getActivity() != null) { // If fragment is attached to parent activity
getActivity().bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
isUploadServiceConnected = true;
allContributions = contributionDao.loadAllContributions();
getActivity().getSupportLoaderManager().initLoader(0, null, ContributionsFragment.this);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case LOCATION_REQUEST: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Timber.d("Location permission granted, refreshing view");
// No need to display permission request button anymore
nearbyNoificationCardView.displayPermissionRequestButton(false);
locationManager.registerLocationManager();
} else {
// Still ask for permission
nearbyNoificationCardView.displayPermissionRequestButton(true);
}
}
break;
default:
// This is needed to allow the request codes from the Fragments to be routed appropriately
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
// show detail at a position
showDetail(i);
}
/**
* Replace whatever is in the current contributionsFragmentContainer view with
* mediaDetailPagerFragment, and preserve previous state in back stack.
* Called when user selects a contribution.
*/
private void showDetail(int i) {
if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment.isVisible()) {
mediaDetailPagerFragment = new MediaDetailPagerFragment();
setMediaDetailPagerFragment();
}
mediaDetailPagerFragment.showImage(i);
}
/**
* Retry upload when it is failed
* @param i position of upload which will be retried
*/
public void retryUpload(int i) {
allContributions.moveToPosition(i);
Contribution c = contributionDao.fromCursor(allContributions);
if (c.getState() == STATE_FAILED) {
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, c);
Timber.d("Restarting for %s", c.toString());
} else {
Timber.d("Skipping re-upload for non-failed %s", c.toString());
}
}
/**
* Delete a failed upload attempt
* @param i position of upload attempt which will be deteled
*/
public void deleteUpload(int i) {
allContributions.moveToPosition(i);
Contribution c = contributionDao.fromCursor(allContributions);
if (c.getState() == STATE_FAILED) {
Timber.d("Deleting failed contrib %s", c.toString());
contributionDao.delete(c);
} else {
Timber.d("Skipping deletion for non-failed contrib %s", c.toString());
}
}
@Override
public void refreshSource() {
getActivity().getSupportLoaderManager().restartLoader(0, null, this);
}
@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));
}
}
@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);
}
}
@SuppressWarnings("ConstantConditions")
private void setUploadCount() {
compositeDisposable.add(mediaWikiApi
.getUploadCount(((MainActivity)getActivity()).sessionManager.getCurrentAccount().name)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::displayUploadCount,
t -> Timber.e(t, "Fetching upload count failed")
));
}
private void displayUploadCount(Integer uploadCount) {
if (getActivity().isFinishing()
|| getResources() == null) {
return;
}
((MainActivity)getActivity()).setNumOfUploads(uploadCount);
}
public void betaSetUploadCount(int betaUploadCount) {
displayUploadCount(betaUploadCount);
}
/**
* Updates notification indicator on toolbar to indicate there are unread notifications
* @param isThereUnreadNotifications true if user checked notifications before last notification date
*/
public void updateNotificationsNotification(boolean isThereUnreadNotifications) {
((MainActivity)getActivity()).updateNotificationIcon(isThereUnreadNotifications);
}
@Override
public void onStart() {
super.onStart();
}
@Override
public void onPause() {
super.onPause();
locationManager.removeLocationListener(this);
locationManager.unregisterLocationManager();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
boolean mediaDetailsVisible = mediaDetailPagerFragment != null && mediaDetailPagerFragment.isVisible();
outState.putBoolean("mediaDetailsVisible", mediaDetailsVisible);
}
@Override
public void onResume() {
super.onResume();
locationManager = new LocationServiceManager(getActivity());
firstLocationUpdate = true;
locationManager.addLocationListener(this);
boolean isSettingsChanged = prefs.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
prefs.edit().putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false).apply();
if (isSettingsChanged) {
refreshSource();
}
if (prefs.getBoolean("displayNearbyCardView", true)) {
nearbyNoificationCardView.cardViewVisibilityState = NearbyNoificationCardView.CardViewVisibilityState.LOADING;
nearbyNoificationCardView.setVisibility(View.VISIBLE);
checkGPS();
} else {
// Hide nearby notification card view if related shared preferences is false
nearbyNoificationCardView.setVisibility(View.GONE);
}
}
/**
* Check GPS to decide displaying request permission button or not.
*/
private void checkGPS() {
if (!locationManager.isProviderEnabled()) {
Timber.d("GPS is not enabled");
nearbyNoificationCardView.permissionType = NearbyNoificationCardView.PermissionType.ENABLE_GPS;
nearbyNoificationCardView.displayPermissionRequestButton(true);
} else {
Timber.d("GPS is enabled");
checkLocationPermission();
}
}
private void checkLocationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (locationManager.isLocationPermissionGranted()) {
nearbyNoificationCardView.permissionType = NearbyNoificationCardView.PermissionType.NO_PERMISSION_NEEDED;
nearbyNoificationCardView.displayPermissionRequestButton(false);
locationManager.registerLocationManager();
} else {
nearbyNoificationCardView.permissionType = NearbyNoificationCardView.PermissionType.ENABLE_LOCATION_PERMISSON;
nearbyNoificationCardView.displayPermissionRequestButton(true);
}
} else {
// If device is under Marshmallow, we already checked for GPS
nearbyNoificationCardView.permissionType = NearbyNoificationCardView.PermissionType.NO_PERMISSION_NEEDED;
nearbyNoificationCardView.displayPermissionRequestButton(false);
locationManager.registerLocationManager();
}
}
private void updateClosestNearbyCardViewInfo() {
curLatLng = locationManager.getLastLocation();
placesDisposable = Observable.fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curLatLng, true)) // thanks to boolean, it will only return closest result
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::updateNearbyNotification,
throwable -> {
Timber.d(throwable);
updateNearbyNotification(null);
});
}
private void updateNearbyNotification(@Nullable NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
if (nearbyPlacesInfo != null && nearbyPlacesInfo.placeList != null && nearbyPlacesInfo.placeList.size() > 0) {
Place closestNearbyPlace = nearbyPlacesInfo.placeList.get(0);
String distance = formatDistanceBetween(curLatLng, closestNearbyPlace.location);
closestNearbyPlace.setDistance(distance);
nearbyNoificationCardView.updateContent (true, closestNearbyPlace);
} else {
// Means that no close nearby place is found
nearbyNoificationCardView.updateContent (false, null);
}
}
@Override
public void onDestroy() {
compositeDisposable.clear();
getChildFragmentManager().removeOnBackStackChangedListener(this);
locationManager.unregisterLocationManager();
locationManager.removeLocationListener(this);
// Try to prevent a possible NPE
locationManager.context = null;
super.onDestroy();
if (isUploadServiceConnected) {
if (getActivity() != null) {
getActivity().unbindService(uploadServiceConnection);
isUploadServiceConnected = false;
}
}
if (placesDisposable != null) {
placesDisposable.dispose();
}
}
@Override
public void onLocationChangedSignificantly(LatLng latLng) {
// Will be called if location changed more than 1000 meter
// Do nothing on slight changes for using network efficiently
firstLocationUpdate = false;
updateClosestNearbyCardViewInfo();
}
@Override
public void onLocationChangedSlightly(LatLng latLng) {
/* Update closest nearby notification card onLocationChangedSlightly
If first time to update location after onResume, then no need to wait for significant
location change. Any closest location is better than no location
*/
if (firstLocationUpdate) {
updateClosestNearbyCardViewInfo();
// Turn it to false, since it is not first location update anymore. To change closest location
// notifiction, we need to wait for a significant location change.
firstLocationUpdate = false;
}
}
@Override
public void onLocationChangedMedium(LatLng latLng) {
// Update closest nearby card view if location changed more than 500 meters
updateClosestNearbyCardViewInfo();
}
}

View file

@ -1,23 +1,26 @@
package fr.free.nrw.commons.contributions;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
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.ProgressBar;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.widget.TextView;
import java.util.Arrays;
@ -30,15 +33,18 @@ import butterknife.ButterKnife;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.nearby.NearbyActivity;
import fr.free.nrw.commons.utils.PermissionUtils;
import timber.log.Timber;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.app.Activity.RESULT_OK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.view.View.GONE;
/**
* Created by root on 01.06.2018.
*/
public class ContributionsListFragment extends CommonsDaggerSupportFragment {
@BindView(R.id.contributionsList)
@ -47,101 +53,122 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
TextView waitingMessage;
@BindView(R.id.loadingContributionsProgressBar)
ProgressBar progressBar;
@BindView(R.id.fab_plus)
FloatingActionButton fabPlus;
@BindView(R.id.fab_camera)
FloatingActionButton fabCamera;
@BindView(R.id.fab_galery)
FloatingActionButton fabGalery;
@BindView(R.id.noDataYet)
TextView noDataYet;
@Inject
@Named("prefs")
SharedPreferences prefs;
@Inject
@Named("default_preferences")
SharedPreferences defaultPrefs;
private ContributionController controller;
private Animation fab_close;
private Animation fab_open;
private Animation rotate_forward;
private Animation rotate_backward;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_contributions, container, false);
ButterKnife.bind(this, v);
private boolean isFabOpen = false;
public ContributionController controller;
contributionsList.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity());
if (savedInstanceState != null) {
Timber.d("Scrolling to %d", savedInstanceState.getInt("grid-position"));
contributionsList.setSelection(savedInstanceState.getInt("grid-position"));
}
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);
//TODO: Should this be in onResume?
String lastModified = prefs.getString("lastSyncTimestamp", "");
Timber.d("Last Sync Timestamp: %s", lastModified);
if (lastModified.equals("")) {
waitingMessage.setVisibility(View.VISIBLE);
} else {
waitingMessage.setVisibility(GONE);
}
contributionsList.setOnItemClickListener((AdapterView.OnItemClickListener) getParentFragment());
changeEmptyScreen(true);
changeProgressBarVisibility(true);
return v;
return view;
}
public ListAdapter getAdapter() {
return contributionsList.getAdapter();
}
public void setAdapter(ListAdapter adapter) {
this.contributionsList.setAdapter(adapter);
if (BuildConfig.FLAVOR.equalsIgnoreCase("beta")){
((ContributionsActivity) getActivity()).betaSetUploadCount(adapter.getCount());
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (controller == null) {
controller = new ContributionController(this);
}
controller.loadState(savedInstanceState);
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
if (controller != null) {
controller.saveState(outState);
} else {
controller = new ContributionController(this);
}
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initializeAnimations();
setListeners();
}
public void changeEmptyScreen(boolean isEmpty){
this.noDataYet.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
}
public void changeProgressBarVisibility(boolean isVisible) {
this.progressBar.setVisibility(isVisible ? View.VISIBLE : View.GONE);
private void initializeAnimations() {
fab_open = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_open);
fab_close = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_close);
rotate_forward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_forward);
rotate_backward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_backward);
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (outState == null) {
outState = new Bundle();
}
super.onSaveInstanceState(outState);
controller.saveState(outState);
outState.putInt("grid-position", contributionsList.getFirstVisiblePosition());
}
private void setListeners() {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
//FIXME: must get the file data for Google Photos when receive the intent answer, in the onActivityResult method
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
if (requestCode == ContributionController.SELECT_FROM_CAMERA) {
// If coming from camera, pass null as uri. Because camera photos get saved to a
// fixed directory
controller.handleImagePicked(requestCode, null, false, null);
} else {
controller.handleImagePicked(requestCode, data.getData(), false, null);
fabPlus.setOnClickListener(view -> animateFAB(isFabOpen));
fabCamera.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
boolean useExtStorage = defaultPrefs.getBoolean("useExternalStorage", true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && useExtStorage) {
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(getActivity(), WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
if (shouldShowRequestPermissionRationale(WRITE_EXTERNAL_STORAGE)) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
new AlertDialog.Builder(getParentFragment().getActivity())
.setMessage(getString(R.string.write_storage_permission_rationale))
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
getActivity().requestPermissions
(new String[]{WRITE_EXTERNAL_STORAGE}, PermissionUtils.CAMERA_PERMISSION_FROM_CONTRIBUTION_LIST);
dialog.dismiss();
})
.setNegativeButton(android.R.string.cancel, null)
.create()
.show();
} else {
// No explanation needed, we can request the permission.
requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE},
3);
// MY_PERMISSIONS_WRITE_EXTERNAL_STORAGE is an
// app-defined int constant. The callback method gets the
// result of the request.
}
} else {
controller.startCameraCapture();
}
} else {
controller.startCameraCapture();
}
}
} else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
}
}
});
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_from_gallery:
//Gallery crashes before reach ShareActivity screen so must implement permissions check here
fabGalery.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//Gallery crashes before reach ShareActivity screen so must implement permissions check here
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Here, thisActivity is the current activity
@ -156,10 +183,11 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
new AlertDialog.Builder(getActivity())
new AlertDialog.Builder(getParentFragment().getActivity())
.setMessage(getString(R.string.read_storage_permission_rationale))
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, 1);
getActivity().requestPermissions
(new String[]{READ_EXTERNAL_STORAGE}, PermissionUtils.GALLERY_PERMISSION_FROM_CONTRIBUTION_LIST);
dialog.dismiss();
})
.setNegativeButton(android.R.string.cancel, null)
@ -179,59 +207,62 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
}
} else {
controller.startGalleryPick();
return true;
}
} else {
controller.startGalleryPick();
return true;
}
}
});
}
return true;
case R.id.menu_from_camera:
boolean useExtStorage = defaultPrefs.getBoolean("useExternalStorage", true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && useExtStorage) {
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(getActivity(), WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
if (shouldShowRequestPermissionRationale(WRITE_EXTERNAL_STORAGE)) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
new AlertDialog.Builder(getActivity())
.setMessage(getString(R.string.write_storage_permission_rationale))
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE}, 3);
dialog.dismiss();
})
.setNegativeButton(android.R.string.cancel, null)
.create()
.show();
} else {
// No explanation needed, we can request the permission.
requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE},
3);
// MY_PERMISSIONS_WRITE_EXTERNAL_STORAGE is an
// app-defined int constant. The callback method gets the
// result of the request.
}
} else {
controller.startCameraCapture();
return true;
}
} else {
controller.startCameraCapture();
return true;
}
return true;
default:
return super.onOptionsItemSelected(item);
private void animateFAB(boolean isFabOpen) {
this.isFabOpen = !isFabOpen;
if (fabPlus.isShown()){
if (isFabOpen) {
fabPlus.startAnimation(rotate_backward);
fabCamera.startAnimation(fab_close);
fabGalery.startAnimation(fab_close);
fabCamera.hide();
fabGalery.hide();
} else {
fabPlus.startAnimation(rotate_forward);
fabCamera.startAnimation(fab_open);
fabGalery.startAnimation(fab_open);
fabCamera.show();
fabGalery.show();
}
this.isFabOpen=!isFabOpen;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
public void onAttach(Context context) {
super.onAttach(context);
ContributionsFragment parentFragment = (ContributionsFragment)getParentFragment();
parentFragment.waitForContributionsListFragment.countDown();
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
if (requestCode == ContributionController.SELECT_FROM_CAMERA) {
// If coming from camera, pass null as uri. Because camera photos get saved to a
// fixed directory
controller.handleImagePicked(requestCode, null, false, null);
} else if (requestCode == ContributionController.SELECT_FROM_GALLERY){
controller.handleImagePicked(requestCode, data.getData(), false, null);
}
} else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
Timber.d("onRequestPermissionsResult: req code = " + " perm = "
+ Arrays.toString(permissions) + " grant =" + Arrays.toString(grantResults));
@ -246,11 +277,12 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
break;
// 2 = Location allowed when 'nearby places' selected
case 2: {
if (grantResults.length > 0 && grantResults[0] == PERMISSION_GRANTED) {
// TODO: understand and fix
/*if (grantResults.length > 0 && grantResults[0] == PERMISSION_GRANTED) {
Timber.d("Location permission granted");
Intent nearbyIntent = new Intent(getActivity(), NearbyActivity.class);
Intent nearbyIntent = new Intent(getActivity(), MainActivity.class);
startActivity(nearbyIntent);
}
}*/
}
break;
case 3: {
@ -262,42 +294,38 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear(); // See http://stackoverflow.com/a/8495697/17865
inflater.inflate(R.menu.fragment_contributions_list, menu);
if (!deviceHasCamera()) {
menu.findItem(R.id.menu_from_camera).setEnabled(false);
}
}
public boolean deviceHasCamera() {
PackageManager pm = getContext().getPackageManager();
return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) ||
pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
controller = new ContributionController(this);
setHasOptionsMenu(true);
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
controller.loadState(savedInstanceState);
/**
* 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 ? View.VISIBLE : View.GONE);
}
/**
* Clears sync message displayed with progress bar before contributions list became visible
*/
protected void clearSyncMessage() {
waitingMessage.setVisibility(GONE);
noDataYet.setVisibility(GONE);
}
public ListAdapter getAdapter() {
return contributionsList.getAdapter();
}
/**
* 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(BuildConfig.FLAVOR.equalsIgnoreCase("beta")){
//TODO: add betaSetUploadCount method
((ContributionsFragment) getParentFragment()).betaSetUploadCount(adapter.getCount());
}
}
public interface SourceRefresher {

View file

@ -0,0 +1,550 @@
package fr.free.nrw.commons.contributions;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AuthenticatedActivity;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.nearby.NearbyFragment;
import fr.free.nrw.commons.nearby.NearbyMapFragment;
import fr.free.nrw.commons.notification.NotificationActivity;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.upload.UploadService;
import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import timber.log.Timber;
import static android.content.ContentResolver.requestSync;
import static fr.free.nrw.commons.location.LocationServiceManager.LOCATION_REQUEST;
public class MainActivity extends AuthenticatedActivity implements FragmentManager.OnBackStackChangedListener {
@Inject
SessionManager sessionManager;
@BindView(R.id.tab_layout)
TabLayout tabLayout;
@BindView(R.id.pager)
public UnswipableViewPager viewPager;
@Inject
public LocationServiceManager locationManager;
@Inject
@Named("default_preferences")
public SharedPreferences prefs;
public Intent uploadServiceIntent;
public boolean isAuthCookieAcquired = false;
public ContributionsActivityPagerAdapter contributionsActivityPagerAdapter;
public final int CONTRIBUTIONS_TAB_POSITION = 0;
public final int NEARBY_TAB_POSITION = 1;
public boolean isContributionsFragmentVisible = true; // False means nearby fragment is visible
private Menu menu;
private boolean isThereUnreadNotifications = false;
private boolean onOrientationChanged = false;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contributions);
ButterKnife.bind(this);
requestAuthToken();
initDrawer();
setTitle(getString(R.string.navigation_item_home)); // Should I create a new string variable with another name instead?
if (savedInstanceState != null ) {
onOrientationChanged = true; // Will be used in nearby fragment to determine significant update of map
//If nearby map was visible, call on Tab Selected to call all nearby operations
if (savedInstanceState.getInt("viewPagerCurrentItem") == 1) {
((NearbyFragment)contributionsActivityPagerAdapter.getItem(1)).onTabSelected(onOrientationChanged);
}
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("viewPagerCurrentItem", viewPager.getCurrentItem());
}
@Override
protected void onAuthCookieAcquired(String authCookie) {
// Do a sync everytime we get here!
requestSync(sessionManager.getCurrentAccount(), BuildConfig.CONTRIBUTION_AUTHORITY, new Bundle());
uploadServiceIntent = new Intent(this, UploadService.class);
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
startService(uploadServiceIntent);
addTabsAndFragments();
isAuthCookieAcquired = true;
if (contributionsActivityPagerAdapter.getItem(0) != null) {
((ContributionsFragment)contributionsActivityPagerAdapter.getItem(0)).onAuthCookieAcquired(uploadServiceIntent);
}
}
private void addTabsAndFragments() {
contributionsActivityPagerAdapter = new ContributionsActivityPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(contributionsActivityPagerAdapter);
tabLayout.addTab(tabLayout.newTab().setText(getResources().getString(R.string.contributions_fragment)));
tabLayout.addTab(tabLayout.newTab().setText(getResources().getString(R.string.nearby_fragment)));
// Set custom view to add nearby info icon next to text
View nearbyTabLinearLayout = LayoutInflater.from(this).inflate(R.layout.custom_nearby_tab_layout, null);
View nearbyInfoPopupWindowLayout = LayoutInflater.from(this).inflate(R.layout.nearby_info_popup_layout, null);
ImageView nearbyInfo = nearbyTabLinearLayout.findViewById(R.id.nearby_info_image);
tabLayout.getTabAt(1).setCustomView(nearbyTabLinearLayout);
nearbyInfo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
/*new AlertDialog.Builder(MainActivity.this)
.setTitle(R.string.title_activity_nearby)
.setMessage(R.string.showcase_view_whole_nearby_activity)
.setCancelable(true)
.setNeutralButton(android.R.string.ok, (dialog, id) -> dialog.cancel())
.create()
.show();*/
String popupText = getResources().getString(R.string.showcase_view_whole_nearby_activity);
ViewUtil.displayPopupWindow(nearbyInfo, MainActivity.this, nearbyInfoPopupWindowLayout, popupText);
}
});
if (uploadServiceIntent != null) {
// If auth cookie already acquired notify contrib fragmnet so that it san operate auth required actions
((ContributionsFragment)contributionsActivityPagerAdapter.getItem(CONTRIBUTIONS_TAB_POSITION)).onAuthCookieAcquired(uploadServiceIntent);
}
setTabAndViewPagerSynchronisation();
}
/**
* Adds number of uploads next to tab text "Contributions" then it will look like
* "Contributions (NUMBER)"
* @param uploadCount
*/
public void setNumOfUploads(int uploadCount) {
tabLayout.getTabAt(0).setText(getResources().getString(R.string.contributions_fragment) +" "+ getResources()
.getQuantityString(R.plurals.contributions_subtitle,
uploadCount, uploadCount));
}
/**
* Normally tab layout and view pager has no relation, which means when you swipe view pager
* tab won't change and vice versa. So we have to notify each of them.
*/
private void setTabAndViewPagerSynchronisation() {
//viewPager.canScrollHorizontally(false);
viewPager.setFocusableInTouchMode(true);
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
switch (position) {
case CONTRIBUTIONS_TAB_POSITION:
Timber.d("Contributions tab selected");
tabLayout.getTabAt(CONTRIBUTIONS_TAB_POSITION).select();
isContributionsFragmentVisible = true;
updateMenuItem();
break;
case NEARBY_TAB_POSITION:
Timber.d("Nearby tab selected");
tabLayout.getTabAt(NEARBY_TAB_POSITION).select();
isContributionsFragmentVisible = false;
updateMenuItem();
// Do all permission and GPS related tasks on tab selected, not on create
((NearbyFragment)contributionsActivityPagerAdapter.getItem(1)).onTabSelected(onOrientationChanged);
break;
default:
tabLayout.getTabAt(CONTRIBUTIONS_TAB_POSITION).select();
break;
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
public void hideTabs() {
changeDrawerIconToBakcButton();
if (tabLayout != null) {
tabLayout.setVisibility(View.GONE);
}
}
public void showTabs() {
changeDrawerIconToDefault();
if (tabLayout != null) {
tabLayout.setVisibility(View.VISIBLE);
}
}
@Override
protected void onAuthFailure() {
}
@Override
public void onBackPressed() {
String contributionsFragmentTag = ((ContributionsActivityPagerAdapter) viewPager.getAdapter()).makeFragmentName(R.id.pager, 0);
String nearbyFragmentTag = ((ContributionsActivityPagerAdapter) viewPager.getAdapter()).makeFragmentName(R.id.pager, 1);
if (getSupportFragmentManager().findFragmentByTag(contributionsFragmentTag) != null && isContributionsFragmentVisible) {
// Meas that contribution fragment is visible (not nearby fragment)
ContributionsFragment contributionsFragment = (ContributionsFragment) getSupportFragmentManager().findFragmentByTag(contributionsFragmentTag);
if (contributionsFragment.getChildFragmentManager().findFragmentByTag(ContributionsFragment.MEDIA_DETAIL_PAGER_FRAGMENT_TAG) != null) {
// Means that media details fragment is visible to uer instead of contributions list fragment (As chils fragment)
// Then we want to go back to contributions list fragment on backbutton pressed from media detail fragment
contributionsFragment.getChildFragmentManager().popBackStack();
// Tabs were invisible when Media Details Fragment is active, make them visible again on Contrib List Fragment active
showTabs();
// Nearby Notification Card View was invisible when Media Details Fragment is active, make it visible again on Contrib List Fragment active, according to preferences
if (prefs.getBoolean("displayNearbyCardView", true)) {
contributionsFragment.nearbyNoificationCardView.setVisibility(View.VISIBLE);
} else {
contributionsFragment.nearbyNoificationCardView.setVisibility(View.GONE);
}
} else {
finish();
}
} else if (getSupportFragmentManager().findFragmentByTag(nearbyFragmentTag) != null && !isContributionsFragmentVisible) {
// Meas that nearby fragment is visible (not contributions fragment)
// Set current item to contributions activity instead of closing the activity
viewPager.setCurrentItem(0);
} else {
super.onBackPressed();
}
}
@Override
public void onBackStackChanged() {
initBackButton();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.contribution_activity_notification_menu, menu);
if (!isThereUnreadNotifications) {
// TODO: used vectors are not compatible with API 19 and below, change them
menu.findItem(R.id.notifications).setIcon(ContextCompat.getDrawable(this, R.drawable.ic_notification_white_clip_art));
} else {
menu.findItem(R.id.notifications).setIcon(ContextCompat.getDrawable(this, R.drawable.ic_notification_white_clip_art_dot));
}
this.menu = menu;
updateMenuItem();
return true;
}
/**
* Responsible with displaying required menu items according to displayed fragment.
* Notifications icon when contributions list is visible, list sheet icon when nearby is visible
*/
private void updateMenuItem() {
if (menu != null) {
if (isContributionsFragmentVisible) {
// Display notifications menu item
menu.findItem(R.id.notifications).setVisible(true);
menu.findItem(R.id.list_sheet).setVisible(false);
Timber.d("Contributions activity notifications menu item is visible");
} else {
// Display bottom list menu item
menu.findItem(R.id.notifications).setVisible(false);
menu.findItem(R.id.list_sheet).setVisible(true);
Timber.d("Contributions activity list sheet menu item is visible");
}
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.notifications:
// Starts notification activity on click to notification icon
NavigationBaseActivity.startActivityWithFlags(this, NotificationActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP);
finish();
return true;
case R.id.list_sheet:
if (contributionsActivityPagerAdapter.getItem(1) != null) {
((NearbyFragment)contributionsActivityPagerAdapter.getItem(1)).listOptionMenuIteClicked();
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private boolean deviceHasCamera() {
PackageManager pm = getPackageManager();
return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) ||
pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
}
/**
* Updte notification icon if there is an unread notification
* @param isThereUnreadNotifications true if user didn't visit notifications activity since
* latest notification came to account
*/
public void updateNotificationIcon(boolean isThereUnreadNotifications) {
if (!isThereUnreadNotifications) {
this.isThereUnreadNotifications = false;
menu.findItem(R.id.notifications).setIcon(ContextCompat.getDrawable(this, R.drawable.ic_notification_white_clip_art));
} else {
this.isThereUnreadNotifications = true;
menu.findItem(R.id.notifications).setIcon(ContextCompat.getDrawable(this, R.drawable.ic_notification_white_clip_art_dot));
}
}
public class ContributionsActivityPagerAdapter extends FragmentPagerAdapter {
FragmentManager fragmentManager;
private boolean isContributionsListFragment = true; // to know what to put in first tab, Contributions of Media Details
public ContributionsActivityPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
this.fragmentManager = fragmentManager;
}
@Override
public int getCount() {
return 2;
}
/*
* Do not use getItem method to access fragments on pager adapter. User reference vairables
* instead.
* */
@Override
public Fragment getItem(int position) {
switch (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
return new ContributionsFragment();
}
case 1:
NearbyFragment retainedNearbyFragment = getNearbyFragment(1);
if (retainedNearbyFragment != null) {
return retainedNearbyFragment;
} else {
// If we reach here, retainedNearbyFragment is null
return new NearbyFragment();
}
default:
return null;
}
}
/**
* Generates fragment tag with makeFragmentName method to get retained contributions fragment
* @param position index of tabs, in our case 0 or 1
* @return
*/
private ContributionsFragment getContributionsFragment(int position) {
String tag = makeFragmentName(R.id.pager, position);
return (ContributionsFragment)fragmentManager.findFragmentByTag(tag);
}
/**
* Generates fragment tag with makeFragmentName method to get retained nearby fragment
* @param position index of tabs, in our case 0 or 1
* @return
*/
private NearbyFragment getNearbyFragment(int position) {
String tag = makeFragmentName(R.id.pager, position);
return (NearbyFragment)fragmentManager.findFragmentByTag(tag);
}
/**
* A simple hack to use retained fragment when getID is called explicitly, if we don't use
* this method, a new fragment will be initialized on each explicit calls of getID
* @param viewId id of view pager
* @param index index of tabs, in our case 0 or 1
* @return
*/
public String makeFragmentName(int viewId, int index) {
return "android:switcher:" + viewId + ":" + index;
}
/**
* In first tab we can have ContributionsFragment or Media details fragment. This method
* is responsible to update related boolean
* @param isContributionsListFragment true when contribution fragment should be visible, false
* means user clicked to MediaDetails
*/
private void updateContributionFragmentTabContent(boolean isContributionsListFragment) {
this.isContributionsListFragment = isContributionsListFragment;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
ContributionsListFragment contributionsListFragment =
(ContributionsListFragment) contributionsActivityPagerAdapter
.getItem(0).getChildFragmentManager()
.findFragmentByTag(ContributionsFragment.CONTRIBUTION_LIST_FRAGMENT_TAG);
contributionsListFragment.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case LOCATION_REQUEST: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Timber.d("Location permission given");
} else {
// If nearby fragment is visible and location permission is not given, send user back to contrib fragment
if (!isContributionsFragmentVisible) {
viewPager.setCurrentItem(CONTRIBUTIONS_TAB_POSITION);
// TODO: If contrib fragment is visible and location permission is not given, display permission request button
} else {
}
}
return;
}
// Storage permission for gallery
case PermissionUtils.GALLERY_PERMISSION_FROM_CONTRIBUTION_LIST: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Storage permission given
ContributionsListFragment contributionsListFragment =
(ContributionsListFragment) contributionsActivityPagerAdapter
.getItem(0).getChildFragmentManager()
.findFragmentByTag(ContributionsFragment.CONTRIBUTION_LIST_FRAGMENT_TAG);
contributionsListFragment.controller.startGalleryPick();
}
return;
}
case PermissionUtils.CAMERA_PERMISSION_FROM_CONTRIBUTION_LIST: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Storage permission given
ContributionsListFragment contributionsListFragment =
(ContributionsListFragment) contributionsActivityPagerAdapter
.getItem(0).getChildFragmentManager()
.findFragmentByTag(ContributionsFragment.CONTRIBUTION_LIST_FRAGMENT_TAG);
contributionsListFragment.controller.startCameraCapture();
}
return;
}
case PermissionUtils.CAMERA_PERMISSION_FROM_NEARBY_MAP: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Storage permission given
NearbyMapFragment nearbyMapFragment =
((NearbyFragment) contributionsActivityPagerAdapter
.getItem(1)).nearbyMapFragment;
nearbyMapFragment.controller.startCameraCapture();
}
return;
}
case PermissionUtils.GALLERY_PERMISSION_FROM_NEARBY_MAP: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Storage permission given
NearbyMapFragment nearbyMapFragment =
((NearbyFragment) contributionsActivityPagerAdapter
.getItem(1)).nearbyMapFragment;
nearbyMapFragment.controller.startGalleryPick();
}
return;
}
default:
return;
}
}
@Override
protected void onDestroy() {
locationManager.unregisterLocationManager();
// Remove ourself from hashmap to prevent memory leaks
locationManager = null;
super.onDestroy();
}
}

View file

@ -0,0 +1,30 @@
package fr.free.nrw.commons.contributions;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
public class UnswipableViewPager extends ViewPager{
public UnswipableViewPager(@NonNull Context context) {
super(context);
}
public UnswipableViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// Unswipable
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// Unswipable
return false;
}
}