diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2d9c9a438..6cbead479 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -69,7 +69,7 @@
@@ -80,17 +80,12 @@
+ android:parentActivityName=".contributions.MainActivity" />
-
-
@@ -104,18 +99,18 @@
+ android:parentActivityName=".contributions.MainActivity" />
+ android:parentActivityName=".contributions.MainActivity" />
,
- 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 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 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 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 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);
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java
new file mode 100644
index 000000000..962a24ce3
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java
@@ -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,
+ 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 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 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 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 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();
+ }
+}
+
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
index a17db5e80..7a4294863 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
@@ -1,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 {
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java
new file mode 100644
index 000000000..e3ed7f18e
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java
@@ -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();
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/UnswipableViewPager.java b/app/src/main/java/fr/free/nrw/commons/contributions/UnswipableViewPager.java
new file mode 100644
index 000000000..46c8f3502
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/UnswipableViewPager.java
@@ -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;
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java b/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java
index d26bae178..19cb09dc6 100644
--- a/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java
+++ b/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java
@@ -9,10 +9,10 @@ import fr.free.nrw.commons.auth.LoginActivity;
import fr.free.nrw.commons.auth.SignupActivity;
import fr.free.nrw.commons.bookmarks.BookmarksActivity;
import fr.free.nrw.commons.category.CategoryDetailsActivity;
+import fr.free.nrw.commons.contributions.MainActivity;
import fr.free.nrw.commons.category.CategoryImagesActivity;
-import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.explore.SearchActivity;
-import fr.free.nrw.commons.nearby.NearbyActivity;
+
import fr.free.nrw.commons.notification.NotificationActivity;
import fr.free.nrw.commons.settings.SettingsActivity;
import fr.free.nrw.commons.upload.MultipleShareActivity;
@@ -35,7 +35,7 @@ public abstract class ActivityBuilderModule {
abstract MultipleShareActivity bindMultipleShareActivity();
@ContributesAndroidInjector
- abstract ContributionsActivity bindContributionsActivity();
+ abstract MainActivity bindContributionsActivity();
@ContributesAndroidInjector
abstract SettingsActivity bindSettingsActivity();
@@ -46,9 +46,6 @@ public abstract class ActivityBuilderModule {
@ContributesAndroidInjector
abstract SignupActivity bindSignupActivity();
- @ContributesAndroidInjector
- abstract NearbyActivity bindNearbyActivity();
-
@ContributesAndroidInjector
abstract NotificationActivity bindNotificationActivity();
diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
index a52f6ddab..d54b556c4 100644
--- a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
+++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
@@ -120,6 +120,17 @@ public class CommonsApplicationModule {
return context.getSharedPreferences("direct_nearby_upload_prefs", MODE_PRIVATE);
}
+ /**
+ * Is used to determine when user is viewed notifications activity last
+ * @param context
+ * @return date of lastReadNotificationDate
+ */
+ @Provides
+ @Named("last_read_notification_date")
+ public SharedPreferences providesLastReadNotificationDatePreferences(Context context) {
+ return context.getSharedPreferences("last_read_notification_date", MODE_PRIVATE);
+ }
+
@Provides
public UploadController providesUploadController(SessionManager sessionManager, @Named("default_preferences") SharedPreferences sharedPreferences, Context context) {
return new UploadController(sessionManager, context, sharedPreferences);
diff --git a/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java b/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java
index 9868f3576..04804dab0 100644
--- a/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java
+++ b/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java
@@ -7,12 +7,14 @@ import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment;
import fr.free.nrw.commons.category.CategorizationFragment;
import fr.free.nrw.commons.category.CategoryImagesListFragment;
import fr.free.nrw.commons.category.SubCategoryListFragment;
+import fr.free.nrw.commons.contributions.ContributionsFragment;
import fr.free.nrw.commons.contributions.ContributionsListFragment;
import fr.free.nrw.commons.explore.categories.SearchCategoryFragment;
import fr.free.nrw.commons.explore.images.SearchImageFragment;
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment;
import fr.free.nrw.commons.media.MediaDetailFragment;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
+import fr.free.nrw.commons.nearby.NearbyFragment;
import fr.free.nrw.commons.nearby.NearbyListFragment;
import fr.free.nrw.commons.nearby.NearbyMapFragment;
import fr.free.nrw.commons.nearby.NoPermissionsFragment;
@@ -69,6 +71,12 @@ public abstract class FragmentBuilderModule {
@ContributesAndroidInjector
abstract RecentSearchesFragment bindRecentSearchesFragment();
+ @ContributesAndroidInjector
+ abstract ContributionsFragment bindContributionsFragment();
+
+ @ContributesAndroidInjector
+ abstract NearbyFragment bindNearbyFragment();
+
@ContributesAndroidInjector
abstract BookmarkPicturesFragment bindBookmarkPictureListFragment();
diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java b/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java
index 4a137beed..c58aa1e10 100644
--- a/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java
+++ b/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java
@@ -26,12 +26,13 @@ public class LocationServiceManager implements LocationListener {
private static final long MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS = 2 * 60 * 100;
private static final long MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS = 10;
- private Context context;
+ public Context context;
private LocationManager locationManager;
private Location lastLocation;
+ //private Location lastLocationDuplicate; // Will be used for nearby card view on contributions activity
private final List locationListeners = new CopyOnWriteArrayList<>();
private boolean isLocationManagerRegistered = false;
- private Set locationExplanationDisplayed = new HashSet<>();
+ public Set locationExplanationDisplayed = new HashSet<>();
/**
* Constructs a new instance of LocationServiceManager.
@@ -253,15 +254,25 @@ public class LocationServiceManager implements LocationListener {
@Override
public void onLocationChanged(Location location) {
+ Timber.d("on location changed");
if (isBetterLocation(location, lastLocation)
.equals(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)) {
lastLocation = location;
+ //lastLocationDuplicate = location;
for (LocationUpdateListener listener : locationListeners) {
listener.onLocationChangedSignificantly(LatLng.from(lastLocation));
}
- } else if (isBetterLocation(location, lastLocation)
+ } else if (location.distanceTo(lastLocation) >= 500) {
+ // Update nearby notification card at every 500 meters.
+ for (LocationUpdateListener listener : locationListeners) {
+ listener.onLocationChangedMedium(LatLng.from(lastLocation));
+ }
+ }
+
+ else if (isBetterLocation(location, lastLocation)
.equals(LocationChangeType.LOCATION_SLIGHTLY_CHANGED)) {
lastLocation = location;
+ //lastLocationDuplicate = location;
for (LocationUpdateListener listener : locationListeners) {
listener.onLocationChangedSlightly(LatLng.from(lastLocation));
}
@@ -286,6 +297,7 @@ public class LocationServiceManager implements LocationListener {
public enum LocationChangeType{
LOCATION_SIGNIFICANTLY_CHANGED, //Went out of borders of nearby markers
LOCATION_SLIGHTLY_CHANGED, //User might be walking or driving
+ LOCATION_MEDIUM_CHANGED, //Between slight and significant changes, will be used for nearby card view updates.
LOCATION_NOT_CHANGED,
PERMISSION_JUST_GRANTED,
MAP_UPDATED
diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.java b/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.java
index f3e920e18..61ff26b11 100644
--- a/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.java
+++ b/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.java
@@ -1,6 +1,7 @@
package fr.free.nrw.commons.location;
public interface LocationUpdateListener {
- void onLocationChangedSignificantly(LatLng latLng);
- void onLocationChangedSlightly(LatLng latLng);
+ void onLocationChangedSignificantly(LatLng latLng); // Will be used to update all nearby markers on the map
+ void onLocationChangedSlightly(LatLng latLng); // Will be used to track users motion
+ void onLocationChangedMedium(LatLng latLng); // Will be used updating nearby card view notification
}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
index 43debf323..8f6012720 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
@@ -145,7 +145,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- detailProvider = (MediaDetailPagerFragment.MediaDetailProvider) getActivity();
+ detailProvider = (MediaDetailPagerFragment.MediaDetailProvider) (getParentFragment().getParentFragment());
if (savedInstanceState != null) {
editable = savedInstanceState.getBoolean("editable");
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java
index 824e5f4df..7caa9454a 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java
@@ -38,7 +38,6 @@ import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
import fr.free.nrw.commons.category.CategoryDetailsActivity;
import fr.free.nrw.commons.category.CategoryImagesActivity;
import fr.free.nrw.commons.contributions.Contribution;
-import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.explore.SearchActivity;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
@@ -137,7 +136,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
Timber.d("Returning as activity is destroyed!");
return true;
}
- MediaDetailProvider provider = (MediaDetailProvider) getActivity();
+ MediaDetailProvider provider = (MediaDetailProvider) getParentFragment();
Media m = provider.getMediaAtPosition(pager.getCurrentItem());
switch (item.getItemId()) {
case R.id.menu_bookmark_current_image:
@@ -174,12 +173,12 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
return true;
case R.id.menu_retry_current_image:
// Retry
- ((ContributionsActivity) getActivity()).retryUpload(pager.getCurrentItem());
+ //((MainActivity) getActivity()).retryUpload(pager.getCurrentItem());
getActivity().getSupportFragmentManager().popBackStack();
return true;
case R.id.menu_cancel_current_image:
// todo: delete image
- ((ContributionsActivity) getActivity()).deleteUpload(pager.getCurrentItem());
+ //((MainActivity) getActivity()).deleteUpload(pager.getCurrentItem());
getActivity().getSupportFragmentManager().popBackStack();
return true;
default:
@@ -254,8 +253,8 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
menu.clear(); // see http://stackoverflow.com/a/8495697/17865
inflater.inflate(R.menu.fragment_image_detail, menu);
if (pager != null) {
- MediaDetailProvider provider = (MediaDetailProvider) getActivity();
- if (provider == null) {
+ MediaDetailProvider provider = (MediaDetailProvider) getParentFragment();
+ if(provider == null) {
return;
}
@@ -326,7 +325,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
@Override
public void onPageScrolled(int i, float v, int i2) {
- if (getActivity() == null) {
+ if(getParentFragment().getActivity() == null) {
Timber.d("Returning as activity is destroyed!");
return;
}
@@ -347,7 +346,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
e.printStackTrace();
}
}
- getActivity().supportInvalidateOptionsMenu();
+ getParentFragment().getActivity().supportInvalidateOptionsMenu();
}
@Override
@@ -381,11 +380,11 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
public Fragment getItem(int i) {
if (i == 0) {
// See bug https://code.google.com/p/android/issues/detail?id=27526
- if (getActivity() == null) {
+ if(getParentFragment().getActivity() == null) {
Timber.d("Skipping getItem. Returning as activity is destroyed!");
return null;
}
- pager.postDelayed(() -> getActivity().supportInvalidateOptionsMenu(), 5);
+ pager.postDelayed(() -> getParentFragment().getActivity().supportInvalidateOptionsMenu(), 5);
}
return MediaDetailFragment.forMedia(i, editable, isFeaturedImage);
}
@@ -396,7 +395,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
Timber.d("Skipping getCount. Returning as activity is destroyed!");
return 0;
}
- return ((MediaDetailProvider) getActivity()).getTotalMediaCount();
+ return ((MediaDetailProvider) getParentFragment()).getTotalMediaCount();
}
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/DirectUpload.java b/app/src/main/java/fr/free/nrw/commons/nearby/DirectUpload.java
index ca25fccf3..e7ccd97d5 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/DirectUpload.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/DirectUpload.java
@@ -4,9 +4,11 @@ import android.os.Build;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
+import android.util.Log;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionController;
+import fr.free.nrw.commons.utils.PermissionUtils;
import timber.log.Timber;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
@@ -24,8 +26,9 @@ class DirectUpload {
}
// These permission requests will be handled by the Fragments.
- // Do not use requestCode 1 as it will conflict with NearbyActivity's requestCodes
+ // Do not use requestCode 1 as it will conflict with NearbyFragment's requestCodes
void initiateGalleryUpload() {
+ Log.d("deneme7","initiateGalleryUpload");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(fragment.getActivity(), READ_EXTERNAL_STORAGE) != PERMISSION_GRANTED) {
if (fragment.shouldShowRequestPermissionRationale(READ_EXTERNAL_STORAGE)) {
@@ -33,15 +36,15 @@ class DirectUpload {
.setMessage(fragment.getActivity().getString(R.string.read_storage_permission_rationale))
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
Timber.d("Requesting permissions for read external storage");
- fragment.requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, 4);
+ fragment.getActivity().requestPermissions
+ (new String[]{READ_EXTERNAL_STORAGE}, PermissionUtils.GALLERY_PERMISSION_FROM_NEARBY_MAP);
dialog.dismiss();
})
.setNegativeButton(android.R.string.cancel, null)
.create()
.show();
} else {
- fragment.requestPermissions(new String[]{READ_EXTERNAL_STORAGE},
- 4);
+ fragment.getActivity().requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, PermissionUtils.GALLERY_PERMISSION_FROM_NEARBY_MAP);
}
} else {
controller.startGalleryPick();
@@ -53,20 +56,22 @@ class DirectUpload {
}
void initiateCameraUpload() {
+ Log.d("deneme7","initiateCameraUpload");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(fragment.getActivity(), WRITE_EXTERNAL_STORAGE) != PERMISSION_GRANTED) {
if (fragment.shouldShowRequestPermissionRationale(WRITE_EXTERNAL_STORAGE)) {
new AlertDialog.Builder(fragment.getActivity())
.setMessage(fragment.getActivity().getString(R.string.write_storage_permission_rationale))
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
- fragment.requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE}, 5);
+ fragment.getActivity().requestPermissions
+ (new String[]{WRITE_EXTERNAL_STORAGE}, PermissionUtils.CAMERA_PERMISSION_FROM_NEARBY_MAP);
dialog.dismiss();
})
.setNegativeButton(android.R.string.cancel, null)
.create()
.show();
} else {
- fragment.requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE}, 5);
+ fragment.getActivity().requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE}, PermissionUtils.CAMERA_PERMISSION_FROM_NEARBY_MAP);
}
} else {
controller.startCameraCapture();
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java
index 39deee7ad..a84e86218 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java
@@ -39,6 +39,7 @@ public class NearbyController {
this.prefs = prefs;
}
+
/**
* Prepares Place list to make their distance information update later.
*
@@ -46,15 +47,15 @@ public class NearbyController {
* @return NearbyPlacesInfo a variable holds Place list without distance information
* and boundary coordinates of current Place List
*/
- public NearbyPlacesInfo loadAttractionsFromLocation(LatLng curLatLng) throws IOException {
-
+ public NearbyPlacesInfo loadAttractionsFromLocation(LatLng curLatLng, boolean returnClosestResult) throws IOException {
Timber.d("Loading attractions near %s", curLatLng);
NearbyPlacesInfo nearbyPlacesInfo = new NearbyPlacesInfo();
if (curLatLng == null) {
+ Timber.d("Loading attractions neari, but curLatLng is null");
return null;
}
- List places = nearbyPlaces.getFromWikidataQuery(curLatLng, Locale.getDefault().getLanguage());
+ List places = nearbyPlaces.getFromWikidataQuery(curLatLng, Locale.getDefault().getLanguage(), returnClosestResult);
if (null != places && places.size() > 0) {
LatLng[] boundaryCoordinates = {places.get(0).location, // south
@@ -168,7 +169,7 @@ public class NearbyController {
}
public class NearbyPlacesInfo {
- List placeList; // List of nearby places
- LatLng[] boundaryCoordinates; // Corners of nearby area
+ public List placeList; // List of nearby places
+ public LatLng[] boundaryCoordinates; // Corners of nearby area
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFragment.java
similarity index 65%
rename from app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java
rename to app/src/main/java/fr/free/nrw/commons/nearby/NearbyFragment.java
index ca4d7adef..5cafedcbc 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFragment.java
@@ -5,21 +5,19 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.graphics.Typeface;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.os.Handler;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.design.widget.BottomSheetBehavior;
import android.support.design.widget.Snackbar;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AlertDialog;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
+import android.view.LayoutInflater;
+
import android.view.View;
+import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
@@ -34,11 +32,11 @@ import javax.inject.Named;
import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.contributions.MainActivity;
+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.LocationServiceManager.LocationChangeType;
import fr.free.nrw.commons.location.LocationUpdateListener;
-import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.UriSerializer;
import fr.free.nrw.commons.utils.ViewUtil;
@@ -48,23 +46,18 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
-import uk.co.deanwild.materialshowcaseview.IShowcaseListener;
-import uk.co.deanwild.materialshowcaseview.MaterialShowcaseView;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.PERMISSION_JUST_GRANTED;
-
-public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener,
- WikidataEditListener.WikidataP18EditListener {
-
- private static final int LOCATION_REQUEST = 1;
+public class NearbyFragment extends CommonsDaggerSupportFragment
+ implements LocationUpdateListener,
+ WikidataEditListener.WikidataP18EditListener {
@BindView(R.id.progressBar)
ProgressBar progressBar;
-
@BindView(R.id.bottom_sheet)
LinearLayout bottomSheet;
@BindView(R.id.bottom_sheet_details)
@@ -78,59 +71,127 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
LocationServiceManager locationManager;
@Inject
NearbyController nearbyController;
- @Inject WikidataEditListener wikidataEditListener;
-
@Inject
- @Named("application_preferences") SharedPreferences applicationPrefs;
- private LatLng curLatLng;
- private Bundle bundle;
- private Disposable placesDisposable;
- private boolean lockNearbyView; //Determines if the nearby places needs to be refreshed
- private BottomSheetBehavior bottomSheetBehavior; // Behavior for list bottom sheet
- private BottomSheetBehavior bottomSheetBehaviorForDetails; // Behavior for details bottom sheet
+ WikidataEditListener wikidataEditListener;
+ @Inject
+ @Named("application_preferences")
+ SharedPreferences applicationPrefs;
+
public NearbyMapFragment nearbyMapFragment;
private NearbyListFragment nearbyListFragment;
private static final String TAG_RETAINED_MAP_FRAGMENT = NearbyMapFragment.class.getSimpleName();
private static final String TAG_RETAINED_LIST_FRAGMENT = NearbyListFragment.class.getSimpleName();
- private View listButton; // Reference to list button to use in tutorial
+ private Bundle bundle;
+ private BottomSheetBehavior bottomSheetBehavior; // Behavior for list bottom sheet
+ private BottomSheetBehavior bottomSheetBehaviorForDetails; // Behavior for details bottom sheet
+
+ private LatLng curLatLng;
+ private Disposable placesDisposable;
+ private boolean lockNearbyView; //Determines if the nearby places needs to be refreshed
+ public View view;
+ private Snackbar snackbar;
+
+ private LatLng lastKnownLocation;
private final String NETWORK_INTENT_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
private BroadcastReceiver broadcastReceiver;
- private boolean isListShowcaseAdded = false;
- private boolean isMapShowCaseAdded = false;
-
- private LatLng lastKnownLocation;
-
- private MaterialShowcaseView secondSingleShowCaseView;
+ private boolean onOrientationChanged = false;
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_nearby);
- ButterKnife.bind(this);
- resumeFragment();
- bundle = new Bundle();
-
- initBottomSheetBehaviour();
- initDrawer();
- wikidataEditListener.setAuthenticationStateListener(this);
+ setRetainInstance(true);
}
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_nearby, container, false);
+ ButterKnife.bind(this, view);
+
+ /*// Resume the fragment if exist
+ resumeFragment();*/
+ bundle = new Bundle();
+ initBottomSheetBehaviour();
+ wikidataEditListener.setAuthenticationStateListener(this);
+ this.view = view;
+ return view;
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ if (savedInstanceState != null) {
+ onOrientationChanged = true;
+ refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
+ }
+ }
+
+ /**
+ * Hide or expand bottom sheet according to states of all sheets
+ */
+ public void listOptionMenuIteClicked() {
+ if(bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_COLLAPSED || bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_HIDDEN){
+ bottomSheetBehaviorForDetails.setState(BottomSheetBehavior.STATE_HIDDEN);
+ bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
+ }else if(bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_EXPANDED){
+ bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
+ }
+
+ }
+
+ /**
+ * Resume fragments if they exists
+ */
private void resumeFragment() {
// Find the retained fragment on activity restarts
nearbyMapFragment = getMapFragment();
nearbyListFragment = getListFragment();
}
+ /**
+ * Returns the map fragment added to child fragment manager previously, if exists.
+ */
+ private NearbyMapFragment getMapFragment() {
+ return (NearbyMapFragment) getChildFragmentManager().findFragmentByTag(TAG_RETAINED_MAP_FRAGMENT);
+ }
+
+ private void removeMapFragment() {
+ if (nearbyMapFragment != null) {
+ android.support.v4.app.FragmentManager fm = getFragmentManager();
+ fm.beginTransaction().remove(nearbyMapFragment).commit();
+ nearbyMapFragment = null;
+ }
+ }
+
+
+ /**
+ * Returns the list fragment added to child fragment manager previously, if exists.
+ */
+ private NearbyListFragment getListFragment() {
+ return (NearbyListFragment) getChildFragmentManager().findFragmentByTag(TAG_RETAINED_LIST_FRAGMENT);
+ }
+
+ private void removeListFragment() {
+ if (nearbyListFragment != null) {
+ android.support.v4.app.FragmentManager fm = getFragmentManager();
+ fm.beginTransaction().remove(nearbyListFragment).commit();
+ nearbyListFragment = null;
+ }
+ }
+
+ /**
+ * Initialize bottom sheet behaviour (sheet for map list.) Set height 9/16 of all window.
+ * Add callback for bottom sheet changes, so that we can sync it with bottom sheet for details
+ * (sheet for nearby details)
+ */
private void initBottomSheetBehaviour() {
transparentView.setAlpha(0);
-
- bottomSheet.getLayoutParams().height = getWindowManager()
+ bottomSheet.getLayoutParams().height = getActivity().getWindowManager()
.getDefaultDisplay().getHeight() / 16 * 9;
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
- // TODO initProperBottomSheetBehavior();
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
@@ -149,259 +210,44 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
bottomSheetBehaviorForDetails.setState(BottomSheetBehavior.STATE_HIDDEN);
}
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.menu_nearby, menu);
-
- new Handler().post(() -> {
-
- listButton = findViewById(R.id.action_display_list);
-
- secondSingleShowCaseView = new MaterialShowcaseView.Builder(this)
- .setTarget(listButton)
- .setDismissText(getString(R.string.showcase_view_got_it_button))
- .setContentText(getString(R.string.showcase_view_list_icon))
- .setDelay(500) // optional but starting animations immediately in onCreate can make them choppy
- .singleUse(ViewUtil.SHOWCASE_VIEW_ID_1) // provide a unique ID used to ensure it is only shown once
- .setDismissStyle(Typeface.defaultFromStyle(Typeface.BOLD))
- .setListener(new IShowcaseListener() {
- @Override
- public void onShowcaseDisplayed(MaterialShowcaseView materialShowcaseView) {
-
- }
-
- // If dismissed, we can inform fragment to start showcase sequence there
- @Override
- public void onShowcaseDismissed(MaterialShowcaseView materialShowcaseView) {
- nearbyMapFragment.onNearbyMaterialShowcaseDismissed();
- }
- })
- .build();
-
- isListShowcaseAdded = true;
-
- if (isMapShowCaseAdded) { // If map showcase is also ready, start ShowcaseSequence
- // Probably this case is not possible. Just added to be careful
- setMapViewTutorialShowCase();
- }
- });
-
- return super.onCreateOptionsMenu(menu);
+ public void prepareViewsForSheetPosition(int bottomSheetState) {
+ // TODO
}
@Override
- public boolean onOptionsItemSelected(MenuItem item) {
-
- // Handle item selection
- switch (item.getItemId()) {
- case R.id.action_display_list:
- if (bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_COLLAPSED || bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_HIDDEN){
- bottomSheetBehaviorForDetails.setState(BottomSheetBehavior.STATE_HIDDEN);
- bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
- }else if (bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_EXPANDED){
- bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
- }
-
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- private void requestLocationPermissions() {
- if (!isFinishing()) {
- locationManager.requestPermissions(this);
- }
+ public void onLocationChangedSignificantly(LatLng latLng) {
+ refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
}
@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");
- //Still need to check if GPS is enabled
- checkGps();
- lastKnownLocation = locationManager.getLKL();
- refreshView(PERMISSION_JUST_GRANTED);
- } else {
- //If permission not granted, go to page that says Nearby Places cannot be displayed
- hideProgressBar();
- showLocationPermissionDeniedErrorDialog();
- }
- }
- break;
-
- default:
- // This is needed to allow the request codes from the Fragments to be routed appropriately
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- }
+ public void onLocationChangedSlightly(LatLng latLng) {
+ refreshView(LOCATION_SLIGHTLY_CHANGED);
}
- private void showLocationPermissionDeniedErrorDialog() {
- new AlertDialog.Builder(this)
- .setMessage(R.string.nearby_needs_permissions)
- .setCancelable(false)
- .setPositiveButton(R.string.give_permission, (dialog, which) -> {
- //will ask for the location permission again
- checkGps();
- })
- .setNegativeButton(R.string.cancel, (dialog, which) -> {
- //dismiss dialog and finish activity
- dialog.cancel();
- finish();
- })
- .create()
- .show();
- }
- private void checkGps() {
- if (!locationManager.isProviderEnabled()) {
- Timber.d("GPS is not enabled");
- new AlertDialog.Builder(this)
- .setMessage(R.string.gps_disabled)
- .setCancelable(false)
- .setPositiveButton(R.string.enable_gps,
- (dialog, id) -> {
- Intent callGPSSettingIntent = new Intent(
- android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
- Timber.d("Loaded settings page");
- startActivityForResult(callGPSSettingIntent, 1);
- })
- .setNegativeButton(R.string.menu_cancel_upload, (dialog, id) -> {
- showLocationPermissionDeniedErrorDialog();
- dialog.cancel();
- })
- .create()
- .show();
- } else {
- Timber.d("GPS is enabled");
- checkLocationPermission();
- }
- }
-
- private void checkLocationPermission() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- if (locationManager.isLocationPermissionGranted()) {
- refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
- } else {
- // Should we show an explanation?
- if (locationManager.isPermissionExplanationRequired(this)) {
- // 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(this)
- .setMessage(getString(R.string.location_permission_rationale_nearby))
- .setPositiveButton(android.R.string.ok, (dialog, which) -> {
- requestLocationPermissions();
- dialog.dismiss();
- })
- .setNegativeButton(android.R.string.cancel, (dialog, id) -> {
- showLocationPermissionDeniedErrorDialog();
- dialog.cancel();
- })
- .create()
- .show();
-
- } else {
- // No explanation needed, we can request the permission.
- requestLocationPermissions();
- }
- }
- } else {
- refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
- }
+ @Override
+ public void onLocationChangedMedium(LatLng latLng) {
+ // For nearby map actions, there are no differences between 500 meter location change (aka medium change) and slight change
+ refreshView(LOCATION_SLIGHTLY_CHANGED);
}
@Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (requestCode == 1) {
- Timber.d("User is back from Settings page");
- refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
- }
+ public void onWikidataEditSuccessful() {
+ refreshView(MAP_UPDATED);
}
- @Override
- protected void onStart() {
- super.onStart();
- locationManager.addLocationListener(this);
- registerLocationUpdates();
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (placesDisposable != null) {
- placesDisposable.dispose();
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- lockNearbyView = false;
- checkGps();
- addNetworkBroadcastReceiver();
- }
-
- @Override
- public void onPause() {
- super.onPause();
- // this means that this activity will not be recreated now, user is leaving it
- // or the activity is otherwise finishing
- if (isFinishing()) {
- // we will not need this fragment anymore, this may also be a good place to signal
- // to the retained fragment object to perform its own cleanup.
- removeMapFragment();
- removeListFragment();
-
- }
- unregisterReceiver(broadcastReceiver);
- broadcastReceiver = null;
- locationManager.removeLocationListener(this);
- locationManager.unregisterLocationManager();
-
- }
-
- private void addNetworkBroadcastReceiver() {
- IntentFilter intentFilter = new IntentFilter(NETWORK_INTENT_ACTION);
- Snackbar snackbar = Snackbar.make(transparentView , R.string.no_internet, Snackbar.LENGTH_INDEFINITE);
-
- broadcastReceiver = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (NetworkUtils.isInternetConnectionEstablished(NearbyActivity.this)) {
- refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
- snackbar.dismiss();
- } else {
- snackbar.show();
- }
- }
- };
-
- this.registerReceiver(broadcastReceiver, intentFilter);
- }
-
-
/**
* This method should be the single point to load/refresh nearby places
*
* @param locationChangeType defines if location shanged significantly or slightly
*/
- private void refreshView(LocationChangeType locationChangeType) {
+ private void refreshView(LocationServiceManager.LocationChangeType locationChangeType) {
+ Timber.d("Refreshing nearby places");
if (lockNearbyView) {
return;
}
- if (!NetworkUtils.isInternetConnectionEstablished(this)) {
+ if (!NetworkUtils.isInternetConnectionEstablished(getActivity())) {
hideProgressBar();
return;
}
@@ -411,7 +257,9 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
if (curLatLng != null && curLatLng.equals(lastLocation)
&& !locationChangeType.equals(MAP_UPDATED)) { //refresh view only if location has changed
- return;
+ if (!onOrientationChanged) {
+ return;
+ }
}
curLatLng = lastLocation;
@@ -424,9 +272,15 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
return;
}
+ /*
+ onOrientation changed is true whenever activities orientation changes. After orientation
+ change we want to refresh map significantly, doesn't matter if location changed significantly
+ or not. Thus, we included onOrientatinChanged boolean to if clause
+ */
if (locationChangeType.equals(LOCATION_SIGNIFICANTLY_CHANGED)
|| locationChangeType.equals(PERMISSION_JUST_GRANTED)
- || locationChangeType.equals(MAP_UPDATED)) {
+ || locationChangeType.equals(MAP_UPDATED)
+ || onOrientationChanged) {
progressBar.setVisibility(View.VISIBLE);
//TODO: This hack inserts curLatLng before populatePlaces is called (see #1440). Ideally a proper fix should be found
@@ -438,7 +292,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
bundle.putString("CurLatLng", gsonCurLatLng);
placesDisposable = Observable.fromCallable(() -> nearbyController
- .loadAttractionsFromLocation(curLatLng))
+ .loadAttractionsFromLocation(curLatLng, false))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::populatePlaces,
@@ -458,40 +312,8 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
}
}
- /**
- * This method first checks if the location permissions has been granted and then register the location manager for updates.
- */
- private void registerLocationUpdates() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- if (locationManager.isLocationPermissionGranted()) {
- locationManager.registerLocationManager();
- } else {
- // Should we show an explanation?
- if (locationManager.isPermissionExplanationRequired(this)) {
- new AlertDialog.Builder(this)
- .setMessage(getString(R.string.location_permission_rationale_nearby))
- .setPositiveButton(android.R.string.ok, (dialog, which) -> {
- requestLocationPermissions();
- dialog.dismiss();
- })
- .setNegativeButton(android.R.string.cancel, (dialog, id) -> {
- showLocationPermissionDeniedErrorDialog();
- dialog.cancel();
- })
- .create()
- .show();
-
- } else {
- // No explanation needed, we can request the permission.
- requestLocationPermissions();
- }
- }
- } else {
- locationManager.registerLocationManager();
- }
- }
-
private void populatePlaces(NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
+ Timber.d("Populating nearby places");
List placeList = nearbyPlacesInfo.placeList;
LatLng[] boundaryCoordinates = nearbyPlacesInfo.boundaryCoordinates;
Gson gson = new GsonBuilder()
@@ -502,7 +324,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
String gsonBoundaryCoordinates = gson.toJson(boundaryCoordinates);
if (placeList.size() == 0) {
- ViewUtil.showSnackbar(findViewById(R.id.container), R.string.no_nearby);
+ ViewUtil.showSnackbar(view.findViewById(R.id.container), R.string.no_nearby);
}
bundle.putString("PlaceList", gsonPlaceList);
@@ -523,47 +345,13 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
updateMapFragment(false);
updateListFragment();
}
-
- isMapShowCaseAdded = true;
- }
-
- public void setMapViewTutorialShowCase() {
- /*
- *This showcase view will be the first step of our nearbyMaterialShowcaseSequence. The reason we use a
- * single item instead of adding another step to nearbyMaterialShowcaseSequence is that we are not able to
- * call withoutShape() method on steps. For mapView we need an showcase view without
- * any circle on it, it should cover the whole page.
- * */
- MaterialShowcaseView firstSingleShowCaseView = new MaterialShowcaseView.Builder(this)
- .setTarget(nearbyMapFragment.mapView)
- .setDismissText(getString(R.string.showcase_view_got_it_button))
- .setContentText(getString(R.string.showcase_view_whole_nearby_activity))
- .setDelay(500) // optional but starting animations immediately in onCreate can make them choppy
- .singleUse(ViewUtil.SHOWCASE_VIEW_ID_2) // provide a unique ID used to ensure it is only shown once
- .withoutShape() // no shape on map view since there are no view to focus on
- .setDismissStyle(Typeface.defaultFromStyle(Typeface.BOLD))
- .setListener(new IShowcaseListener() {
- @Override
- public void onShowcaseDisplayed(MaterialShowcaseView materialShowcaseView) {
-
- }
-
- @Override
- public void onShowcaseDismissed(MaterialShowcaseView materialShowcaseView) {
- /* Add other nearbyMaterialShowcaseSequence here, it will make the user feel as they are a
- * nearbyMaterialShowcaseSequence whole together.
- * */
- secondSingleShowCaseView.show(NearbyActivity.this);
- }
- })
- .build();
-
- if (applicationPrefs.getBoolean("firstRunNearby", true)) {
- applicationPrefs.edit().putBoolean("firstRunNearby", false).apply();
- firstSingleShowCaseView.show(this);
- }
}
+ /**
+ * Lock nearby view updates while updating map or list. Because we don't want new update calls
+ * when we already updating for old location update.
+ * @param lock
+ */
private void lockNearbyView(boolean lock) {
if (lock) {
lockNearbyView = true;
@@ -576,53 +364,22 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
}
}
- private void hideProgressBar() {
- if (progressBar != null) {
- progressBar.setVisibility(View.GONE);
- }
- }
-
- private NearbyMapFragment getMapFragment() {
- return (NearbyMapFragment) getSupportFragmentManager().findFragmentByTag(TAG_RETAINED_MAP_FRAGMENT);
- }
-
- private void removeMapFragment() {
- if (nearbyMapFragment != null) {
- android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
- fm.beginTransaction().remove(nearbyMapFragment).commit();
- nearbyMapFragment = null;
- }
- }
-
- private NearbyListFragment getListFragment() {
- return (NearbyListFragment) getSupportFragmentManager().findFragmentByTag(TAG_RETAINED_LIST_FRAGMENT);
- }
-
- private void removeListFragment() {
- if (nearbyListFragment != null) {
- android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
- fm.beginTransaction().remove(nearbyListFragment).commit();
- nearbyListFragment = null;
- }
- }
-
private void updateMapFragment(boolean isSlightUpdate) {
/*
- * Significant update means updating nearby place markers. Slightly update means only
- * updating current location marker and camera target.
- * We update our map Significantly on each 1000 meter change, but we can't never know
- * the frequency of nearby places. Thus we check if we are close to the boundaries of
- * our nearby markers, we update our map Significantly.
- * */
-
+ Significant update means updating nearby place markers. Slightly update means only
+ updating current location marker and camera target.
+ We update our map Significantly on each 1000 meter change, but we can't never know
+ the frequency of nearby places. Thus we check if we are close to the boundaries of
+ our nearby markers, we update our map Significantly.
+ */
NearbyMapFragment nearbyMapFragment = getMapFragment();
if (nearbyMapFragment != null && curLatLng != null) {
hideProgressBar(); // In case it is visible (this happens, not an impossible case)
/*
- * If we are close to nearby places boundaries, we need a significant update to
- * get new nearby places. Check order is south, north, west, east
- * */
+ * If we are close to nearby places boundaries, we need a significant update to
+ * get new nearby places. Check order is south, north, west, east
+ * */
if (nearbyMapFragment.boundaryCoordinates != null
&& (curLatLng.getLatitude() <= nearbyMapFragment.boundaryCoordinates[0].getLatitude()
|| curLatLng.getLatitude() >= nearbyMapFragment.boundaryCoordinates[1].getLatitude()
@@ -630,7 +387,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|| curLatLng.getLongitude() >= nearbyMapFragment.boundaryCoordinates[3].getLongitude())) {
// populate places
placesDisposable = Observable.fromCallable(() -> nearbyController
- .loadAttractionsFromLocation(curLatLng))
+ .loadAttractionsFromLocation(curLatLng, false))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::populatePlaces,
@@ -645,6 +402,15 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
return;
}
+ /*
+ If this is the map update just after orientation change, then it is not a slight update
+ anymore. We want to significantly update map after each orientation change
+ */
+ if (onOrientationChanged) {
+ isSlightUpdate = false;
+ onOrientationChanged = false;
+ }
+
if (isSlightUpdate) {
nearbyMapFragment.setBundleForUpdtes(bundle);
nearbyMapFragment.updateMapSlightly();
@@ -671,7 +437,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
* Calls fragment for map view.
*/
private void setMapFragment() {
- FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
+ FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
nearbyMapFragment = new NearbyMapFragment();
nearbyMapFragment.setArguments(bundle);
fragmentTransaction.replace(R.id.container, nearbyMapFragment, TAG_RETAINED_MAP_FRAGMENT);
@@ -682,7 +448,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
* Calls fragment for list view.
*/
private void setListFragment() {
- FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
+ FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
nearbyListFragment = new NearbyListFragment();
nearbyListFragment.setArguments(bundle);
fragmentTransaction.replace(R.id.container_sheet, nearbyListFragment, TAG_RETAINED_LIST_FRAGMENT);
@@ -691,26 +457,235 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
fragmentTransaction.commitAllowingStateLoss();
}
- @Override
- public void onLocationChangedSignificantly(LatLng latLng) {
- refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
+ private void hideProgressBar() {
+ if (progressBar != null) {
+ progressBar.setVisibility(View.GONE);
+ }
}
- @Override
- public void onLocationChangedSlightly(LatLng latLng) {
- refreshView(LOCATION_SLIGHTLY_CHANGED);
+ /**
+ * This method first checks if the location permissions has been granted and then register the location manager for updates.
+ */
+ private void registerLocationUpdates() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (locationManager.isLocationPermissionGranted()) {
+ locationManager.registerLocationManager();
+ } else {
+ // Should we show an explanation?
+ if (locationManager.isPermissionExplanationRequired(getActivity())) {
+ new AlertDialog.Builder(getActivity())
+ .setMessage(getString(R.string.location_permission_rationale_nearby))
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ requestLocationPermissions();
+ dialog.dismiss();
+ })
+ .setNegativeButton(android.R.string.cancel, (dialog, id) -> {
+ showLocationPermissionDeniedErrorDialog();
+ dialog.cancel();
+ })
+ .create()
+ .show();
+
+ } else {
+ // No explanation needed, we can request the permission.
+ requestLocationPermissions();
+ }
+ }
+ } else {
+ locationManager.registerLocationManager();
+ }
}
- public void prepareViewsForSheetPosition(int bottomSheetState) {
- // TODO
+ private void requestLocationPermissions() {
+ if (!getActivity().isFinishing()) {
+ locationManager.requestPermissions(getActivity());
+ }
+ }
+
+ private void showLocationPermissionDeniedErrorDialog() {
+ new AlertDialog.Builder(getActivity())
+ .setMessage(R.string.nearby_needs_permissions)
+ .setCancelable(false)
+ .setPositiveButton(R.string.give_permission, (dialog, which) -> {
+ //will ask for the location permission again
+ checkGps();
+ })
+ .setNegativeButton(R.string.cancel, (dialog, which) -> {
+ //dismiss dialog and send user to contributions tab instead
+ dialog.cancel();
+ ((MainActivity)getActivity()).viewPager.setCurrentItem(((MainActivity)getActivity()).CONTRIBUTIONS_TAB_POSITION);
+ })
+ .create()
+ .show();
+ }
+
+ /**
+ * Checks device GPS permission first for all API levels
+ */
+ private void checkGps() {
+ Timber.d("checking GPS");
+ if (!locationManager.isProviderEnabled()) {
+ Timber.d("GPS is not enabled");
+ new AlertDialog.Builder(getActivity())
+ .setMessage(R.string.gps_disabled)
+ .setCancelable(false)
+ .setPositiveButton(R.string.enable_gps,
+ (dialog, id) -> {
+ Intent callGPSSettingIntent = new Intent(
+ android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
+ Timber.d("Loaded settings page");
+ startActivityForResult(callGPSSettingIntent, 1);
+ })
+ .setNegativeButton(R.string.menu_cancel_upload, (dialog, id) -> {
+ showLocationPermissionDeniedErrorDialog();
+ dialog.cancel();
+ })
+ .create()
+ .show();
+ } else {
+ Timber.d("GPS is enabled");
+ checkLocationPermission();
+ }
+ }
+
+ /**
+ * This method ideally should be called from inside of CheckGPS method. If device GPS is enabled
+ * then we need to control app specific permissions for >=M devices. For other devices, enabled
+ * GPS is enough for nearby, so directly call refresh view.
+ */
+ private void checkLocationPermission() {
+ Timber.d("Checking location permission");
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (locationManager.isLocationPermissionGranted()) {
+ refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
+ } else {
+ // Should we show an explanation?
+ if (locationManager.isPermissionExplanationRequired(getActivity())) {
+ // 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.location_permission_rationale_nearby))
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ requestLocationPermissions();
+ dialog.dismiss();
+ })
+ .setNegativeButton(android.R.string.cancel, (dialog, id) -> {
+ showLocationPermissionDeniedErrorDialog();
+ dialog.cancel();
+ })
+ .create()
+ .show();
+
+ } else {
+ // No explanation needed, we can request the permission.
+ requestLocationPermissions();
+ }
+ }
+ } else {
+ refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
+ }
}
private void showErrorMessage(String message) {
- ViewUtil.showLongToast(NearbyActivity.this, message);
+ ViewUtil.showLongToast(getActivity(), message);
+ }
+
+ private void addNetworkBroadcastReceiver() {
+ IntentFilter intentFilter = new IntentFilter(NETWORK_INTENT_ACTION);
+ snackbar = Snackbar.make(transparentView , R.string.no_internet, Snackbar.LENGTH_INDEFINITE);
+
+ broadcastReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (snackbar != null) {
+ if (NetworkUtils.isInternetConnectionEstablished(getActivity())) {
+ refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
+ snackbar.dismiss();
+ } else {
+ snackbar.show();
+ }
+ }
+ }
+ };
+
+ getActivity().registerReceiver(broadcastReceiver, intentFilter);
+
}
@Override
- public void onWikidataEditSuccessful() {
- refreshView(MAP_UPDATED);
+ public void onResume() {
+ super.onResume();
+ // Resume the fragment if exist
+ resumeFragment();
+ }
+
+ public void onTabSelected(boolean onOrientationChanged) {
+ Timber.d("On nearby tab selected");
+ this.onOrientationChanged = onOrientationChanged;
+ performNearbyOperations();
+
+ }
+
+ /**
+ * Calls nearby operations in required order.
+ */
+ private void performNearbyOperations() {
+ locationManager.addLocationListener(this);
+ registerLocationUpdates();
+ lockNearbyView = false;
+ checkGps();
+ addNetworkBroadcastReceiver();
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (placesDisposable != null) {
+ placesDisposable.dispose();
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ snackbar = null;
+ broadcastReceiver = null;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ // this means that this activity will not be recreated now, user is leaving it
+ // or the activity is otherwise finishing
+ if(getActivity().isFinishing()) {
+ // we will not need this fragment anymore, this may also be a good place to signal
+ // to the retained fragment object to perform its own cleanup.
+ //removeMapFragment();
+ removeListFragment();
+
+ }
+ if (broadcastReceiver != null) {
+ getActivity().unregisterReceiver(broadcastReceiver);
+ broadcastReceiver = null;
+ }
+
+ if (locationManager != null) {
+ locationManager.removeLocationListener(this);
+ locationManager.unregisterLocationManager();
+ }
}
}
+
+
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java
index b094cbb41..3cf60c31d 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java
@@ -73,7 +73,7 @@ public class NearbyListFragment extends DaggerFragment {
ViewGroup container,
Bundle savedInstanceState) {
Timber.d("NearbyListFragment created");
- View view = inflater.inflate(R.layout.fragment_nearby, container, false);
+ View view = inflater.inflate(R.layout.fragment_nearby_list, container, false);
recyclerView = view.findViewById(R.id.listView);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java
index 69a05a339..628ab72f9 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java
@@ -7,7 +7,6 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
-import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
@@ -16,6 +15,7 @@ import android.support.design.widget.BottomSheetBehavior;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AlertDialog;
+import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -109,7 +109,7 @@ public class NearbyMapFragment extends DaggerFragment {
private Animation fab_close;
private Animation fab_open;
private Animation rotate_forward;
- private ContributionController controller;
+ public ContributionController controller;
private DirectUpload directUpload;
private Place place;
@@ -122,9 +122,7 @@ public class NearbyMapFragment extends DaggerFragment {
private final double CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT = 0.06;
private final double CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE = 0.04;
- private boolean isSecondMaterialShowcaseDismissed;
private boolean isMapReady;
- private MaterialShowcaseView thirdSingleShowCaseView;
private Bundle bundleForUpdtes;// Carry information from activity about changed nearby places and current location
@@ -177,6 +175,13 @@ public class NearbyMapFragment extends DaggerFragment {
setRetainInstance(true);
}
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ }
+
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -214,7 +219,12 @@ public class NearbyMapFragment extends DaggerFragment {
});
}
+ /**
+ * Updates map slightly means it doesn't updates all nearby markers around. It just updates
+ * location tracker marker of user.
+ */
public void updateMapSlightly() {
+ Timber.d("updateMapSlightly called, bundle is:"+bundleForUpdtes);
if (mapboxMap != null) {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Uri.class, new UriDeserializer())
@@ -229,7 +239,13 @@ public class NearbyMapFragment extends DaggerFragment {
}
+ /**
+ * Updates map significantly means it updates nearby markers and location tracker marker. It is
+ * called when user is out of boundaries (south, north, east or west) of markers drawn by
+ * previous nearby call.
+ */
public void updateMapSignificantly() {
+ Timber.d("updateMapSignificantly called, bundle is:"+bundleForUpdtes);
if (mapboxMap != null) {
if (bundleForUpdtes != null) {
Gson gson = new GsonBuilder()
@@ -309,6 +325,12 @@ public class NearbyMapFragment extends DaggerFragment {
}
}
+ /**
+ * Updates camera position according to list sheet status. If list sheet is collapsed, camera
+ * focus should be in the center. If list sheet is expanded, camera focus should be visible
+ * on the gap between list sheet and tab layout.
+ * @param isBottomListSheetExpanded
+ */
private void updateMapCameraAccordingToBottomSheet(boolean isBottomListSheetExpanded) {
CameraPosition position;
this.isBottomListSheetExpanded = isBottomListSheetExpanded;
@@ -345,39 +367,40 @@ public class NearbyMapFragment extends DaggerFragment {
}
private void initViews() {
- bottomSheetList = getActivity().findViewById(R.id.bottom_sheet);
+ Timber.d("initViews called");
+ bottomSheetList = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.bottom_sheet);
bottomSheetListBehavior = BottomSheetBehavior.from(bottomSheetList);
- bottomSheetDetails = getActivity().findViewById(R.id.bottom_sheet_details);
+ bottomSheetDetails = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.bottom_sheet_details);
bottomSheetDetailsBehavior = BottomSheetBehavior.from(bottomSheetDetails);
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
bottomSheetDetails.setVisibility(View.VISIBLE);
- fabPlus = getActivity().findViewById(R.id.fab_plus);
- fabCamera = getActivity().findViewById(R.id.fab_camera);
- fabGallery = getActivity().findViewById(R.id.fab_galery);
- fabRecenter = getActivity().findViewById(R.id.fab_recenter);
+ fabPlus = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.fab_plus);
+ fabCamera = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.fab_camera);
+ fabGallery = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.fab_galery);
+ fabRecenter = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.fab_recenter);
- 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);
+ fab_open = AnimationUtils.loadAnimation(getParentFragment().getActivity(), R.anim.fab_open);
+ fab_close = AnimationUtils.loadAnimation(getParentFragment().getActivity(), R.anim.fab_close);
+ rotate_forward = AnimationUtils.loadAnimation(getParentFragment().getActivity(), R.anim.rotate_forward);
+ rotate_backward = AnimationUtils.loadAnimation(getParentFragment().getActivity(), R.anim.rotate_backward);
- transparentView = getActivity().findViewById(R.id.transparentView);
+ transparentView = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.transparentView);
- description = getActivity().findViewById(R.id.description);
- title = getActivity().findViewById(R.id.title);
- distance = getActivity().findViewById(R.id.category);
- icon = getActivity().findViewById(R.id.icon);
+ description = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.description);
+ title = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.title);
+ distance = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.category);
+ icon = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.icon);
- wikidataButton = getActivity().findViewById(R.id.wikidataButton);
- wikipediaButton = getActivity().findViewById(R.id.wikipediaButton);
- directionsButton = getActivity().findViewById(R.id.directionsButton);
- commonsButton = getActivity().findViewById(R.id.commonsButton);
+ wikidataButton = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.wikidataButton);
+ wikipediaButton = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.wikipediaButton);
+ directionsButton = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.directionsButton);
+ commonsButton = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.commonsButton);
- wikidataButtonText = getActivity().findViewById(R.id.wikidataButtonText);
- wikipediaButtonText = getActivity().findViewById(R.id.wikipediaButtonText);
- directionsButtonText = getActivity().findViewById(R.id.directionsButtonText);
- commonsButtonText = getActivity().findViewById(R.id.commonsButtonText);
+ wikidataButtonText = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.wikidataButtonText);
+ wikipediaButtonText = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.wikipediaButtonText);
+ directionsButtonText = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.directionsButtonText);
+ commonsButtonText = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.commonsButtonText);
bookmarkButton = getActivity().findViewById(R.id.bookmarkButton);
bookmarkButtonImage = getActivity().findViewById(R.id.bookmarkButtonImage);
@@ -494,6 +517,7 @@ public class NearbyMapFragment extends DaggerFragment {
}
private void setupMapView(Bundle savedInstanceState) {
+ Timber.d("setupMapView called");
MapboxMapOptions options = new MapboxMapOptions()
.compassGravity(Gravity.BOTTOM | Gravity.LEFT)
.compassMargins(new int[]{12, 0, 0, 24})
@@ -505,15 +529,19 @@ public class NearbyMapFragment extends DaggerFragment {
.zoom(11)
.build());
- // create map
- mapView = new MapView(getActivity(), options);
- mapView.onCreate(savedInstanceState);
- mapView.getMapAsync(mapboxMap -> {
- ((NearbyActivity)getActivity()).setMapViewTutorialShowCase();
- NearbyMapFragment.this.mapboxMap = mapboxMap;
- updateMapSignificantly();
- });
- mapView.setStyleUrl("asset://mapstyle.json");
+ if (!getParentFragment().getActivity().isFinishing()) {
+ mapView = new MapView(getParentFragment().getActivity(), options);
+ // create map
+ mapView.onCreate(savedInstanceState);
+ mapView.getMapAsync(new OnMapReadyCallback() {
+ @Override
+ public void onMapReady(MapboxMap mapboxMap) {
+ NearbyMapFragment.this.mapboxMap = mapboxMap;
+ updateMapSignificantly();
+ }
+ });
+ mapView.setStyleUrl("asset://mapstyle.json");
+ }
}
/**
@@ -542,6 +570,7 @@ public class NearbyMapFragment extends DaggerFragment {
* move.
*/
private void addCurrentLocationMarker(MapboxMap mapboxMap) {
+ Timber.d("addCurrentLocationMarker is called");
if (currentLocationMarker != null) {
currentLocationMarker.remove(); // Remove previous marker, we are not Hansel and Gretel
}
@@ -564,8 +593,11 @@ public class NearbyMapFragment extends DaggerFragment {
mapboxMap.addPolygon(currentLocationPolygonOptions);
}
+ /**
+ * Adds markers for nearby places to mapbox map
+ */
private void addNearbyMarkerstoMapBoxMap() {
-
+ Timber.d("addNearbyMarkerstoMapBoxMap is called");
mapboxMap.addMarkers(baseMarkerOptions);
mapboxMap.setOnInfoWindowCloseListener(marker -> {
@@ -624,6 +656,12 @@ public class NearbyMapFragment extends DaggerFragment {
return circle;
}
+ /**
+ * If nearby details bottom sheet state is collapsed: show fab plus
+ * If nearby details bottom sheet state is expanded: show fab plus
+ * If nearby details bottom sheet state is hidden: hide all fabs
+ * @param bottomSheetState
+ */
public void prepareViewsForSheetPosition(int bottomSheetState) {
switch (bottomSheetState) {
@@ -648,6 +686,9 @@ public class NearbyMapFragment extends DaggerFragment {
}
}
+ /**
+ * Hides all fabs
+ */
private void hideFAB() {
removeAnchorFromFABs(fabPlus);
@@ -679,25 +720,14 @@ public class NearbyMapFragment extends DaggerFragment {
private void showFAB() {
- addAnchorToBigFABs(fabPlus, getActivity().findViewById(R.id.bottom_sheet_details).getId());
+ addAnchorToBigFABs(fabPlus, ((NearbyFragment)getParentFragment()).view.findViewById(R.id.bottom_sheet_details).getId());
fabPlus.show();
- addAnchorToSmallFABs(fabGallery, getActivity().findViewById(R.id.empty_view).getId());
+ addAnchorToSmallFABs(fabGallery, ((NearbyFragment)getParentFragment()).view.findViewById(R.id.empty_view).getId());
- addAnchorToSmallFABs(fabCamera, getActivity().findViewById(R.id.empty_view1).getId());
- thirdSingleShowCaseView = new MaterialShowcaseView.Builder(this.getActivity())
- .setTarget(fabPlus)
- .setDismissText(getString(R.string.showcase_view_got_it_button))
- .setContentText(getString(R.string.showcase_view_plus_fab))
- .setDelay(500) // optional but starting animations immediately in onCreate can make them choppy
- .singleUse(ViewUtil.SHOWCASE_VIEW_ID_3) // provide a unique ID used to ensure it is only shown once
- .setDismissStyle(Typeface.defaultFromStyle(Typeface.BOLD))
- .build();
+ addAnchorToSmallFABs(fabCamera, ((NearbyFragment)getParentFragment()).view.findViewById(R.id.empty_view1).getId());
isMapReady = true;
- if (isSecondMaterialShowcaseDismissed) {
- thirdSingleShowCaseView.show(getActivity());
- }
}
@@ -724,6 +754,11 @@ public class NearbyMapFragment extends DaggerFragment {
floatingActionButton.setLayoutParams(params);
}
+ /**
+ * Same botom sheet carries information for all nearby places, so we need to pass information
+ * (title, description, distance and links) to view on nearby marker click
+ * @param place Place of clicked nearby marker
+ */
private void passInfoToSheet(Place place) {
this.place = place;
@@ -795,7 +830,7 @@ public class NearbyMapFragment extends DaggerFragment {
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Timber.d("onRequestPermissionsResult: req code = " + " perm = " + permissions + " grant =" + grantResults);
- // Do not use requestCode 1 as it will conflict with NearbyActivity's requestCodes
+ // Do not use requestCode 1 as it will conflict with NearbyFragment's requestCodes
switch (requestCode) {
// 4 = "Read external storage" allowed when gallery selected
case 4: {
@@ -873,18 +908,13 @@ public class NearbyMapFragment extends DaggerFragment {
}
}
+ /**
+ * This bundle is sent whenever and updte for nearby map comes, not for recreation, for updates
+ */
public void setBundleForUpdtes(Bundle bundleForUpdtes) {
this.bundleForUpdtes = bundleForUpdtes;
}
- public void onNearbyMaterialShowcaseDismissed() {
- isSecondMaterialShowcaseDismissed = true;
- if (isMapReady) {
- thirdSingleShowCaseView.show(getActivity());
- }
- }
-
-
@Override
public void onStart() {
if (mapView != null) {
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyNoificationCardView.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyNoificationCardView.java
new file mode 100644
index 000000000..6df87388a
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyNoificationCardView.java
@@ -0,0 +1,278 @@
+package fr.free.nrw.commons.nearby;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.design.widget.CoordinatorLayout;
+import android.support.design.widget.SwipeDismissBehavior;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.widget.CardView;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import android.widget.Toast;
+import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.contributions.MainActivity;
+import fr.free.nrw.commons.utils.ViewUtil;
+import timber.log.Timber;
+
+/**
+ * Custom card view for nearby notification card view on main screen, above contributions list
+ */
+public class NearbyNoificationCardView extends CardView{
+
+ private Context context;
+
+ private Button permissionRequestButton;
+ private RelativeLayout contentLayout;
+ private TextView notificationTitle;
+ private TextView notificationDistance;
+ private ImageView notificationIcon;
+ private ProgressBar progressBar;
+
+ public CardViewVisibilityState cardViewVisibilityState;
+
+ public PermissionType permissionType;
+
+ float x1,x2;
+
+ public NearbyNoificationCardView(@NonNull Context context) {
+ super(context);
+ this.context = context;
+ cardViewVisibilityState = CardViewVisibilityState.INVISIBLE;
+ init();
+ }
+
+ public NearbyNoificationCardView(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ this.context = context;
+ cardViewVisibilityState = CardViewVisibilityState.INVISIBLE;
+ init();
+ }
+
+ public NearbyNoificationCardView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ this.context = context;
+ cardViewVisibilityState = CardViewVisibilityState.INVISIBLE;
+ init();
+ }
+
+ private void init() {
+ View rootView = inflate(context, R.layout.nearby_card_view, this);
+
+ permissionRequestButton = rootView.findViewById(R.id.permission_request_button);
+ contentLayout = rootView.findViewById(R.id.content_layout);
+
+ notificationTitle = rootView.findViewById(R.id.nearby_title);
+ notificationDistance = rootView.findViewById(R.id.nearby_distance);
+
+ notificationIcon = rootView.findViewById(R.id.nearby_icon);
+
+ progressBar = rootView.findViewById(R.id.progressBar);
+
+ setActionListeners();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ // If you don't setVisibility after getting layout params, then you will se an empty space in place of nerabyNotificationCardView
+ if (((MainActivity)context).prefs.getBoolean("displayNearbyCardView", true)) {
+ this.setVisibility(VISIBLE);
+ } else {
+ this.setVisibility(GONE);
+ }
+ }
+
+
+ private void setActionListeners() {
+ this.setOnClickListener(view -> ((MainActivity)context).viewPager.setCurrentItem(1));
+
+ this.setOnTouchListener(
+ (v, event) -> {
+ boolean isSwipe = false;
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ x1 = event.getX();
+ break;
+ case MotionEvent.ACTION_UP:
+ x2 = event.getX();
+ float deltaX = x2 - x1;
+ if (deltaX < 0) {
+ //Right to left swipe
+ isSwipe = true;
+ } else if (deltaX > 0) {
+ //Left to right swipe
+ isSwipe = true;
+ }
+ break;
+ }
+ if (isSwipe) {
+ v.setVisibility(GONE);
+ // Save shared preference for nearby card view accordingly
+ ((MainActivity) context).prefs.edit()
+ .putBoolean("displayNearbyCardView", false).apply();
+ ViewUtil.showLongToast(context, getResources().getString(R.string.nearby_notification_dismiss_message));
+ return true;
+ }
+ return false;
+ });
+ }
+
+ /**
+ * Sets permission request button visible and content layout invisible, then adds correct
+ * permission request actions to permission request button according to PermissionType enum
+ * @param isPermissionRequestButtonNeeded true if permissions missing
+ */
+ public void displayPermissionRequestButton(boolean isPermissionRequestButtonNeeded) {
+ if (isPermissionRequestButtonNeeded) {
+ cardViewVisibilityState = CardViewVisibilityState.ASK_PERMISSION;
+ contentLayout.setVisibility(GONE);
+ permissionRequestButton.setVisibility(VISIBLE);
+
+ if (permissionType == PermissionType.ENABLE_LOCATION_PERMISSON) {
+
+ permissionRequestButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (!((MainActivity)context).isFinishing()) {
+ ((MainActivity) context).locationManager.requestPermissions((MainActivity) context);
+ }
+ }
+ });
+
+ } else if (permissionType == PermissionType.ENABLE_GPS) {
+
+ permissionRequestButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ new AlertDialog.Builder(context)
+ .setMessage(R.string.gps_disabled)
+ .setCancelable(false)
+ .setPositiveButton(R.string.enable_gps,
+ (dialog, id) -> {
+ Intent callGPSSettingIntent = new Intent(
+ android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
+ Timber.d("Loaded settings page");
+ ((MainActivity) context).startActivityForResult(callGPSSettingIntent, 1);
+ })
+ .setNegativeButton(R.string.menu_cancel_upload, (dialog, id) -> {
+ dialog.cancel();
+ displayPermissionRequestButton(true);
+ })
+ .create()
+ .show();
+ }
+ });
+ }
+
+
+ } else {
+ cardViewVisibilityState = CardViewVisibilityState.LOADING;
+ permissionRequestButton.setVisibility(GONE);
+ contentLayout.setVisibility(VISIBLE);
+ // Set visibility of elements in content layout once it become visible
+ progressBar.setVisibility(VISIBLE);
+ notificationTitle.setVisibility(GONE);
+ notificationDistance.setVisibility(GONE);
+ notificationIcon.setVisibility(GONE);
+
+ permissionRequestButton.setVisibility(GONE);
+ }
+ }
+
+ /**
+ * Pass place information to views.
+ * @param isClosestNearbyPlaceFound false if there are no close place
+ * @param place Closes place where we will get information from
+ */
+ public void updateContent(boolean isClosestNearbyPlaceFound, Place place) {
+ if (this.getVisibility() == GONE) {
+ return; // If nearby card view is invisible because of preferences, do nothing
+ }
+ cardViewVisibilityState = CardViewVisibilityState.READY;
+ permissionRequestButton.setVisibility(GONE);
+ contentLayout.setVisibility(VISIBLE);
+ // Make progress bar invisible once data is ready
+ progressBar.setVisibility(GONE);
+ // And content views visible since they are ready
+ notificationTitle.setVisibility(VISIBLE);
+ notificationDistance.setVisibility(VISIBLE);
+ notificationIcon.setVisibility(VISIBLE);
+
+ if (isClosestNearbyPlaceFound) {
+ notificationTitle.setText(place.name);
+ notificationDistance.setText(place.distance);
+ } else {
+ notificationDistance.setText("");
+ notificationTitle.setText(R.string.no_close_nearby);
+ }
+ }
+
+ @Override
+ protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ if (visibility == VISIBLE) {
+ /**
+ * Sometimes we need to preserve previous state of notification card view without getting
+ * any data from user. Ie. wen user came back from Media Details fragment to Contrib List
+ * fragment, we need to know what was the state of card view, and set it to exact same state.
+ */
+ switch (cardViewVisibilityState) {
+ case READY:
+ permissionRequestButton.setVisibility(GONE);
+ contentLayout.setVisibility(VISIBLE);
+ // Make progress bar invisible once data is ready
+ progressBar.setVisibility(GONE);
+ // And content views visible since they are ready
+ notificationTitle.setVisibility(VISIBLE);
+ notificationDistance.setVisibility(VISIBLE);
+ notificationIcon.setVisibility(VISIBLE);
+ break;
+ case LOADING:
+ permissionRequestButton.setVisibility(GONE);
+ contentLayout.setVisibility(VISIBLE);
+ // Set visibility of elements in content layout once it become visible
+ progressBar.setVisibility(VISIBLE);
+ notificationTitle.setVisibility(GONE);
+ notificationDistance.setVisibility(GONE);
+ notificationIcon.setVisibility(GONE);
+ permissionRequestButton.setVisibility(GONE);
+ break;
+ case ASK_PERMISSION:
+ contentLayout.setVisibility(GONE);
+ permissionRequestButton.setVisibility(VISIBLE);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * This states will help us to preserve progress bar and content layout states
+ */
+ public enum CardViewVisibilityState {
+ LOADING,
+ READY,
+ INVISIBLE,
+ ASK_PERMISSION,
+ }
+
+ /**
+ * We need to know which kind of permission we need to request, then update permission request
+ * button action accordingly
+ */
+ public enum PermissionType {
+ ENABLE_GPS,
+ ENABLE_LOCATION_PERMISSON, // For only after Marsmallow
+ NO_PERMISSION_NEEDED
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java
index 9e10ca8c4..6ca4af318 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java
@@ -23,9 +23,9 @@ import timber.log.Timber;
public class NearbyPlaces {
- private static final int MIN_RESULTS = 40;
+ private static int MIN_RESULTS = 40;
private static final double INITIAL_RADIUS = 1.0; // in kilometers
- private static final double MAX_RADIUS = 300.0; // in kilometers
+ private static double MAX_RADIUS = 300.0; // in kilometers
private static final double RADIUS_MULTIPLIER = 1.618;
private static final Uri WIKIDATA_QUERY_URL = Uri.parse("https://query.wikidata.org/sparql");
private static final Uri WIKIDATA_QUERY_UI_URL = Uri.parse("https://query.wikidata.org/");
@@ -41,9 +41,22 @@ public class NearbyPlaces {
}
}
- List getFromWikidataQuery(LatLng curLatLng, String lang) throws IOException {
+ List getFromWikidataQuery(LatLng curLatLng, String lang, boolean returnClosestResult) throws IOException {
List places = Collections.emptyList();
+ /**
+ * If returnClosestResult is true, then this means that we are trying to get closest point
+ * to use in cardView above contributions list
+ */
+ if (returnClosestResult) {
+ MIN_RESULTS = 1; // Return closest nearby place
+ MAX_RADIUS = 5; // Return places only in 5 km area
+ radius = INITIAL_RADIUS; // refresh radius again, otherwise increased radius is grater than MAX_RADIUS, thus returns null
+ } else {
+ MIN_RESULTS = 40;
+ MAX_RADIUS = 300.0; // in kilometers
+ }
+
// increase the radius gradually to find a satisfactory number of nearby places
while (radius <= MAX_RADIUS) {
try {
@@ -150,4 +163,5 @@ public class NearbyPlaces {
return places;
}
+
}
diff --git a/app/src/main/java/fr/free/nrw/commons/notification/Notification.java b/app/src/main/java/fr/free/nrw/commons/notification/Notification.java
index e6d759f66..0116d024c 100644
--- a/app/src/main/java/fr/free/nrw/commons/notification/Notification.java
+++ b/app/src/main/java/fr/free/nrw/commons/notification/Notification.java
@@ -11,13 +11,15 @@ public class Notification {
public String description;
public String link;
public String iconUrl;
+ public String dateWithYear;
- public Notification(NotificationType notificationType, String notificationText, String date, String description, String link, String iconUrl) {
+ public Notification(NotificationType notificationType, String notificationText, String date, String description, String link, String iconUrl, String dateWithYear) {
this.notificationType = notificationType;
this.notificationText = notificationText;
this.date = date;
this.description = description;
this.link = link;
this.iconUrl = iconUrl;
+ this.dateWithYear = dateWithYear;
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java
index 5af23c340..2108b166e 100644
--- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java
@@ -17,6 +17,7 @@ import android.widget.RelativeLayout;
import com.pedrogomez.renderers.RVRendererAdapter;
import java.util.Collections;
+import java.util.Date;
import java.util.List;
import javax.inject.Inject;
@@ -85,7 +86,12 @@ public class NotificationActivity extends NavigationBaseActivity {
private void addNotifications() {
Timber.d("Add notifications");
- if (mNotificationWorkerFragment == null){
+ // Store when add notification is called last
+ long currentDate = new Date(System.currentTimeMillis()).getTime();
+ getSharedPreferences("prefs", MODE_PRIVATE).edit().putLong("last_read_notification_date", currentDate).apply();
+ Timber.d("Set last notification read date to current date:"+ currentDate);
+
+ if(mNotificationWorkerFragment == null){
Observable.fromCallable(() -> {
progressBar.setVisibility(View.VISIBLE);
return controller.getNotifications();
diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationUtils.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationUtils.java
index 095a8a666..7c19c516c 100644
--- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationUtils.java
+++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationUtils.java
@@ -144,7 +144,7 @@ public class NotificationUtils {
notificationText = getWelcomeMessage(context, document);
break;
}
- return new Notification(type, notificationText, getTimestamp(document), description, link, iconUrl);
+ return new Notification(type, notificationText, getTimestamp(document), description, link, iconUrl, getTimestampWithYear(document));
}
private static String getNotificationText(Node document) {
@@ -247,6 +247,14 @@ public class NotificationUtils {
return "";
}
+ private static String getTimestampWithYear(Node document) {
+ Element timestampElement = (Element) getNode(document, "timestamp");
+ if (timestampElement != null) {
+ return timestampElement.getAttribute("utcunix");
+ }
+ return "";
+ }
+
private static String getNotificationDescription(Node document) {
Element titleElement = (Element) getNode(document, "title");
if (titleElement != null) {
diff --git a/app/src/main/java/fr/free/nrw/commons/notification/UnreadNotificationsCheckAsync.java b/app/src/main/java/fr/free/nrw/commons/notification/UnreadNotificationsCheckAsync.java
new file mode 100644
index 000000000..cb504f9c5
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/notification/UnreadNotificationsCheckAsync.java
@@ -0,0 +1,81 @@
+package fr.free.nrw.commons.notification;
+
+import android.os.AsyncTask;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.Date;
+import java.util.List;
+
+import fr.free.nrw.commons.contributions.MainActivity;
+import fr.free.nrw.commons.contributions.ContributionsFragment;
+import timber.log.Timber;
+
+/**
+ * This asynctask will check unread notifications after a date (date user check notifications last)
+ */
+
+public class UnreadNotificationsCheckAsync extends AsyncTask {
+
+ WeakReference context;
+ NotificationController notificationController;
+
+
+ public UnreadNotificationsCheckAsync(MainActivity context, NotificationController notificationController) {
+ this.context = new WeakReference<>(context);
+ this.notificationController = notificationController;
+ }
+
+ @Override
+ protected Notification doInBackground(Void... voids) {
+ Notification lastNotification = null;
+
+ try {
+ lastNotification = findLastNotification(notificationController.getNotifications());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return lastNotification;
+ }
+
+ @Override
+ protected void onPostExecute(Notification lastNotification) {
+ super.onPostExecute(lastNotification);
+
+ if (lastNotification == null) {
+ return;
+ }
+
+ Date lastNotificationCheckDate = new Date(context.get()
+ .getSharedPreferences("prefs",0)
+ .getLong("last_read_notification_date", 0));
+ Timber.d("You may have unread notifications since"+lastNotificationCheckDate);
+
+ boolean isThereUnreadNotifications;
+
+ Date lastReadNotificationDate = new java.util.Date(Long.parseLong(lastNotification.dateWithYear)*1000);
+
+ if (lastNotificationCheckDate.before(lastReadNotificationDate)) {
+ isThereUnreadNotifications = true;
+ } else {
+ isThereUnreadNotifications = false;
+ }
+
+ // Check if activity is still running
+ if (context.get().getWindow().getDecorView().isShown() && !context.get().isFinishing()) {
+ // Check if fragment is not null and visible
+ if (context.get().isContributionsFragmentVisible && context.get().contributionsActivityPagerAdapter.getItem(0) != null) {
+ ((ContributionsFragment)(context.get().contributionsActivityPagerAdapter.getItem(0))).updateNotificationsNotification(isThereUnreadNotifications);
+ }
+ }
+ }
+
+ private Notification findLastNotification(List allNotifications) {
+ if (allNotifications.size() > 0) {
+ return allNotifications.get(allNotifications.size()-1);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.java b/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.java
index 18441146e..6b806d248 100644
--- a/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.java
@@ -8,7 +8,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
-import android.os.Bundle;
import com.dinuscxj.progressbar.CircleProgressBar;
@@ -16,7 +15,7 @@ import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.contributions.ContributionsActivity;
+import fr.free.nrw.commons.contributions.MainActivity;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
@@ -26,16 +25,9 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
-import com.dinuscxj.progressbar.CircleProgressBar;
-
import java.io.File;
import java.io.FileOutputStream;
-import butterknife.BindView;
-import butterknife.ButterKnife;
-import butterknife.OnClick;
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.contributions.ContributionsActivity;
/**
* Displays the final score of quiz and congratulates the user
@@ -63,7 +55,7 @@ public class QuizResultActivity extends AppCompatActivity {
setScore(score);
}else{
startActivityWithFlags(
- this, ContributionsActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
+ this, MainActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
Intent.FLAG_ACTIVITY_SINGLE_TOP);
super.onBackPressed();
}
@@ -87,14 +79,14 @@ public class QuizResultActivity extends AppCompatActivity {
@OnClick(R.id.quiz_result_next)
public void launchContributionActivity(){
startActivityWithFlags(
- this, ContributionsActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
+ this, MainActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
Intent.FLAG_ACTIVITY_SINGLE_TOP);
}
@Override
public void onBackPressed() {
startActivityWithFlags(
- this, ContributionsActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
+ this, MainActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
Intent.FLAG_ACTIVITY_SINGLE_TOP);
super.onBackPressed();
}
diff --git a/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java b/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java
index 77c94ca1d..763a6d09a 100644
--- a/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java
@@ -13,6 +13,7 @@ import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
+import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -32,11 +33,9 @@ import fr.free.nrw.commons.R;
import fr.free.nrw.commons.WelcomeActivity;
import fr.free.nrw.commons.achievements.AchievementsActivity;
import fr.free.nrw.commons.auth.LoginActivity;
-import fr.free.nrw.commons.bookmarks.BookmarksActivity;
-import fr.free.nrw.commons.contributions.ContributionsActivity;
+import fr.free.nrw.commons.contributions.MainActivity;
import fr.free.nrw.commons.category.CategoryImagesActivity;
-import fr.free.nrw.commons.contributions.ContributionsActivity;
-import fr.free.nrw.commons.nearby.NearbyActivity;
+import fr.free.nrw.commons.bookmarks.BookmarksActivity;
import fr.free.nrw.commons.notification.NotificationActivity;
import fr.free.nrw.commons.settings.SettingsActivity;
import timber.log.Timber;
@@ -91,6 +90,23 @@ public abstract class NavigationBaseActivity extends BaseActivity
}
}
+ public void changeDrawerIconToBakcButton() {
+ toggle.setDrawerIndicatorEnabled(false);
+ toggle.setHomeAsUpIndicator(R.drawable.ic_arrow_back_white);
+ toggle.setToolbarNavigationClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onBackPressed();
+ }
+ });
+ }
+
+ public void changeDrawerIconToDefault() {
+ if (toggle != null) {
+ toggle.setDrawerIndicatorEnabled(true);
+ }
+ }
+
/**
* Set the username in navigationHeader.
*/
@@ -156,13 +172,9 @@ public abstract class NavigationBaseActivity extends BaseActivity
case R.id.action_home:
drawerLayout.closeDrawer(navigationView);
startActivityWithFlags(
- this, ContributionsActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
+ this, MainActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
Intent.FLAG_ACTIVITY_SINGLE_TOP);
return true;
- case R.id.action_nearby:
- drawerLayout.closeDrawer(navigationView);
- startActivityWithFlags(this, NearbyActivity.class, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
- return true;
case R.id.action_about:
drawerLayout.closeDrawer(navigationView);
startActivityWithFlags(this, AboutActivity.class, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/DetectUnwantedPicturesAsync.java b/app/src/main/java/fr/free/nrw/commons/upload/DetectUnwantedPicturesAsync.java
index 5a413e49a..342869074 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/DetectUnwantedPicturesAsync.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/DetectUnwantedPicturesAsync.java
@@ -10,7 +10,7 @@ import java.io.IOException;
import java.lang.ref.WeakReference;
import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.contributions.ContributionsActivity;
+import fr.free.nrw.commons.contributions.MainActivity;
import fr.free.nrw.commons.utils.ImageUtils;
import timber.log.Timber;
@@ -63,8 +63,8 @@ public class DetectUnwantedPicturesAsync extends AsyncTask {
- //user does not wish to upload the picture, take them back to ContributionsActivity
- Intent intent = new Intent(activity, ContributionsActivity.class);
+ //user does not wish to upload the picture, take them back to MainActivity
+ Intent intent = new Intent(activity, MainActivity.class);
dialogInterface.dismiss();
activity.startActivity(intent);
});
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ExistingFileAsync.java b/app/src/main/java/fr/free/nrw/commons/upload/ExistingFileAsync.java
index 7d59a7568..08669ed9e 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/ExistingFileAsync.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/ExistingFileAsync.java
@@ -10,7 +10,7 @@ import java.io.IOException;
import java.lang.ref.WeakReference;
import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.contributions.ContributionsActivity;
+import fr.free.nrw.commons.contributions.MainActivity;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber;
@@ -77,8 +77,8 @@ public class ExistingFileAsync extends AsyncTask {
builder.setMessage(R.string.file_exists)
.setTitle(R.string.warning);
builder.setPositiveButton(R.string.no, (dialog, id) -> {
- //Go back to ContributionsActivity
- Intent intent = new Intent(context.get(), ContributionsActivity.class);
+ //Go back to MainActivity
+ Intent intent = new Intent(context.get(), MainActivity.class);
context.get().startActivity(intent);
callback.onResult(Result.DUPLICATE_CANCELLED);
});
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java
index 2eae7b8f5..823a4b91f 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java
@@ -36,7 +36,7 @@ import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionDao;
-import fr.free.nrw.commons.contributions.ContributionsActivity;
+import fr.free.nrw.commons.contributions.MainActivity;
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.mwapi.UploadResult;
@@ -193,7 +193,7 @@ public class UploadService extends HandlerService {
.setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload))
.setOngoing(true)
.setProgress(100, 0, true)
- .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, ContributionsActivity.class), 0))
+ .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0))
.setTicker(getString(R.string.upload_progress_notification_title_in_progress, contribution.getDisplayTitle()));
}
@@ -318,7 +318,7 @@ public class UploadService extends HandlerService {
Notification failureNotification = new NotificationCompat.Builder(this).setAutoCancel(true)
.setSmallIcon(R.drawable.ic_launcher)
.setAutoCancel(true)
- .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, ContributionsActivity.class), 0))
+ .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0))
.setTicker(getString(R.string.upload_failed_notification_title, contribution.getDisplayTitle()))
.setContentTitle(getString(R.string.upload_failed_notification_title, contribution.getDisplayTitle()))
.setContentText(getString(R.string.upload_failed_notification_subtitle))
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/ContributionListViewUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/ContributionListViewUtils.java
new file mode 100644
index 000000000..6dc9625d7
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/utils/ContributionListViewUtils.java
@@ -0,0 +1,39 @@
+package fr.free.nrw.commons.utils;
+
+import android.util.Log;
+import android.view.View;
+
+/**
+ * This class includes utilities for contribution list fragment indicators, such as number of
+ * uploads, notification and nearby cards and their progress bar behind them.
+ */
+public class ContributionListViewUtils {
+
+ /**
+ * Sets indicator and progress bar visibility according to 3 states, data is ready to display,
+ * data still loading, both should be invisible because media details fragment is visible
+ * @param indicator this can be numOfUploads text view, notification/nearby card views
+ * @param progressBar this is the progress bar behind indicators, displays they are loading
+ * @param isIndicatorReady is indicator fetched the information will be displayed
+ * @param isBothInvisible true if contribution list fragment is not active (ie. Media Details Fragment is active)
+ */
+ public static void setIndicatorVisibility(View indicator, View progressBar, boolean isIndicatorReady, boolean isBothInvisible) {
+ if (indicator!=null && progressBar!=null) {
+ if (isIndicatorReady) {
+ // Indicator ready, display them
+ indicator.setVisibility(View.VISIBLE);
+ progressBar.setVisibility(View.GONE);
+ } else {
+ if (isBothInvisible) {
+ // Media Details Fragment is visible, hide both
+ indicator.setVisibility(View.GONE);
+ progressBar.setVisibility(View.GONE);
+ } else {
+ // Indicator is not ready, still loading
+ indicator.setVisibility(View.GONE);
+ progressBar.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.java
index 3429ef403..8595634d5 100644
--- a/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.java
+++ b/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.java
@@ -11,6 +11,11 @@ import fr.free.nrw.commons.CommonsApplication;
public class PermissionUtils {
+ public static final int CAMERA_PERMISSION_FROM_CONTRIBUTION_LIST = 100;
+ public static final int GALLERY_PERMISSION_FROM_CONTRIBUTION_LIST = 101;
+ public static final int CAMERA_PERMISSION_FROM_NEARBY_MAP = 102;
+ public static final int GALLERY_PERMISSION_FROM_NEARBY_MAP = 103;
+
/**
* This method can be used by any activity which requires a permission which has been blocked(marked never ask again by the user)
It open the app settings from where the user can manually give us the required permission.
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/ViewUtil.java b/app/src/main/java/fr/free/nrw/commons/utils/ViewUtil.java
index ed6513ca2..a2c25c948 100644
--- a/app/src/main/java/fr/free/nrw/commons/utils/ViewUtil.java
+++ b/app/src/main/java/fr/free/nrw/commons/utils/ViewUtil.java
@@ -5,7 +5,9 @@ import android.content.Context;
import android.support.design.widget.Snackbar;
import android.view.Display;
import android.view.View;
+import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
+import android.widget.PopupWindow;
import android.widget.Toast;
public class ViewUtil {
@@ -49,4 +51,15 @@ public class ViewUtil {
}
}
+ public static void displayPopupWindow(View anchorView, Context context, View popupWindowLayout, String text) {
+
+ PopupWindow popup = new PopupWindow(context);
+ popup.setContentView(popupWindowLayout);
+ // Closes the popup window when touch outside of it - when looses focus
+ popup.setOutsideTouchable(true);
+ popup.setFocusable(true);
+ // Show anchored to button
+ popup.showAsDropDown(anchorView);
+ }
+
}
diff --git a/app/src/main/res/drawable-hdpi/ic_arrow_back_white.png b/app/src/main/res/drawable-hdpi/ic_arrow_back_white.png
new file mode 100644
index 000000000..0b78dc8c4
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_arrow_back_white.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_notification_white_clip_art.png b/app/src/main/res/drawable-hdpi/ic_notification_white_clip_art.png
new file mode 100644
index 000000000..d974d19d5
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_notification_white_clip_art.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_notification_white_clip_art_dot.png b/app/src/main/res/drawable-hdpi/ic_notification_white_clip_art_dot.png
new file mode 100644
index 000000000..b03310f55
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_notification_white_clip_art_dot.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_arrow_back_white.png b/app/src/main/res/drawable-mdpi/ic_arrow_back_white.png
new file mode 100644
index 000000000..8d1142f31
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_arrow_back_white.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_notification_white_clip_art.png b/app/src/main/res/drawable-mdpi/ic_notification_white_clip_art.png
new file mode 100644
index 000000000..f9cb42735
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_notification_white_clip_art.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_notification_white_clip_art_dot.png b/app/src/main/res/drawable-mdpi/ic_notification_white_clip_art_dot.png
new file mode 100644
index 000000000..ebb4f86c0
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_notification_white_clip_art_dot.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_arrow_back_white.png b/app/src/main/res/drawable-xhdpi/ic_arrow_back_white.png
new file mode 100644
index 000000000..f782543d3
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_arrow_back_white.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_notification_white_clip_art.png b/app/src/main/res/drawable-xhdpi/ic_notification_white_clip_art.png
new file mode 100644
index 000000000..62369be0a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_notification_white_clip_art.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_notification_white_clip_art_dot.png b/app/src/main/res/drawable-xhdpi/ic_notification_white_clip_art_dot.png
new file mode 100644
index 000000000..c6be29890
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_notification_white_clip_art_dot.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_arrow_back_white.png b/app/src/main/res/drawable-xxhdpi/ic_arrow_back_white.png
new file mode 100644
index 000000000..2fe75945f
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_arrow_back_white.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_notification_white_clip_art.png b/app/src/main/res/drawable-xxhdpi/ic_notification_white_clip_art.png
new file mode 100644
index 000000000..907c922fc
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_notification_white_clip_art.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_notification_white_clip_art_dot.png b/app/src/main/res/drawable-xxhdpi/ic_notification_white_clip_art_dot.png
new file mode 100644
index 000000000..239de1d73
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_notification_white_clip_art_dot.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_arrow_back_white.png b/app/src/main/res/drawable-xxxhdpi/ic_arrow_back_white.png
new file mode 100644
index 000000000..575d5075c
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_arrow_back_white.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_notification_white_clip_art.png b/app/src/main/res/drawable-xxxhdpi/ic_notification_white_clip_art.png
new file mode 100644
index 000000000..88ba092c1
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_notification_white_clip_art.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_notification_white_clip_art_dot.png b/app/src/main/res/drawable-xxxhdpi/ic_notification_white_clip_art_dot.png
new file mode 100644
index 000000000..c5a5e3f1f
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_notification_white_clip_art_dot.png differ
diff --git a/app/src/main/res/drawable/ic_location_white_24dp.xml b/app/src/main/res/drawable/ic_location_white_24dp.xml
new file mode 100644
index 000000000..aea56cb5a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_location_white_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_notifications_active_white_24dp.xml b/app/src/main/res/drawable/ic_notifications_active_white_24dp.xml
new file mode 100644
index 000000000..5d6921643
--- /dev/null
+++ b/app/src/main/res/drawable/ic_notifications_active_white_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_notifications_white_24dp.xml b/app/src/main/res/drawable/ic_notifications_white_24dp.xml
new file mode 100644
index 000000000..120895c4f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_notifications_white_24dp.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/app/src/main/res/layout/activity_contributions.xml b/app/src/main/res/layout/activity_contributions.xml
index 51a48f0a5..2e6e92728 100644
--- a/app/src/main/res/layout/activity_contributions.xml
+++ b/app/src/main/res/layout/activity_contributions.xml
@@ -1,34 +1,51 @@
+ android:layout_height="match_parent"
+ android:background="?attr/contributionsListBackground"
+ >
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+ >
+
+
+ android:layout_below="@id/tab_layout">
-
+ android:layout_height="match_parent" />
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_contributions.xml b/app/src/main/res/layout/fragment_contributions.xml
index a016d752c..dd1959178 100644
--- a/app/src/main/res/layout/fragment_contributions.xml
+++ b/app/src/main/res/layout/fragment_contributions.xml
@@ -1,51 +1,22 @@
-
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical">
-
+
-
+
+
-
-
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_contributions_list.xml b/app/src/main/res/layout/fragment_contributions_list.xml
new file mode 100644
index 000000000..719283336
--- /dev/null
+++ b/app/src/main/res/layout/fragment_contributions_list.xml
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_nearby.xml b/app/src/main/res/layout/fragment_nearby.xml
index ef580fe99..4269f135b 100644
--- a/app/src/main/res/layout/fragment_nearby.xml
+++ b/app/src/main/res/layout/fragment_nearby.xml
@@ -1,13 +1,161 @@
-
-
-
+
+ android:layout_height="match_parent">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
diff --git a/app/src/main/res/layout/fragment_nearby_list.xml b/app/src/main/res/layout/fragment_nearby_list.xml
new file mode 100644
index 000000000..ef580fe99
--- /dev/null
+++ b/app/src/main/res/layout/fragment_nearby_list.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/app/src/main/res/layout/nearby_card_view.xml b/app/src/main/res/layout/nearby_card_view.xml
new file mode 100644
index 000000000..fe52e219a
--- /dev/null
+++ b/app/src/main/res/layout/nearby_card_view.xml
@@ -0,0 +1,96 @@
+
+
+
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/nearby_info_popup_layout.xml b/app/src/main/res/layout/nearby_info_popup_layout.xml
new file mode 100644
index 000000000..04c1a2d54
--- /dev/null
+++ b/app/src/main/res/layout/nearby_info_popup_layout.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/contribution_activity_notification_menu.xml b/app/src/main/res/menu/contribution_activity_notification_menu.xml
new file mode 100644
index 000000000..b480d917b
--- /dev/null
+++ b/app/src/main/res/menu/contribution_activity_notification_menu.xml
@@ -0,0 +1,14 @@
+
diff --git a/app/src/main/res/menu/drawer.xml b/app/src/main/res/menu/drawer.xml
index 2adccb8e2..70ac42550 100644
--- a/app/src/main/res/menu/drawer.xml
+++ b/app/src/main/res/menu/drawer.xml
@@ -11,11 +11,6 @@
android:id="@+id/action_home"
android:icon="@drawable/ic_home_black_24dp"
android:title="@string/navigation_item_home" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index a4c9ef716..c305de897 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -16,6 +16,12 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index bad958661..9cf0363f5 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -3,6 +3,7 @@
#303030
#fafafa
+ #1a1a1a
#0c609c
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a14377ccc..103be0ac6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -69,8 +69,8 @@
- @string/contributions_subtitle_zero
- - %1$d upload
- - %1$d uploads
+ - (%1$d)
+ - (%1$d)
- Starting %1$d upload
@@ -354,8 +354,18 @@
Error occurred!
Commons Notification
+
+ Contributions
+ Nearby
+ Notifications
+ Display nearby notification
+ Tap here to see the nearest place that needs pictures
+ No nearby places found close to you
+ List
+
Storage Permission
We need your permission to access the external storage of your device in order to upload images.
+ You won\'t see the nearest place that needs pictures anymore. However, you can re-enable this notification in Settings if you wish.
Bookmarks
Bookmarks
Pictures
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 26d9c357b..70bec86a3 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -1,6 +1,8 @@
+
+
+
+