diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java index b79cfa083..731da3fbf 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java @@ -22,7 +22,7 @@ import fr.free.nrw.commons.upload.UploadService; import static android.content.ContentResolver.requestSync; -public class ContributionsActivity extends AuthenticatedActivity { +public class ContributionsActivity extends AuthenticatedActivity implements FragmentManager.OnBackStackChangedListener { @Inject SessionManager sessionManager; @@ -178,6 +178,11 @@ public class ContributionsActivity extends AuthenticatedActivity { } } + @Override + public void onBackStackChanged() { + + } + public class ContributionsActivityPagerAdapter extends FragmentPagerAdapter { FragmentManager fragmentManager; private boolean isContributionsListFragment = true; // to know what to put in first tab, Contributions of Media Details diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java index 077ada12d..d5f314fa1 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java @@ -1,6 +1,5 @@ package fr.free.nrw.commons.contributions; -import android.app.FragmentManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -12,6 +11,7 @@ import android.graphics.PorterDuff; import android.os.Bundle; import android.os.IBinder; import android.support.annotation.Nullable; +import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.content.ContextCompat; @@ -30,6 +30,7 @@ import android.widget.TextView; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CountDownLatch; import javax.inject.Inject; import javax.inject.Named; @@ -44,8 +45,10 @@ import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.nearby.NearbyPlaces; import fr.free.nrw.commons.notification.Notification; import fr.free.nrw.commons.notification.NotificationController; +import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.upload.UploadService; import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; import io.reactivex.schedulers.Schedulers; import timber.log.Timber; @@ -75,7 +78,8 @@ public class ContributionsFragment private Cursor allContributions; private UploadService uploadService; private boolean isUploadServiceConnected; - + private CompositeDisposable compositeDisposable = new CompositeDisposable(); + CountDownLatch waitForContributionsListFragment = new CountDownLatch(1); public TextView numberOfUploads; public ProgressBar numberOfUploadsProgressBar; @@ -332,6 +336,11 @@ public class ContributionsFragment return contributionsListFragment.getAdapter().getCount(); } + @Override + public void notifyDatasetChanged() { + + } + @Override public void registerDataSetObserver(DataSetObserver observer) { Adapter adapter = contributionsListFragment.getAdapter(); @@ -354,12 +363,41 @@ public class ContributionsFragment @SuppressWarnings("ConstantConditions") private void setUploadCount() { + if (getChildFragmentManager().findFragmentByTag(MEDIA_DETAIL_PAGER_FRAGMENT_TAG) != null) { + // Means Media Details Fragment is active + // TODO: Neslihan ContributionListViewUtils.setIndicatorVisibility(numberOfUploads, numberOfUploadsProgressBar,false, true); + } else { + // Means Contribution List Fragment is visible to user + // TODO: Neslihan ContributionListViewUtils.setIndicatorVisibility(numberOfUploads, numberOfUploadsProgressBar,false, false); + } + compositeDisposable.add(mediaWikiApi + .getUploadCount(((ContributionsActivity)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() + || numberOfUploads == null + || getResources() == null) { + return; + } + numberOfUploads.setText(getResources() + .getQuantityString(R.plurals.contributions_subtitle, + uploadCount, uploadCount)); + if (getChildFragmentManager().findFragmentByTag(MEDIA_DETAIL_PAGER_FRAGMENT_TAG) != null) { + // Means Media Details Fragment is active + // TODO: Neslihan ContributionListViewUtils.setIndicatorVisibility(numberOfUploads, numberOfUploadsProgressBar,false, true); + + } else { + // Means Contribution List Fragment is visible to user + // TODO: Neslihan ContributionListViewUtils.setIndicatorVisibility(numberOfUploads, numberOfUploadsProgressBar,true, false); + } } public void betaSetUploadCount(int betaUploadCount) { @@ -381,5 +419,35 @@ public class ContributionsFragment public void updateNearbyNotification(List nearbyPlaces) { } + + @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(); + boolean isSettingsChanged = prefs.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false); + prefs.edit().putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false).apply(); + if (isSettingsChanged) { + refreshSource(); + } + } + + @Override + public void onDestroy() { + compositeDisposable.clear(); + getChildFragmentManager().removeOnBackStackChangedListener(this); + super.onDestroy(); + + if (isUploadServiceConnected) { + if (getActivity() != null) { + getActivity().unbindService(uploadServiceConnection); + } + } + } } 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 3faf040fb..62fdb86c0 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,44 +1,32 @@ package fr.free.nrw.commons.contributions; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.os.Build; +import android.content.Context; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AlertDialog; +import android.support.annotation.Nullable; +import android.support.design.widget.FloatingActionButton; 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 android.widget.TextView; -import java.util.Arrays; - -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.di.CommonsDaggerSupportFragment; -import fr.free.nrw.commons.nearby.NearbyActivity; -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,252 +35,108 @@ 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; - @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_list, container, false); - ButterKnife.bind(this, v); + private boolean isFabOpen = false; - 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()); changeProgressBarVisibility(true); - return v; + return view; + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + initializeAnimations(); + setListeners(); + } + + 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); + } + + private void setListeners() { + fabPlus.setOnClickListener(view -> animateFAB(isFabOpen)); + } + + 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 onAttach(Context context) { + super.onAttach(context); + ContributionsFragment parentFragment = (ContributionsFragment)getParentFragment(); + parentFragment.waitForContributionsListFragment.countDown(); + } + + /** + * 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); } 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: Neslihan open later ((ContributionsActivity) getActivity()).betaSetUploadCount(adapter.getCount()); + //TODO: add betaSetUploadCount method + ((ContributionsFragment) getParentFragment()).betaSetUploadCount(adapter.getCount()); } } - public void changeProgressBarVisibility(boolean isVisible) { - this.progressBar.setVisibility(isVisible ? View.VISIBLE : View.GONE); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - if (outState == null) { - outState = new Bundle(); - } - super.onSaveInstanceState(outState); - controller.saveState(outState); - outState.putInt("grid-position", contributionsList.getFirstVisiblePosition()); - } - - @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); - } - } 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 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - - // Here, thisActivity is the current activity - if (ContextCompat.checkSelfPermission(getActivity(), - READ_EXTERNAL_STORAGE) - != PERMISSION_GRANTED) { - - // Should we show an explanation? - if (shouldShowRequestPermissionRationale(READ_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.read_storage_permission_rationale)) - .setPositiveButton(android.R.string.ok, (dialog, which) -> { - requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, 1); - dialog.dismiss(); - }) - .setNegativeButton(android.R.string.cancel, null) - .create() - .show(); - - } else { - - // No explanation needed, we can request the permission. - - requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, - 1); - - // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an - // app-defined int constant. The callback method gets the - // result of the request. - } - } 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); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, - @NonNull int[] grantResults) { - Timber.d("onRequestPermissionsResult: req code = " + " perm = " - + Arrays.toString(permissions) + " grant =" + Arrays.toString(grantResults)); - - switch (requestCode) { - // 1 = Storage allowed when gallery selected - case 1: { - if (grantResults.length > 0 && grantResults[0] == PERMISSION_GRANTED) { - Timber.d("Call controller.startGalleryPick()"); - controller.startGalleryPick(); - } - } - break; - // 2 = Location allowed when 'nearby places' selected - case 2: { - if (grantResults.length > 0 && grantResults[0] == PERMISSION_GRANTED) { - Timber.d("Location permission granted"); - Intent nearbyIntent = new Intent(getActivity(), NearbyActivity.class); - startActivity(nearbyIntent); - } - } - break; - case 3: { - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - Timber.d("Call controller.startCameraCapture()"); - controller.startCameraCapture(); - } - } - } - } - - @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); - } - - protected void clearSyncMessage() { - waitingMessage.setVisibility(GONE); - } - public interface SourceRefresher { void refreshSource(); }