diff --git a/CHANGELOG.md b/CHANGELOG.md index 46debc88a..6a4866d40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Wikimedia Commons for Android +## v2.9.0 +- New main screen UI with Nearby tab +- New upload UI and flow +- Multiple uploads +- Send Log File revamp +- Fixed issues with wrong "image taken" date +- Fixed default zoom level in Nearby map +- Incremented target SDK to 27, with corresponding notification channel fix +- Removed several redundant libraries to reduce bloat + ## v2.8.5 - Fixed issues with sporadic upload failures due to wrong mimeType diff --git a/app/build.gradle b/app/build.gradle index 6118d99d5..dd17ee725 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -89,8 +89,8 @@ android { defaultConfig { applicationId 'fr.free.nrw.commons' - versionCode 92 - versionName '2.8.5' + versionCode 94 + versionName '2.9.0' setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName()) minSdkVersion 15 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 eea4e25d5..50666e65a 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,5 +1,6 @@ package fr.free.nrw.commons.contributions; +import android.Manifest; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -20,12 +21,14 @@ 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.support.v7.app.AlertDialog; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Adapter; import android.widget.AdapterView; +import android.widget.CheckBox; +import android.widget.CompoundButton; import java.util.ArrayList; import java.util.concurrent.CountDownLatch; @@ -50,6 +53,8 @@ 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 fr.free.nrw.commons.utils.DialogUtil; +import fr.free.nrw.commons.utils.ViewUtil; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; @@ -102,10 +107,11 @@ public class ContributionsFragment private LatLng curLatLng; private boolean firstLocationUpdate = true; - private LocationServiceManager locationManager; + public LocationServiceManager locationManager; private boolean isFragmentAttachedBefore = false; - + private View checkBoxView; + private CheckBox checkBox; /** * Since we will need to use parent activity on onAuthCookieAcquired, we have to wait @@ -137,6 +143,18 @@ public class ContributionsFragment 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); + checkBoxView = View.inflate(getActivity(), R.layout.nearby_permission_dialog, null); + checkBox = (CheckBox) checkBoxView.findViewById(R.id.never_ask_again); + checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + // Do not ask for permission on activity start again + prefs.edit().putBoolean("displayLocationPermissionForCardView",false).apply(); + } + } + }); if (savedInstanceState != null) { mediaDetailPagerFragment = (MediaDetailPagerFragment)getChildFragmentManager().findFragmentByTag(MEDIA_DETAIL_PAGER_FRAGMENT_TAG); @@ -186,7 +204,9 @@ public class ContributionsFragment // show nearby card view on contributions list is visible if (nearbyNoificationCardView != null) { if (prefs.getBoolean("displayNearbyCardView", true)) { - nearbyNoificationCardView.setVisibility(View.VISIBLE); + if (nearbyNoificationCardView.cardViewVisibilityState == NearbyNoificationCardView.CardViewVisibilityState.READY) { + nearbyNoificationCardView.setVisibility(View.VISIBLE); + } } else { nearbyNoificationCardView.setVisibility(View.GONE); } @@ -312,16 +332,24 @@ public class ContributionsFragment @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + Timber.d("onRequestPermissionsResult"); 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); + if (prefs.getBoolean("displayLocationPermissionForCardView", true)) { + // Still ask for permission + DialogUtil.showAlertDialog(getActivity(), + getString(R.string.nearby_card_permission_title), + getString(R.string.nearby_card_permission_explanation), + () -> displayYouWontSeeNearbyMessage(), + () -> enableLocationPermission(), + checkBoxView, + false); + } } } break; @@ -499,9 +527,10 @@ public class ContributionsFragment if (prefs.getBoolean("displayNearbyCardView", true)) { - nearbyNoificationCardView.cardViewVisibilityState = NearbyNoificationCardView.CardViewVisibilityState.LOADING; - nearbyNoificationCardView.setVisibility(View.VISIBLE); checkGPS(); + if (nearbyNoificationCardView.cardViewVisibilityState == NearbyNoificationCardView.CardViewVisibilityState.READY) { + nearbyNoificationCardView.setVisibility(View.VISIBLE); + } } else { // Hide nearby notification card view if related shared preferences is false @@ -511,7 +540,6 @@ public class ContributionsFragment } - /** * Check GPS to decide displaying request permission button or not. */ @@ -519,7 +547,15 @@ public class ContributionsFragment if (!locationManager.isProviderEnabled()) { Timber.d("GPS is not enabled"); nearbyNoificationCardView.permissionType = NearbyNoificationCardView.PermissionType.ENABLE_GPS; - nearbyNoificationCardView.displayPermissionRequestButton(true); + if (prefs.getBoolean("displayLocationPermissionForCardView", true)) { + DialogUtil.showAlertDialog(getActivity(), + getString(R.string.nearby_card_permission_title), + getString(R.string.nearby_card_permission_explanation), + () -> displayYouWontSeeNearbyMessage(), + () -> enableGPS(), + checkBoxView, + false); + } } else { Timber.d("GPS is enabled"); checkLocationPermission(); @@ -530,20 +566,57 @@ public class ContributionsFragment 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); + // If user didn't selected Don't ask again + if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION) + && prefs.getBoolean("displayLocationPermissionForCardView", true)) { + DialogUtil.showAlertDialog(getActivity(), + getString(R.string.nearby_card_permission_title), + getString(R.string.nearby_card_permission_explanation), + () -> displayYouWontSeeNearbyMessage(), + () -> enableLocationPermission(), + checkBoxView, + false); + } } } 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 enableLocationPermission() { + if (!getActivity().isFinishing()) { + ((MainActivity) getActivity()).locationManager.requestPermissions(getActivity()); + } + } + + private void enableGPS() { + 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"); + ((MainActivity) getActivity()).startActivityForResult(callGPSSettingIntent, 1); + }) + .setNegativeButton(R.string.menu_cancel_upload, (dialog, id) -> { + dialog.cancel(); + displayYouWontSeeNearbyMessage(); + }) + .create() + .show(); + } + + private void displayYouWontSeeNearbyMessage() { + ViewUtil.showLongToast(getActivity(), getResources().getString(R.string.unable_to_display_nearest_place)); + } + private void updateClosestNearbyCardViewInfo() { curLatLng = locationManager.getLastLocation(); diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java index e3ed7f18e..9b8035e5d 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java @@ -31,6 +31,7 @@ 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.nearby.NearbyNoificationCardView; import fr.free.nrw.commons.notification.NotificationActivity; import fr.free.nrw.commons.theme.NavigationBaseActivity; import fr.free.nrw.commons.upload.UploadService; @@ -253,7 +254,9 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag 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); + if (contributionsFragment.nearbyNoificationCardView.cardViewVisibilityState == NearbyNoificationCardView.CardViewVisibilityState.READY) { + contributionsFragment.nearbyNoificationCardView.setVisibility(View.VISIBLE); + } } else { contributionsFragment.nearbyNoificationCardView.setVisibility(View.GONE); } @@ -468,6 +471,8 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Timber.d("Location permission given"); + ((ContributionsFragment)contributionsActivityPagerAdapter + .getItem(0)).locationManager.registerLocationManager(); } else { // If nearby fragment is visible and location permission is not given, send user back to contrib fragment if (!isContributionsFragmentVisible) { 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 bf649474e..32870e897 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 @@ -157,8 +157,11 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - detailProvider = (MediaDetailPagerFragment.MediaDetailProvider) (getParentFragment().getParentFragment()); - + if (getParentFragment() != null + && getParentFragment() instanceof MediaDetailPagerFragment) { + detailProvider = + ((MediaDetailPagerFragment) getParentFragment()).getMediaDetailProvider(); + } if (savedInstanceState != null) { editable = savedInstanceState.getBoolean("editable"); isCategoryImage = savedInstanceState.getBoolean("isCategoryImage"); @@ -230,7 +233,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { @Override public void onResume() { super.onResume(); - ((ContributionsFragment)(getParentFragment().getParentFragment())).nearbyNoificationCardView.setVisibility(View.GONE); + if(getParentFragment()!=null && getParentFragment().getParentFragment()!=null) { + //Added a check because, not necessarily, the parent fragment will have a parent fragment, say + // in the case when MediaDetailPagerFragment is directly started by the CategoryImagesActivity + ((ContributionsFragment) (getParentFragment().getParentFragment())).nearbyNoificationCardView + .setVisibility(View.GONE); + } media = detailProvider.getMediaAtPosition(index); if (media == null) { // Ask the detail provider to ping us when we're ready 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 7caa9454a..3db8258fb 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 @@ -69,6 +69,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple private boolean isFeaturedImage; MediaDetailAdapter adapter; private Bookmark bookmark; + private MediaDetailProvider provider; public MediaDetailPagerFragment() { this(false, false); @@ -128,6 +129,23 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple isFeaturedImage = savedInstanceState.getBoolean("isFeaturedImage"); } setHasOptionsMenu(true); + initProvider(); + } + + /** + * initialise the provider, based on from where the fragment was started, as in from an activity + * or a fragment + */ + private void initProvider() { + if (getParentFragment() != null) { + provider = (MediaDetailProvider) getParentFragment(); + } else { + provider = (MediaDetailProvider) getActivity(); + } + } + + public MediaDetailProvider getMediaDetailProvider() { + return provider; } @Override @@ -136,7 +154,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple Timber.d("Returning as activity is destroyed!"); return true; } - MediaDetailProvider provider = (MediaDetailProvider) getParentFragment(); + Media m = provider.getMediaAtPosition(pager.getCurrentItem()); switch (item.getItemId()) { case R.id.menu_bookmark_current_image: @@ -346,7 +364,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple e.printStackTrace(); } } - getParentFragment().getActivity().supportInvalidateOptionsMenu(); + getActivity().invalidateOptionsMenu(); } @Override @@ -384,7 +402,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple Timber.d("Skipping getItem. Returning as activity is destroyed!"); return null; } - pager.postDelayed(() -> getParentFragment().getActivity().supportInvalidateOptionsMenu(), 5); + pager.postDelayed(() -> getActivity().invalidateOptionsMenu(), 5); } return MediaDetailFragment.forMedia(i, editable, isFeaturedImage); } @@ -395,7 +413,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple Timber.d("Skipping getCount. Returning as activity is destroyed!"); return 0; } - return ((MediaDetailProvider) getParentFragment()).getTotalMediaCount(); + return provider.getTotalMediaCount(); } } } 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 628ab72f9..c8c1d3ebd 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 @@ -135,6 +135,8 @@ public class NearbyMapFragment extends DaggerFragment { @Inject BookmarkLocationsDao bookmarkLocationDao; + private static final double ZOOM_LEVEL = 14f; + public NearbyMapFragment() { } @@ -304,7 +306,7 @@ public class NearbyMapFragment extends DaggerFragment { curMapBoxLatLng.getLongitude()) : curMapBoxLatLng ) // Sets the new camera position .zoom(isBottomListSheetExpanded ? - 11 // zoom level is fixed to 11 when bottom sheet is expanded + ZOOM_LEVEL // zoom level is fixed when bottom sheet is expanded :mapboxMap.getCameraPosition().zoom) // Same zoom level .build(); }else { @@ -314,7 +316,7 @@ public class NearbyMapFragment extends DaggerFragment { curMapBoxLatLng.getLongitude()) : curMapBoxLatLng ) // Sets the new camera position .zoom(isBottomListSheetExpanded ? - 11 // zoom level is fixed to 11 when bottom sheet is expanded + ZOOM_LEVEL // zoom level is fixed when bottom sheet is expanded :mapboxMap.getCameraPosition().zoom) // Same zoom level .build(); } @@ -325,47 +327,6 @@ 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; - if (mapboxMap != null && curLatLng != null) { - if (isBottomListSheetExpanded) { - // Make camera to follow user on location change - if (ViewUtil.isPortrait(getActivity())) { - position = new CameraPosition.Builder() - .target(new LatLng(curLatLng.getLatitude() - CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT, - curLatLng.getLongitude())) // Sets the new camera target above - // current to make it visible when sheet is expanded - .zoom(11) // Fixed zoom level - .build(); - } else { - position = new CameraPosition.Builder() - .target(new LatLng(curLatLng.getLatitude() - CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE, - curLatLng.getLongitude())) // Sets the new camera target above - // current to make it visible when sheet is expanded - .zoom(11) // Fixed zoom level - .build(); - } - - } else { - // Make camera to follow user on location change - position = new CameraPosition.Builder() - .target(new LatLng(curLatLng.getLatitude(), - curLatLng.getLongitude())) // Sets the new camera target to curLatLng - .zoom(mapboxMap.getCameraPosition().zoom) // Same zoom level - .build(); - } - mapboxMap.animateCamera(CameraUpdateFactory - .newCameraPosition(position), 1000); - } - } - private void initViews() { Timber.d("initViews called"); bottomSheetList = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.bottom_sheet); @@ -446,7 +407,7 @@ public class NearbyMapFragment extends DaggerFragment { curLatLng.getLongitude()) : new LatLng(curLatLng.getLatitude(), curLatLng.getLongitude(), 0)) // Sets the new camera position .zoom(isBottomListSheetExpanded ? - 11 // zoom level is fixed to 11 when bottom sheet is expanded + ZOOM_LEVEL :mapboxMap.getCameraPosition().zoom) // Same zoom level .build(); }else { @@ -456,7 +417,7 @@ public class NearbyMapFragment extends DaggerFragment { curLatLng.getLongitude()) : new LatLng(curLatLng.getLatitude(), curLatLng.getLongitude(), 0)) // Sets the new camera position .zoom(isBottomListSheetExpanded ? - 11 // zoom level is fixed to 11 when bottom sheet is expanded + ZOOM_LEVEL :mapboxMap.getCameraPosition().zoom) // Same zoom level .build(); } @@ -494,9 +455,6 @@ public class NearbyMapFragment extends DaggerFragment { public void onStateChanged(@NonNull View bottomSheet, int newState) { if (newState == BottomSheetBehavior.STATE_EXPANDED) { bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); - updateMapCameraAccordingToBottomSheet(true); - } else { - updateMapCameraAccordingToBottomSheet(false); } } @@ -526,7 +484,7 @@ public class NearbyMapFragment extends DaggerFragment { .attributionEnabled(false) .camera(new CameraPosition.Builder() .target(new LatLng(curLatLng.getLatitude(), curLatLng.getLongitude())) - .zoom(11) + .zoom(ZOOM_LEVEL) .build()); if (!getParentFragment().getActivity().isFinishing()) { 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 index c44fbf763..61798a95a 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyNoificationCardView.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyNoificationCardView.java @@ -3,6 +3,7 @@ package fr.free.nrw.commons.nearby; import android.content.Context; import android.content.Intent; import android.content.res.Resources; +import android.os.Handler; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.CoordinatorLayout; @@ -10,6 +11,7 @@ import android.support.design.widget.SwipeDismissBehavior; import android.support.v7.app.AlertDialog; import android.support.v7.widget.CardView; import android.util.AttributeSet; +import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.Button; @@ -19,6 +21,7 @@ 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; @@ -86,7 +89,7 @@ public class NearbyNoificationCardView extends CardView{ 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)) { + if (((MainActivity)context).prefs.getBoolean("displayNearbyCardView", true) && this.cardViewVisibilityState == NearbyNoificationCardView.CardViewVisibilityState.READY) { this.setVisibility(VISIBLE); } else { this.setVisibility(GONE); @@ -183,7 +186,7 @@ public class NearbyNoificationCardView extends CardView{ } else { cardViewVisibilityState = CardViewVisibilityState.LOADING; - permissionRequestButton.setVisibility(GONE); + /*permissionRequestButton.setVisibility(GONE); contentLayout.setVisibility(VISIBLE); // Set visibility of elements in content layout once it become visible progressBar.setVisibility(VISIBLE); @@ -191,10 +194,35 @@ public class NearbyNoificationCardView extends CardView{ notificationDistance.setVisibility(GONE); notificationIcon.setVisibility(GONE); - permissionRequestButton.setVisibility(GONE); + permissionRequestButton.setVisibility(GONE);*/ + + this.setVisibility(GONE); + Handler nearbyNotificationHandler = new Handler(); + Runnable nearbyNotificationRunnable = new Runnable() { + @Override + public void run() { + if (cardViewVisibilityState != NearbyNoificationCardView.CardViewVisibilityState.READY + && cardViewVisibilityState != NearbyNoificationCardView.CardViewVisibilityState.ASK_PERMISSION + && cardViewVisibilityState != NearbyNoificationCardView.CardViewVisibilityState.INVISIBLE) { + // If after 30 seconds, card view is not ready + errorOcured(); + } else { + suceeded(); + } + } + }; + nearbyNotificationHandler.postDelayed(nearbyNotificationRunnable, 30000); } } + private void errorOcured() { + this.setVisibility(GONE); + } + + private void suceeded() { + this.setVisibility(VISIBLE); + } + /** * Pass place information to views. * @param isClosestNearbyPlaceFound false if there are no close place @@ -202,6 +230,7 @@ public class NearbyNoificationCardView extends CardView{ */ public void updateContent(boolean isClosestNearbyPlaceFound, Place place) { Timber.d("Update nearby card notification content"); + this.setVisibility(VISIBLE); cardViewVisibilityState = CardViewVisibilityState.READY; permissionRequestButton.setVisibility(GONE); contentLayout.setVisibility(VISIBLE); @@ -269,6 +298,7 @@ public class NearbyNoificationCardView extends CardView{ READY, INVISIBLE, ASK_PERMISSION, + ERROR_OCURED } /** diff --git a/app/src/main/java/fr/free/nrw/commons/ui/widget/CustomEditText.java b/app/src/main/java/fr/free/nrw/commons/ui/widget/CustomEditText.java new file mode 100644 index 000000000..632eef081 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/ui/widget/CustomEditText.java @@ -0,0 +1,204 @@ +package fr.free.nrw.commons.ui.widget; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.EditText; + +/** + * Custom edit text with a drawable click listener + * https://stackoverflow.com/questions/13135447/setting-onclicklistener-for-the-drawable-right-of-an-edittext + */ +@SuppressLint("AppCompatCustomView") +public class CustomEditText extends EditText { + + private Drawable drawableRight; + private Drawable drawableLeft; + private Drawable drawableTop; + private Drawable drawableBottom; + + int actionX, actionY; + + private DrawableClickListener clickListener; + + public CustomEditText(Context context, AttributeSet attrs) { + super(context, attrs); + // this Contructure required when you are using this view in xml + } + + public CustomEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + } + + @Override + public void setCompoundDrawables(Drawable left, Drawable top, + Drawable right, Drawable bottom) { + if (left != null) { + drawableLeft = left; + } + if (right != null) { + drawableRight = right; + } + if (top != null) { + drawableTop = top; + } + if (bottom != null) { + drawableBottom = bottom; + } + super.setCompoundDrawables(left, top, right, bottom); + } + + /** + * Fires the appropriate drawable click listener on touching the icon + * @param event + * @return + */ + @Override + public boolean onTouchEvent(MotionEvent event) { + Rect bounds; + if (event.getAction() == MotionEvent.ACTION_DOWN) { + actionX = (int) event.getX(); + actionY = (int) event.getY(); + if (drawableBottom != null + && drawableBottom.getBounds().contains(actionX, actionY)) { + clickListener.onClick(DrawableClickListener.DrawablePosition.BOTTOM); + return super.onTouchEvent(event); + } + + if (drawableTop != null + && drawableTop.getBounds().contains(actionX, actionY)) { + clickListener.onClick(DrawableClickListener.DrawablePosition.TOP); + return super.onTouchEvent(event); + } + + // this works for left since container shares 0,0 origin with bounds + if (drawableLeft != null) { + bounds = null; + bounds = drawableLeft.getBounds(); + + int x, y; + int extraTapArea = (int) (13 * getResources().getDisplayMetrics().density + 0.5); + + x = actionX; + y = actionY; + + if (!bounds.contains(actionX, actionY)) { + /** Gives the +20 area for tapping. */ + x = (int) (actionX - extraTapArea); + y = (int) (actionY - extraTapArea); + + if (x <= 0) + x = actionX; + if (y <= 0) + y = actionY; + + /** Creates square from the smallest value */ + if (x < y) { + y = x; + } + } + + if (bounds.contains(x, y) && clickListener != null) { + clickListener + .onClick(DrawableClickListener.DrawablePosition.LEFT); + event.setAction(MotionEvent.ACTION_CANCEL); + return false; + + } + } + + if (drawableRight != null) { + + bounds = null; + bounds = drawableRight.getBounds(); + + int x, y; + int extraTapArea = 13; + + /** + * IF USER CLICKS JUST OUT SIDE THE RECTANGLE OF THE DRAWABLE + * THAN ADD X AND SUBTRACT THE Y WITH SOME VALUE SO THAT AFTER + * CALCULATING X AND Y CO-ORDINATE LIES INTO THE DRAWBABLE + * BOUND. - this process help to increase the tappable area of + * the rectangle. + */ + x = (int) (actionX + extraTapArea); + y = (int) (actionY - extraTapArea); + + /**Since this is right drawable subtract the value of x from the width + * of view. so that width - tappedarea will result in x co-ordinate in drawable bound. + */ + x = getWidth() - x; + + /*x can be negative if user taps at x co-ordinate just near the width. + * e.g views width = 300 and user taps 290. Then as per previous calculation + * 290 + 13 = 303. So subtract X from getWidth() will result in negative value. + * So to avoid this add the value previous added when x goes negative. + */ + + if (x <= 0) { + x += extraTapArea; + } + + /* If result after calculating for extra tappable area is negative. + * assign the original value so that after subtracting + * extratapping area value doesn't go into negative value. + */ + + if (y <= 0) + y = actionY; + + /**If drawble bounds contains the x and y points then move ahead.*/ + if (bounds.contains(x, y) && clickListener != null) { + clickListener + .onClick(DrawableClickListener.DrawablePosition.RIGHT); + event.setAction(MotionEvent.ACTION_CANCEL); + return false; + } + return super.onTouchEvent(event); + } + + } + return super.onTouchEvent(event); + } + + @Override + protected void finalize() throws Throwable { + drawableRight = null; + drawableBottom = null; + drawableLeft = null; + drawableTop = null; + super.finalize(); + } + + /** + * Attaches the drawable click listener to the custom edit text + * @param listener + */ + public void setDrawableClickListener(DrawableClickListener listener) { + this.clickListener = listener; + } + + /** + * Interface for drawable click listener + */ + public interface DrawableClickListener { + enum DrawablePosition {TOP, BOTTOM, LEFT, RIGHT} + void onClick(DrawablePosition target); + } + +} diff --git a/app/src/main/java/fr/free/nrw/commons/upload/DescriptionsAdapter.java b/app/src/main/java/fr/free/nrw/commons/upload/DescriptionsAdapter.java index 8c2432ee1..e3730dc14 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/DescriptionsAdapter.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/DescriptionsAdapter.java @@ -4,26 +4,22 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v4.view.ViewCompat; import android.support.v7.widget.AppCompatSpinner; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.EditText; import java.util.ArrayList; import java.util.List; import butterknife.BindView; import butterknife.ButterKnife; -import butterknife.OnTouch; -import butterknife.Optional; import fr.free.nrw.commons.R; +import fr.free.nrw.commons.ui.widget.CustomEditText; import fr.free.nrw.commons.utils.AbstractTextWatcher; import fr.free.nrw.commons.utils.BiMap; import fr.free.nrw.commons.utils.ViewUtil; @@ -31,8 +27,6 @@ import io.reactivex.subjects.BehaviorSubject; import io.reactivex.subjects.Subject; import timber.log.Timber; -import static android.view.MotionEvent.ACTION_UP; - class DescriptionsAdapter extends RecyclerView.Adapter { private Title title; @@ -119,7 +113,7 @@ class DescriptionsAdapter extends RecyclerView.Adapter { + switch (target) { + case RIGHT: + if (getAdapterPosition() == 0) { + callback.showAlert(R.string.media_detail_title, R.string.title_info); + } + break; + default: + break; + } + }); + } else { Description description = descriptions.get(position - 1); Timber.d("Description is " + description); @@ -168,9 +174,7 @@ class DescriptionsAdapter extends RecyclerView.Adapter { - description.setDescriptionText(descriptionText); - })); + descItemEditText.addTextChangedListener(new AbstractTextWatcher(description::setDescriptionText)); descItemEditText.setOnFocusChangeListener((v, hasFocus) -> { if (!hasFocus) { ViewUtil.hideKeyboard(v); @@ -179,97 +183,77 @@ class DescriptionsAdapter extends RecyclerView.Adapter { + switch (target) { + case RIGHT: + if (getAdapterPosition() == 1) { + callback.showAlert(R.string.media_detail_description, + R.string.description_info); + } + break; + default: + break; } + }); + + initLanguageSpinner(position, description); + } + + } + + /** + * Extracted out the function to init the language spinner with different system supported languages + * @param position + * @param description + */ + private void initLanguageSpinner(int position, Description description) { + SpinnerLanguagesAdapter languagesAdapter = new SpinnerLanguagesAdapter(context, + R.layout.row_item_languages_spinner, selectedLanguages); + languagesAdapter.notifyDataSetChanged(); + spinnerDescriptionLanguages.setAdapter(languagesAdapter); + + if (description.getSelectedLanguageIndex() == -1) { + if (position == 1) { + int defaultLocaleIndex = languagesAdapter.getIndexOfUserDefaultLocale(context); + spinnerDescriptionLanguages.setSelection(defaultLocaleIndex); } else { - spinnerDescriptionLanguages.setSelection(description.getSelectedLanguageIndex()); - selectedLanguages.put(spinnerDescriptionLanguages, description.getLanguageCode()); + spinnerDescriptionLanguages.setSelection(0); } + } else { + spinnerDescriptionLanguages.setSelection(description.getSelectedLanguageIndex()); + selectedLanguages.put(spinnerDescriptionLanguages, description.getLanguageCode()); + } - //TODO do it the butterknife way - spinnerDescriptionLanguages.setOnItemSelectedListener(new OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView adapterView, View view, int position, - long l) { - description.setSelectedLanguageIndex(position); - String languageCode = ((SpinnerLanguagesAdapter) adapterView.getAdapter()).getLanguageCode(position); - description.setLanguageCode(languageCode); - selectedLanguages.remove(adapterView); - selectedLanguages.put(adapterView, languageCode); - ((SpinnerLanguagesAdapter) adapterView.getAdapter()).selectedLangCode = languageCode; - } + //TODO do it the butterknife way + spinnerDescriptionLanguages.setOnItemSelectedListener(new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int position, + long l) { + description.setSelectedLanguageIndex(position); + String languageCode = ((SpinnerLanguagesAdapter) adapterView.getAdapter()).getLanguageCode(position); + description.setLanguageCode(languageCode); + selectedLanguages.remove(adapterView); + selectedLanguages.put(adapterView, languageCode); + ((SpinnerLanguagesAdapter) adapterView.getAdapter()).selectedLangCode = languageCode; + } @Override public void onNothingSelected(AdapterView adapterView) { - } - }); - } - + } + }); } - @Optional - @OnTouch(R.id.description_item_edit_text) - boolean descriptionInfo(View view, MotionEvent motionEvent) { - //Title info is visible only for the title - if (getAdapterPosition() == 0) { - if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) { - final int value = view.getRight() - descItemEditText - .getCompoundDrawables()[2] - .getBounds().width(); - if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() >= value) { - callback.showAlert(R.string.media_detail_title, R.string.title_info); - return true; - } - } else { - final int value = descItemEditText.getLeft() + descItemEditText - .getCompoundDrawables()[0] - .getBounds().width(); - if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() <= value) { - callback.showAlert(R.string.media_detail_title, R.string.title_info); - return true; - } - } - //Description info is visible only for the first description - } else if (getAdapterPosition() == 1) { - final int value; - if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) { - value = view.getRight() - descItemEditText.getCompoundDrawables()[2].getBounds().width(); - if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() >= value) { - callback.showAlert(R.string.media_detail_description, - R.string.description_info); - return true; - } - } else { - value = descItemEditText.getLeft() + descItemEditText - .getCompoundDrawables()[0] - .getBounds().width(); - if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() <= value) { - callback.showAlert(R.string.media_detail_description, - R.string.description_info); - return true; - } - } - } - return false; + /** + * Extracted out the method to get the icon drawable + * @return + */ + private Drawable getInfoIcon() { + return context.getResources().getDrawable(R.drawable.mapbox_info_icon_default); } } - private Drawable getInfoIcon() { - return context.getResources() - .getDrawable(R.drawable.mapbox_info_icon_default); - } - public interface Callback { void showAlert(int mediaDetailDescription, int descriptionInfo); } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java index 2ea89e8e6..d71bc0985 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java @@ -223,6 +223,9 @@ public class UploadActivity extends AuthenticatedActivity implements UploadView, bottomCardSubtitle.setText(cardSubTitle); categoryTitle.setText(cardTitle); licenseTitle.setText(cardTitle); + if (currentStep == stepCount) { + dismissKeyboard(); + } if(isShowingItem) { descriptionsAdapter.setItems(uploadItem.title, uploadItem.descriptions); rvDescriptions.setAdapter(descriptionsAdapter); diff --git a/app/src/main/java/fr/free/nrw/commons/utils/DialogUtil.java b/app/src/main/java/fr/free/nrw/commons/utils/DialogUtil.java index f68037488..21cc69b14 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/DialogUtil.java +++ b/app/src/main/java/fr/free/nrw/commons/utils/DialogUtil.java @@ -10,6 +10,7 @@ import android.os.Build; import android.support.annotation.Nullable; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; +import android.view.View; import fr.free.nrw.commons.R; import timber.log.Timber; @@ -160,6 +161,63 @@ public class DialogUtil { showSafely(activity, dialog); } + /* + Shows alert dialog with custom view + */ + public static void showAlertDialog(Activity activity, + String title, + String message, + final Runnable onPositiveBtnClick, + final Runnable onNegativeBtnClick, + View customView, + boolean cancelable) { + showAlertDialog(activity, + title, + message, + activity.getString(R.string.no), + activity.getString(R.string.yes), + onPositiveBtnClick, + onNegativeBtnClick, + customView, + false); + } + + /* + Shows alert dialog with custom view + */ + private static void showAlertDialog(Activity activity, + String title, + String message, + String positiveButtonText, + String negativeButtonText, + final Runnable onPositiveBtnClick, + final Runnable onNegativeBtnClick, + View customView, + boolean cancelable) { + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(title); + builder.setMessage(message); + builder.setView(customView); + builder.setCancelable(cancelable); + + builder.setPositiveButton(positiveButtonText, (dialogInterface, i) -> { + dialogInterface.dismiss(); + if (onPositiveBtnClick != null) { + onPositiveBtnClick.run(); + } + }); + + builder.setNegativeButton(negativeButtonText, (DialogInterface dialogInterface, int i) -> { + dialogInterface.dismiss(); + if (onNegativeBtnClick != null) { + onNegativeBtnClick.run(); + } + }); + + AlertDialog dialog = builder.create(); + showSafely(activity, dialog); + } + public interface Callback { void onPositiveButtonClicked(); diff --git a/app/src/main/res/layout/nearby_permission_dialog.xml b/app/src/main/res/layout/nearby_permission_dialog.xml new file mode 100644 index 000000000..feb19a4cf --- /dev/null +++ b/app/src/main/res/layout/nearby_permission_dialog.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/row_item_description.xml b/app/src/main/res/layout/row_item_description.xml index f626abcfa..3282a9a2f 100644 --- a/app/src/main/res/layout/row_item_description.xml +++ b/app/src/main/res/layout/row_item_description.xml @@ -1,6 +1,5 @@ - diff --git a/app/src/main/res/layout/row_item_title.xml b/app/src/main/res/layout/row_item_title.xml index 3a2262067..b67a99711 100644 --- a/app/src/main/res/layout/row_item_title.xml +++ b/app/src/main/res/layout/row_item_title.xml @@ -7,7 +7,7 @@ android:layout_height="wrap_content" tools:showIn="@layout/activity_upload"> - การอัปโหลดของฉัน แชร์ ดูในเบราว์เซอร์ - ชื่อเรื่อง + ชื่อเรื่อง กรุณาระบุชืิ่อเรื่องของไฟล์นี้ คำอธิบาย ไม่สามารถเข้าสู่ระบบได้ - ความล้มเหลวของเครือข่าย @@ -63,7 +63,7 @@ GPS ถูกปิดใช้งานในอุปกรณ์ของคุณอยู่ คุณต้องการเปิดใช้งานหรือไม่? เปิดใช้งาน GPS ยังไม่มีการอัปโหลด - การอัปโหลด %1$d รายการ + การอัปโหลด %1$d รายการ กำลังเริ่มอัปโหลด %1$d รายการ กำลังเริ่มอัปโหลด %1$d รายการ @@ -155,6 +155,7 @@ สถานที่ใกล้เคียง ไม่พบสถานที่ใกล้เคียง คำเตือน + ไฟล์นี้มีอยู่แล้วบนคอมมอนส์ คุณแน่ใจหรือว่าคุณต้องการดำเนินการต่อ? ใช่ ไม่ ชื่อเรื่อง @@ -212,7 +213,6 @@ ข้อผิดพลาดขณะแคชภาพ ชื่อเรื่องที่อธิบายลักษณะเฉพาะของไฟล์ ซึ่งจะใช้เป็นชื่อไฟล์ คุณอาจใช้ภาษาธรรมดาที่มีเว้นวรรคก็ได้ อย่ารวมนามสกุลไฟล์ โปรดอธิบายสื่อดังกล่าวให้มากที่สุดเท่าที่จะได้: สื่อนี้ถูกถ่ายที่ไหน? สื่อนี้แสดงถึงอะไร? บริบทคืออะไร? โปรดอธิบายถึงวัตถุหรือบุคคล เปิดเผยข้อมูลที่ไม่อาจคาดเดาได้อย่างง่ายดาย เช่น เวลาที่ถ่าย หากเป็นภาพทิวทัศน์ หากสื่อแสดงถึงสิ่งที่ไม่ธรรมดา โปรดอธิบายว่าอะไรทำให้สื่อดังกล่าวไม่ธรรมดา - ไฟล์นี้มีอยู่แล้วบนคอมมอนส์ คุณแน่ใจหรือว่าคุณต้องการดำเนินการต่อ? ภาพนี้มืดเกินไป คุณแน่ใจหรือว่าคุณต้องการอัปโหลดภาพนี้? วิกิมีเดียคอมมอนส์นั้นมีไว้สำหรับรูปภาพที่มีคุณค่าในทางสารานุกรมเท่านั้น ภาพนี้มัว คุณแน่ใจหรือว่าคุณต้องการอัปโหลดภาพนี้? วิกิมีเดียคอมมอนส์นั้นมีไว้สำหรับรูปภาพที่มีคุณค่าในทางสารานุกรมเท่านั้น ให้สิทธิ์ diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 7be81344a..65627b42a 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -185,6 +185,7 @@ Місця поблизу Не знайдено місць поблизу Попередження + Цей файл вже існує на Вікісховищі. Ви впевнені, що хочете продовжити? Так Ні Назва diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index ac0b1a74a..af8f66157 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -165,6 +165,7 @@ Nơi Lân cận Không tìm thấy nơi lân cận Cảnh báo + Tập tin này đã tồn tại ở Commons. Bạn có chắc chắn muốn tiếp tục? Không Tiêu đề diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index c7bd947b7..8f0675475 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -179,6 +179,7 @@ 附近地点 找不到附近地点 警告 + 此文件在共享资源已存在。您确定要继续么? 标题 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9cc8234f2..43af8979f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -425,5 +425,11 @@ Upload your first media by touching the camera or gallery icon above. No, Go Back (For all images in set) + Permission Request + Would you like us to use your current location to display the nearest place that needs pictures? + Unable to display nearest place that needs pictures without location permissions + Never ask this again + Display location permission + Ask for location permission when needed for nearby notification card view feature. diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 0af4d50c6..e272e02af 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -42,6 +42,12 @@ android:defaultValue="true" android:summary="@string/display_nearby_notification_summary" /> + +