diff --git a/android/jni/com/mapswithme/maps/Framework.cpp b/android/jni/com/mapswithme/maps/Framework.cpp index 2205d9fd06..5f276a6ea2 100644 --- a/android/jni/com/mapswithme/maps/Framework.cpp +++ b/android/jni/com/mapswithme/maps/Framework.cpp @@ -1896,4 +1896,39 @@ Java_com_mapswithme_maps_Framework_nativeGetAccessToken(JNIEnv * env, jclass) auto & user = frm()->GetUser(); return jni::ToJavaString(env, user.GetAccessToken()); } + +JNIEXPORT jobject JNICALL +Java_com_mapswithme_maps_Framework_nativeGetMapObject(JNIEnv * env, jclass, + jobject notificationMapObject) +{ + eye::MapObject mapObject; + auto const getBestTypeId = + jni::GetMethodID(env, notificationMapObject, "getBestType", "()Ljava/lang/String;"); + auto const bestType = + static_cast(env->CallObjectMethod(notificationMapObject, getBestTypeId)); + mapObject.SetBestType(jni::ToNativeString(env, bestType)); + + auto const getMercatorPosXId = + jni::GetMethodID(env, notificationMapObject, "getMercatorPosX", "()D"); + auto const getMercatorPosYId = + jni::GetMethodID(env, notificationMapObject, "getMercatorPosY", "()D"); + + auto const posX = + static_cast(env->CallDoubleMethod(notificationMapObject, getMercatorPosXId)); + auto const posY = + static_cast(env->CallDoubleMethod(notificationMapObject, getMercatorPosYId)); + mapObject.SetPos({posX, posY}); + + auto const getDefaultNameId = + jni::GetMethodID(env, notificationMapObject, "getDefaultName", "()Ljava/lang/String;"); + auto const defaultName = + static_cast(env->CallObjectMethod(notificationMapObject, getDefaultNameId)); + mapObject.SetDefaultName(jni::ToNativeString(env, defaultName)); + + place_page::Info info; + if (frm()->MakePlacePageInfo(mapObject, info)) + return usermark_helper::CreateMapObject(env, info); + + return nullptr; +} } // extern "C" diff --git a/android/jni/com/mapswithme/maps/LightFramework.cpp b/android/jni/com/mapswithme/maps/LightFramework.cpp index 0905e6ef11..2fbf100704 100644 --- a/android/jni/com/mapswithme/maps/LightFramework.cpp +++ b/android/jni/com/mapswithme/maps/LightFramework.cpp @@ -1,6 +1,8 @@ #include "map/framework_light.hpp" #include "map/local_ads_manager.hpp" +#include "base/assert.hpp" + #include "com/mapswithme/core/jni_helper.hpp" using namespace lightweight; @@ -66,4 +68,36 @@ Java_com_mapswithme_maps_LightFramework_nativeLogLocalAdsEvent(JNIEnv * env, jcl static_cast(accuracyInMeters)); framework.GetNonConst()->RegisterEvent(std::move(event)); } + +JNIEXPORT jobject JNICALL +Java_com_mapswithme_maps_LightFramework_nativeGetNotification(JNIEnv * env, jclass clazz) +{ + Framework framework(REQUEST_TYPE_NOTIFICATION); + auto const notification = framework.Get(); + + if (!notification) + return nullptr; + + // Type::UgcReview is only supported. + CHECK_EQUAL(notification.get().m_type, notifications::NotificationCandidate::Type::UgcReview, ()); + + static jclass const candidateId = + jni::GetGlobalClassRef(env, "com/mapswithme/maps/background/NotificationCandidate"); + static jclass const mapObjectId = + jni::GetGlobalClassRef(env, "com/mapswithme/maps/background/NotificationCandidate$MapObject"); + static jmethodID const candidateCtor = jni::GetConstructorID( + env, candidateId, "(ILcom/mapswithme/maps/background/NotificationCandidate$MapObject;)V"); + static jmethodID const mapObjectCtor = jni::GetConstructorID( + env, mapObjectId, "(DDLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + + auto const & srcObject = notification.get().m_mapObject; + ASSERT(srcObject, ()); + auto const readableName = jni::ToJavaString(env, srcObject->GetReadableName()); + auto const defaultName = jni::ToJavaString(env, srcObject->GetDefaultName()); + auto const type = jni::ToJavaString(env, srcObject->GetBestType()); + auto const mapObject = env->NewObject(mapObjectId, mapObjectCtor, srcObject->GetPos().x, + srcObject->GetPos().y, readableName, defaultName, type); + return env->NewObject(candidateId, candidateCtor, static_cast(notification.get().m_type), + mapObject); +} } // extern "C" diff --git a/android/src/com/mapswithme/maps/Framework.java b/android/src/com/mapswithme/maps/Framework.java index 1c1138664e..bf42ba7603 100644 --- a/android/src/com/mapswithme/maps/Framework.java +++ b/android/src/com/mapswithme/maps/Framework.java @@ -16,6 +16,7 @@ import com.mapswithme.maps.api.ParsedRoutingData; import com.mapswithme.maps.api.ParsedSearchRequest; import com.mapswithme.maps.api.ParsedUrlMwmRequest; import com.mapswithme.maps.auth.AuthorizationListener; +import com.mapswithme.maps.background.NotificationCandidate; import com.mapswithme.maps.bookmarks.data.DistanceAndAzimut; import com.mapswithme.maps.bookmarks.data.MapObject; import com.mapswithme.maps.gdpr.UserBindingListener; @@ -510,4 +511,8 @@ public class Framework @Nullable public static native String nativeGetAccessToken(); + + @Nullable + public static native MapObject nativeGetMapObject( + @NonNull NotificationCandidate.MapObject mapObject); } diff --git a/android/src/com/mapswithme/maps/LightFramework.java b/android/src/com/mapswithme/maps/LightFramework.java index 02df3a432d..fe61e2b9d8 100644 --- a/android/src/com/mapswithme/maps/LightFramework.java +++ b/android/src/com/mapswithme/maps/LightFramework.java @@ -1,7 +1,9 @@ package com.mapswithme.maps; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import com.mapswithme.maps.background.NotificationCandidate; import com.mapswithme.maps.geofence.GeoFenceFeature; public class LightFramework @@ -15,4 +17,6 @@ public class LightFramework public static native void nativeLogLocalAdsEvent(int type, double lat, double lon, int accuracyInMeters, long mwmVersion, @NonNull String countryId, int featureIndex); + @Nullable + public static native NotificationCandidate nativeGetNotification(); } diff --git a/android/src/com/mapswithme/maps/MwmActivity.java b/android/src/com/mapswithme/maps/MwmActivity.java index 17634af3b4..3b60bb33f7 100644 --- a/android/src/com/mapswithme/maps/MwmActivity.java +++ b/android/src/com/mapswithme/maps/MwmActivity.java @@ -38,6 +38,7 @@ import com.mapswithme.maps.api.ParsedSearchRequest; import com.mapswithme.maps.api.ParsedUrlMwmRequest; import com.mapswithme.maps.api.RoutePoint; import com.mapswithme.maps.auth.PassportAuthDialogFragment; +import com.mapswithme.maps.background.NotificationCandidate; import com.mapswithme.maps.background.Notifier; import com.mapswithme.maps.base.BaseMwmFragmentActivity; import com.mapswithme.maps.base.OnBackPressListener; @@ -103,6 +104,9 @@ import com.mapswithme.maps.sound.TtsPlayer; import com.mapswithme.maps.taxi.TaxiInfo; import com.mapswithme.maps.taxi.TaxiManager; import com.mapswithme.maps.tips.TipsApi; +import com.mapswithme.maps.ugc.EditParams; +import com.mapswithme.maps.ugc.UGC; +import com.mapswithme.maps.ugc.UGCEditorActivity; import com.mapswithme.maps.widget.FadeView; import com.mapswithme.maps.widget.menu.BaseMenu; import com.mapswithme.maps.widget.menu.MainMenu; @@ -340,15 +344,25 @@ public class MwmActivity extends BaseMwmFragmentActivity } @NonNull - public static Intent createAuthenticateIntent() + public static Intent createAuthenticateIntent(@NonNull Context context) { - return new Intent(MwmApplication.get(), MwmActivity.class) + return new Intent(context, MwmActivity.class) .addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) .putExtra(MwmActivity.EXTRA_TASK, new MwmActivity.ShowDialogTask(PassportAuthDialogFragment.class.getName())); } + @NonNull + public static Intent createLeaveReviewIntent(@NonNull Context context, + @NonNull NotificationCandidate.MapObject mapObject) + { + return new Intent(context, MwmActivity.class) + .addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + .putExtra(MwmActivity.EXTRA_TASK, new MwmActivity.ShowUGCEditorTask(mapObject)); + } + @Override public void onRenderingInitialized() { @@ -2661,6 +2675,32 @@ public class MwmActivity extends BaseMwmFragmentActivity } } + public static class ShowUGCEditorTask implements MapTask + { + @NonNull + private NotificationCandidate.MapObject mMapObject; + + public ShowUGCEditorTask(@NonNull NotificationCandidate.MapObject mapObject) + { + mMapObject = mapObject; + } + + @Override + public boolean run(@NonNull MwmActivity target) + { + MapObject mapObject = Framework.nativeGetMapObject(mMapObject); + + if (mapObject == null) + return false; + + EditParams.Builder builder = EditParams.Builder.fromMapObject(mapObject) + .setDefaultRating(UGC.RATING_NONE) + .setFromNotification(true); + UGCEditorActivity.start(target, builder.build()); + return true; + } + } + private class CurrentPositionClickListener implements OnClickListener { @Override diff --git a/android/src/com/mapswithme/maps/MwmApplication.java b/android/src/com/mapswithme/maps/MwmApplication.java index d4a529ddcd..3edefd6f78 100644 --- a/android/src/com/mapswithme/maps/MwmApplication.java +++ b/android/src/com/mapswithme/maps/MwmApplication.java @@ -90,11 +90,22 @@ public class MwmApplication extends Application return sSelf; } + /** + * + * Use {@link #backgroundTracker(Context)} instead. + */ + @Deprecated public static AppBackgroundTracker backgroundTracker() { return sSelf.mBackgroundTracker; } + @NonNull + public static AppBackgroundTracker backgroundTracker(@NonNull Context context) + { + return ((MwmApplication) context.getApplicationContext()).getBackgroundTracker(); + } + /** * * Use {@link #prefs(Context)} instead. @@ -255,6 +266,12 @@ public class MwmApplication extends Application return mFrameworkInitialized && mPlatformInitialized; } + @NonNull + public AppBackgroundTracker getBackgroundTracker() + { + return mBackgroundTracker; + } + static { System.loadLibrary("mapswithme"); diff --git a/android/src/com/mapswithme/maps/background/NotificationCandidate.java b/android/src/com/mapswithme/maps/background/NotificationCandidate.java new file mode 100644 index 0000000000..d08ae6206a --- /dev/null +++ b/android/src/com/mapswithme/maps/background/NotificationCandidate.java @@ -0,0 +1,105 @@ +package com.mapswithme.maps.background; + +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.io.Serializable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +public class NotificationCandidate +{ + // This constants should be compatible with notifications::NotificationCandidate::Type enum + // from c++ side. + static final int TYPE_UGC_AUTH = 0; + static final int TYPE_UGC_REVIEW = 1; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ TYPE_UGC_AUTH, TYPE_UGC_REVIEW }) + @interface NotificationType {} + + public static class MapObject implements Serializable + { + private final double mMercatorPosX; + private final double mMercatorPosY; + @NonNull + private final String mReadableName; + + @NonNull + private final String mDefaultName; + @NonNull + private final String mBestType; + + MapObject(double posX, double posY, @NonNull String readableName, @NonNull String defaultName, + @NonNull String bestType) + { + mMercatorPosX = posX; + mMercatorPosY = posY; + mReadableName = readableName; + mDefaultName = defaultName; + mBestType = bestType; + } + + @SuppressWarnings("unused") + public double getMercatorPosX() + { + return mMercatorPosX; + } + + @SuppressWarnings("unused") + public double getMercatorPosY() + { + return mMercatorPosY; + } + + @NonNull + public String getReadableName() + { + return mReadableName; + } + + @NonNull + public String getDefaultName() + { + return mDefaultName; + } + + @NonNull + @SuppressWarnings("unused") + public String getBestType() + { + return mBestType; + } + } + + @NotificationType + private final int mType; + + @Nullable + private MapObject mMapObject; + + @SuppressWarnings("unused") + NotificationCandidate(@NotificationType int type) + { + mType = type; + } + + @SuppressWarnings("unused") + NotificationCandidate(@NotificationType int type, @Nullable MapObject mapObject) + { + this(type); + mMapObject = mapObject; + } + + public int getType() + { + return mType; + } + + @Nullable + public MapObject getMapObject() + { + return mMapObject; + } +} diff --git a/android/src/com/mapswithme/maps/background/NotificationService.java b/android/src/com/mapswithme/maps/background/NotificationService.java index 0ee96c6959..6766d11e98 100644 --- a/android/src/com/mapswithme/maps/background/NotificationService.java +++ b/android/src/com/mapswithme/maps/background/NotificationService.java @@ -10,7 +10,6 @@ import com.mapswithme.maps.LightFramework; import com.mapswithme.maps.MwmApplication; import com.mapswithme.maps.routing.RoutingController; import com.mapswithme.maps.scheduling.JobIdMap; -import com.mapswithme.util.NetworkPolicy; import com.mapswithme.util.PermissionsUtils; import com.mapswithme.util.log.Logger; import com.mapswithme.util.log.LoggerFactory; @@ -44,27 +43,15 @@ public class NotificationService extends JobIntentService private boolean notifyIsNotAuthenticated() { - if (!PermissionsUtils.isExternalStorageGranted() || - !NetworkPolicy.getCurrentNetworkUsageStatus() || - LightFramework.nativeIsAuthenticated() || - LightFramework.nativeGetNumberUnsentUGC() < MIN_COUNT_UNSENT_UGC) + if (LightFramework.nativeIsAuthenticated() + || LightFramework.nativeGetNumberUnsentUGC() < MIN_COUNT_UNSENT_UGC) { - LOGGER.d(TAG, "Authentication notification is rejected. External storage granted: " + - PermissionsUtils.isExternalStorageGranted() + ". Is user authenticated: " + - LightFramework.nativeIsAuthenticated() + ". Current network usage status: " + - NetworkPolicy.getCurrentNetworkUsageStatus() + ". Number of unsent UGC: " + + LOGGER.d(TAG, "Authentication notification is rejected. Is user authenticated: " + + LightFramework.nativeIsAuthenticated() + ". Number of unsent UGC: " + LightFramework.nativeGetNumberUnsentUGC()); return false; } - // Do not show push when user is in the navigation mode. - if (MwmApplication.get().arePlatformAndCoreInitialized() && - RoutingController.get().isNavigating()) - { - LOGGER.d(TAG, "Authentication notification is rejected. The user is in navigation mode."); - return false; - } - final long lastEventTimestamp = prefs().getLong(LAST_AUTH_NOTIFICATION_TIMESTAMP, 0); if (System.currentTimeMillis() - lastEventTimestamp > MIN_AUTH_EVENT_DELTA_MILLIS) @@ -85,20 +72,58 @@ public class NotificationService extends JobIntentService return false; } + private boolean notifySmart() + { + if (!PermissionsUtils.isExternalStorageGranted() + || MwmApplication.backgroundTracker(getApplication()).isForeground()) + { + return false; + } + + NotificationCandidate candidate = LightFramework.nativeGetNotification(); + + if (candidate == null || candidate.getMapObject() == null) + return false; + + if (candidate.getType() == NotificationCandidate.TYPE_UGC_REVIEW) + { + Notifier notifier = Notifier.from(getApplication()); + notifier.notifyLeaveReview(candidate.getMapObject()); + return true; + } + + return false; + } + @Override protected void onHandleWork(@NonNull Intent intent) { final String action = intent.getAction(); if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) - onConnectivityChanged(); + TryToShowNotification(); } - private void onConnectivityChanged() + private void TryToShowNotification() { + if (!PermissionsUtils.isExternalStorageGranted()) + { + LOGGER.d(TAG, "Notification is rejected. External storage is not granted."); + return; + } + + // Do not show push when user is in the navigation mode. + if (MwmApplication.get().arePlatformAndCoreInitialized() + && RoutingController.get().isNavigating()) + { + LOGGER.d(TAG, "Notification is rejected. The user is in navigation mode."); + return; + } + final NotificationExecutor notifyOrder[] = { - this::notifyIsNotAuthenticated + this::notifyIsNotAuthenticated, + this::notifySmart }; // Only one notification should be shown at a time. diff --git a/android/src/com/mapswithme/maps/background/Notifier.java b/android/src/com/mapswithme/maps/background/Notifier.java index 6bf3e2d2e8..a632809a85 100644 --- a/android/src/com/mapswithme/maps/background/Notifier.java +++ b/android/src/com/mapswithme/maps/background/Notifier.java @@ -27,6 +27,7 @@ public final class Notifier public static final int ID_NONE = 0; public static final int ID_DOWNLOAD_FAILED = 1; public static final int ID_IS_NOT_AUTHENTICATED = 2; + public static final int ID_LEAVE_REVIEW = 3; @NonNull private final Application mContext; @@ -58,7 +59,7 @@ public final class Notifier public void notifyAuthentication() { - Intent authIntent = MwmActivity.createAuthenticateIntent(); + Intent authIntent = MwmActivity.createAuthenticateIntent(mContext); authIntent.putExtra(EXTRA_CANCEL_NOTIFICATION, Notifier.ID_IS_NOT_AUTHENTICATED); authIntent.putExtra(EXTRA_NOTIFICATION_CLICKED, Statistics.EventName.UGC_NOT_AUTH_NOTIFICATION_CLICKED); @@ -79,6 +80,31 @@ public final class Notifier Statistics.INSTANCE.trackEvent(Statistics.EventName.UGC_NOT_AUTH_NOTIFICATION_SHOWN); } + public void notifyLeaveReview(@NonNull NotificationCandidate.MapObject mapObject) + { + Intent reviewIntent = MwmActivity.createLeaveReviewIntent(mContext, mapObject); + reviewIntent.putExtra(EXTRA_CANCEL_NOTIFICATION, Notifier.ID_LEAVE_REVIEW); + reviewIntent.putExtra(EXTRA_NOTIFICATION_CLICKED, + Statistics.EventName.UGC_REVIEW_NOTIFICATION_CLICKED); + + PendingIntent pi = + PendingIntent.getActivity(mContext, 0, reviewIntent, PendingIntent.FLAG_UPDATE_CURRENT); + + String channel = NotificationChannelFactory.createProvider(mContext).getAuthChannel(); + NotificationCompat.Builder builder = + getBuilder(String.format(mContext.getString(R.string.notification_leave_review_title), + mapObject.getReadableName()), + String.format(mContext.getString(R.string.notification_leave_review_content), + mapObject.getReadableName()), + pi, channel); + + builder.addAction(0, mContext.getString(R.string.leave_a_review), pi); + + getNotificationManager().notify(ID_LEAVE_REVIEW, builder.build()); + + Statistics.INSTANCE.trackEvent(Statistics.EventName.UGC_REVIEW_NOTIFICATION_SHOWN); + } + public void cancelNotification(@NotificationId int id) { if (id == ID_NONE) @@ -120,13 +146,13 @@ public final class Notifier { return new NotificationCompat.Builder(mContext, channel) - .setAutoCancel(true) - .setSmallIcon(R.drawable.ic_notification) - .setColor(mContext.getResources().getColor(R.color.base_accent)) - .setContentTitle(title) - .setContentText(content) - .setTicker(getTicker(title, content)) - .setContentIntent(pendingIntent); + .setAutoCancel(true) + .setSmallIcon(R.drawable.ic_notification) + .setColor(mContext.getResources().getColor(R.color.base_accent)) + .setContentTitle(title) + .setContentText(content) + .setTicker(getTicker(title, content)) + .setContentIntent(pendingIntent); } @NonNull diff --git a/android/src/com/mapswithme/maps/ugc/EditParams.java b/android/src/com/mapswithme/maps/ugc/EditParams.java index 20f9b39360..b44c0e4cf8 100644 --- a/android/src/com/mapswithme/maps/ugc/EditParams.java +++ b/android/src/com/mapswithme/maps/ugc/EditParams.java @@ -4,10 +4,11 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.mapswithme.maps.bookmarks.data.FeatureId; +import com.mapswithme.maps.bookmarks.data.MapObject; import java.util.ArrayList; -class EditParams +public class EditParams { @NonNull private final String mTitle; @@ -19,6 +20,7 @@ class EditParams private final int mDefaultRating; private final boolean mCanBeReviewed; private final boolean mFromPP; + private final boolean mFromNotification; // TODO: mLat, mLon, mAddress are added just for debugging null feature id for ugc object. // Remove they after problem is fixed. private double mLat; @@ -34,6 +36,7 @@ class EditParams mDefaultRating = builder.mDefaultRating; mCanBeReviewed = builder.mCanBeReviewed; mFromPP = builder.mFromPP; + mFromNotification = builder.mFromNotification; mLat = builder.mLat; mLon = builder.mLon; mAddress = builder.mAddress; @@ -72,6 +75,11 @@ class EditParams return mFromPP; } + boolean isFromNotification() + { + return mFromNotification; + } + double getLat() { return mLat; @@ -100,11 +108,23 @@ class EditParams private int mDefaultRating; private boolean mCanBeReviewed; private boolean mFromPP; + private boolean mFromNotification; private double mLat; private double mLon; @Nullable private String mAddress; + @NonNull + public static EditParams.Builder fromMapObject(@NonNull MapObject mapObject) + { + return new EditParams.Builder(mapObject.getTitle(), mapObject.getFeatureId()) + .setRatings(mapObject.getDefaultRatings()) + .setCanBeReviewed(mapObject.canBeReviewed()) + .setLat(mapObject.getLat()) + .setLon(mapObject.getLon()) + .setAddress(mapObject.getAddress()); + } + public Builder(@NonNull String title, @NonNull FeatureId featureId) { mTitle = title; @@ -117,24 +137,30 @@ class EditParams return this; } - Builder setDefaultRating(@UGC.Impress int defaultRating) + public Builder setDefaultRating(@UGC.Impress int defaultRating) { mDefaultRating = defaultRating; return this; } - Builder setCanBeReviewed(boolean value) + public Builder setCanBeReviewed(boolean value) { mCanBeReviewed = value; return this; } - Builder setFromPP(boolean value) + public Builder setFromPP(boolean value) { mFromPP = value; return this; } + public Builder setFromNotification(boolean value) + { + mFromNotification = value; + return this; + } + public Builder setLat(double lat) { mLat = lat; diff --git a/android/src/com/mapswithme/maps/ugc/UGC.java b/android/src/com/mapswithme/maps/ugc/UGC.java index 7d9d4a183a..53c034ff53 100644 --- a/android/src/com/mapswithme/maps/ugc/UGC.java +++ b/android/src/com/mapswithme/maps/ugc/UGC.java @@ -26,13 +26,13 @@ public class UGC public @interface Impress {} - static final int RATING_NONE = 0; - static final int RATING_HORRIBLE = 1; - static final int RATING_BAD = 2; - static final int RATING_NORMAL = 3; - static final int RATING_GOOD = 4; - static final int RATING_EXCELLENT = 5; - static final int RATING_COMING_SOON = 6; + public static final int RATING_NONE = 0; + public static final int RATING_HORRIBLE = 1; + public static final int RATING_BAD = 2; + public static final int RATING_NORMAL = 3; + public static final int RATING_GOOD = 4; + public static final int RATING_EXCELLENT = 5; + public static final int RATING_COMING_SOON = 6; @NonNull private final Rating[] mRatings; diff --git a/android/src/com/mapswithme/maps/ugc/UGCController.java b/android/src/com/mapswithme/maps/ugc/UGCController.java index 2b1138b8d1..ce504a7577 100644 --- a/android/src/com/mapswithme/maps/ugc/UGCController.java +++ b/android/src/com/mapswithme/maps/ugc/UGCController.java @@ -68,9 +68,9 @@ public class UGCController implements View.OnClickListener, UGC.UGCListener if (mMapObject == null) return; - EditParams.Builder builder = prepareEditParamsBuilder(mMapObject) - .setDefaultRating(UGC.RATING_NONE) - .setFromPP(true); + EditParams.Builder builder = EditParams.Builder.fromMapObject(mMapObject) + .setDefaultRating(UGC.RATING_NONE) + .setFromPP(true); UGCEditorActivity.start((Activity) mPlacePage.getContext(), builder.build()); } @@ -159,7 +159,8 @@ public class UGCController implements View.OnClickListener, UGC.UGCListener @Override public void onClick(View v) { - switch (v.getId()){ + switch (v.getId()) + { case R.id.ll__horrible: onAggRatingTapped(UGC.RATING_HORRIBLE); break; @@ -252,23 +253,11 @@ public class UGCController implements View.OnClickListener, UGC.UGCListener if (mMapObject == null) return; - EditParams.Builder builder = prepareEditParamsBuilder(mMapObject) - .setDefaultRating(rating) - .setFromPP(false); + EditParams.Builder builder = EditParams.Builder.fromMapObject(mMapObject) + .setDefaultRating(rating); UGCEditorActivity.start((Activity) mPlacePage.getContext(), builder.build()); } - @NonNull - private static EditParams.Builder prepareEditParamsBuilder(@NonNull MapObject mapObject) - { - return new EditParams.Builder(mapObject.getTitle(), mapObject.getFeatureId()) - .setRatings(mapObject.getDefaultRatings()) - .setCanBeReviewed(mapObject.canBeReviewed()) - .setLat(mapObject.getLat()) - .setLon(mapObject.getLon()) - .setAddress(mapObject.getAddress()); - } - private void setUserReviewAndRatingsView(@Nullable UGCUpdate update) { UiUtils.showIf(update != null, mUserReviewView, mUserReviewDivider, diff --git a/android/src/com/mapswithme/maps/ugc/UGCEditorActivity.java b/android/src/com/mapswithme/maps/ugc/UGCEditorActivity.java index b6abb004c4..8b7a6adbce 100644 --- a/android/src/com/mapswithme/maps/ugc/UGCEditorActivity.java +++ b/android/src/com/mapswithme/maps/ugc/UGCEditorActivity.java @@ -16,7 +16,7 @@ public class UGCEditorActivity extends BaseMwmFragmentActivity { public static void start(@NonNull Activity activity, @NonNull EditParams params) { - Statistics.INSTANCE.trackUGCStart(false /* isEdit */, params.isFromPP()); + Statistics.INSTANCE.trackUGCStart(false, params.isFromPP(), params.isFromNotification()); UserActionsLogger.logUgcEditorOpened(); final Intent i = new Intent(activity, UGCEditorActivity.class); Bundle args = new Bundle(); diff --git a/android/src/com/mapswithme/util/statistics/Statistics.java b/android/src/com/mapswithme/util/statistics/Statistics.java index c3ed3c8ba6..59b12d24b9 100644 --- a/android/src/com/mapswithme/util/statistics/Statistics.java +++ b/android/src/com/mapswithme/util/statistics/Statistics.java @@ -347,6 +347,8 @@ public enum Statistics public static final String TTS_FAILURE_LOCATION = "TTS failure location"; public static final String UGC_NOT_AUTH_NOTIFICATION_SHOWN = "UGC_UnsentNotification_shown"; public static final String UGC_NOT_AUTH_NOTIFICATION_CLICKED = "UGC_UnsentNotification_clicked"; + public static final String UGC_REVIEW_NOTIFICATION_SHOWN = "UGC_ReviewNotification_shown"; + public static final String UGC_REVIEW_NOTIFICATION_CLICKED = "UGC_ReviewNotification_clicked"; // routing public static final String ROUTING_BUILD = "Routing. Build"; @@ -537,6 +539,7 @@ public enum Statistics static final String AFTER_SAVE = "after_save"; static final String PLACEPAGE_PREVIEW = "placepage_preview"; static final String PLACEPAGE = "placepage"; + static final String NOTIFICATION = "notification"; public static final String FACEBOOK = "facebook"; public static final String CHECKIN = "check_in"; public static final String CHECKOUT = "check_out"; @@ -1208,14 +1211,15 @@ public enum Statistics } } - public void trackUGCStart(boolean isEdit, boolean isPPPreview) + public void trackUGCStart(boolean isEdit, boolean isPPPreview, boolean isFromNotification) { trackEvent(UGC_REVIEW_START, params() .add(EventParam.IS_AUTHENTICATED, Framework.nativeIsUserAuthenticated()) .add(EventParam.IS_ONLINE, ConnectionState.isConnected()) .add(EventParam.MODE, isEdit ? ParamValue.EDIT : ParamValue.ADD) - .add(EventParam.FROM, isPPPreview ? ParamValue.PLACEPAGE_PREVIEW : ParamValue.PLACEPAGE) + .add(EventParam.FROM, isPPPreview ? ParamValue.PLACEPAGE_PREVIEW : + isFromNotification ? ParamValue.NOTIFICATION : ParamValue.PLACEPAGE) .get()); } diff --git a/map/framework.cpp b/map/framework.cpp index a590137840..9f9b083a7b 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -3841,3 +3841,25 @@ double Framework::GetLastBackgroundTime() const { return m_startBackgroundTime; } + +bool Framework::MakePlacePageInfo(eye::MapObject const & mapObject, place_page::Info & info) +{ + m2::RectD rect = MercatorBounds::RectByCenterXYAndOffset(mapObject.GetPos(), kMwmPointAccuracy); + bool found = false; + + m_model.GetDataSource().ForEachInRect([this, &info, &mapObject, &found](FeatureType & ft) + { + if (found || !feature::GetCenter(ft).EqualDxDy(mapObject.GetPos(), kMwmPointAccuracy)) + return; + + auto const foundMapObject = utils::MakeEyeMapObject(ft); + if (!foundMapObject.IsEmpty() && mapObject.AlmostEquals(foundMapObject)) + { + FillInfoFromFeatureType(ft, info); + found = true; + } + }, + rect, scales::GetUpperScale()); + + return found; +} diff --git a/map/framework.hpp b/map/framework.hpp index ef18753e12..818347fa0d 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -908,4 +908,6 @@ public: // TipsApi::Delegate override. bool HaveTransit(m2::PointD const & pt) const override; double GetLastBackgroundTime() const override; + + bool MakePlacePageInfo(eye::MapObject const & mapObject, place_page::Info & info); }; diff --git a/map/notifications/notification_manager.cpp b/map/notifications/notification_manager.cpp index ee7ed0859c..b7e6fa52bf 100644 --- a/map/notifications/notification_manager.cpp +++ b/map/notifications/notification_manager.cpp @@ -132,6 +132,8 @@ void NotificationManager::Load() void NotificationManager::TrimExpired() { auto & candidates = m_queue.m_candidates; + size_t sizeBefore = candidates.size(); + candidates.erase(std::remove_if(candidates.begin(), candidates.end(), [](auto const & item) { if (item.m_used.time_since_epoch().count() != 0) @@ -140,7 +142,8 @@ void NotificationManager::TrimExpired() return Clock::now() - item.m_created >= kCandidatesExpirePeriod; }), candidates.end()); - VERIFY(Save(), ()); + if (sizeBefore != candidates.size()) + VERIFY(Save(), ()); } boost::optional NotificationManager::GetNotification() @@ -221,6 +224,7 @@ void NotificationManager::ProcessUgcRateCandidates(eye::MapObject const & poi) candidate.m_type = NotificationCandidate::Type::UgcReview; candidate.m_created = Clock::now(); candidate.m_mapObject = std::make_shared(poi); + candidate.m_mapObject->GetEditableEvents().clear(); m_queue.m_candidates.emplace_back(std::move(candidate)); VERIFY(Save(), ()); diff --git a/map/notifications/notification_queue.hpp b/map/notifications/notification_queue.hpp index de70147dfe..cb4e9e170b 100644 --- a/map/notifications/notification_queue.hpp +++ b/map/notifications/notification_queue.hpp @@ -15,8 +15,8 @@ struct NotificationCandidate { enum class Type : uint8_t { - UgcAuth, - UgcReview + UgcAuth = 0, + UgcReview = 1 }; DECLARE_VISITOR(visitor(m_type, "type"), visitor(m_created, "created_time"), diff --git a/map/utils.hpp b/map/utils.hpp index 5dc3fcf8c8..b3d4c8589b 100644 --- a/map/utils.hpp +++ b/map/utils.hpp @@ -6,10 +6,6 @@ namespace place_page { class Info; } -namespace eye -{ -class MapObject; -} class FeatureType; diff --git a/metrics/eye.cpp b/metrics/eye.cpp index 6f8ce75dfd..cb24332d42 100644 --- a/metrics/eye.cpp +++ b/metrics/eye.cpp @@ -23,10 +23,10 @@ namespace auto constexpr kMapObjectEventsExpirePeriod = std::chrono::hours(24 * 30 * 3); auto constexpr kEventCooldown = std::chrono::seconds(2); -std::array const kMapEventSupportedTypes = {"amenity-bar", "amenity-cafe", +std::array const kMapEventSupportedTypes = {{"amenity-bar", "amenity-cafe", "amenity-pub", "amenity-restaurant", "amenity-fast_food", "amenity-biergarden", - "shop-bakery"}; + "shop-bakery"}}; void Load(Info & info) {