diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 30aa626..0d15693 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,29 +1,134 @@ - - - - - - - - - - + + + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
\ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 9f1bae2..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..19364dc --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 99202cc..af0bbdd 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,29 +1,9 @@ - - - + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml index c2b81ab..a19cef9 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,12 +2,9 @@ - - - - - - + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index e46958d..c4d42b6 100644 --- a/build.gradle +++ b/build.gradle @@ -10,8 +10,8 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' - classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' // Add this line + classpath 'com.android.tools.build:gradle:4.0.1' + //classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' // Add this line // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/fivestarslibrary/build.gradle b/fivestarslibrary/build.gradle index 5a81f61..4385631 100644 --- a/fivestarslibrary/build.gradle +++ b/fivestarslibrary/build.gradle @@ -1,15 +1,15 @@ apply plugin: 'com.android.library' -apply plugin: 'com.github.dcendents.android-maven' +//apply plugin: 'com.github.dcendents.android-maven' group='com.github.Angtrim' android { - compileSdkVersion 27 - buildToolsVersion '28.0.3' + compileSdkVersion 29 + buildToolsVersion '29.0.2' defaultConfig { minSdkVersion 14 - targetSdkVersion 27 + targetSdkVersion 29 versionCode 1 versionName "1.0" } @@ -23,6 +23,6 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:appcompat-v7:27.1.1' - + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'com.google.android.play:core:1.8.0' } diff --git a/fivestarslibrary/src/main/java/angtrim/com/fivestarslibrary/FiveStarsDialog.java b/fivestarslibrary/src/main/java/angtrim/com/fivestarslibrary/FiveStarsDialog.java index 01047b4..623589e 100644 --- a/fivestarslibrary/src/main/java/angtrim/com/fivestarslibrary/FiveStarsDialog.java +++ b/fivestarslibrary/src/main/java/angtrim/com/fivestarslibrary/FiveStarsDialog.java @@ -1,5 +1,6 @@ package angtrim.com.fivestarslibrary; +import android.app.Activity; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -7,27 +8,44 @@ import android.graphics.PorterDuff; import android.graphics.drawable.LayerDrawable; import android.net.Uri; -import android.support.v7.app.AlertDialog; -import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; + +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.widget.RatingBar; import android.widget.TextView; +import com.google.android.play.core.review.ReviewInfo; +import com.google.android.play.core.review.ReviewManager; +import com.google.android.play.core.review.ReviewManagerFactory; +import com.google.android.play.core.tasks.OnCompleteListener; +import com.google.android.play.core.tasks.OnFailureListener; +import com.google.android.play.core.tasks.Task; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.concurrent.TimeUnit; /** * Created by angtrim on 12/09/15. */ public class FiveStarsDialog implements DialogInterface.OnClickListener { - private final static String DEFAULT_TITLE = "Rate this app"; - private final static String DEFAULT_TEXT = "How much do you love our app?"; private final static String SP_NUM_OF_ACCESS = "numOfAccess"; + private final static String SP_MAX_DATE = "maxDate"; + private final static String PATTERN = "yyyy/MM/dd"; private static final String SP_DISABLED = "disabled"; private static final String TAG = FiveStarsDialog.class.getSimpleName(); private final Context context; private boolean isForceMode = false; private final SharedPreferences sharedPrefs; + private String defaultTitle; + private String defaultText; private String supportEmail; private TextView contentTextView; private RatingBar ratingBar; @@ -38,13 +56,21 @@ public class FiveStarsDialog implements DialogInterface.OnClickListener { private int upperBound = 4; private NegativeReviewListener negativeReviewListener; private ReviewListener reviewListener; + private InAppReviewListener inAppReviewListener; private int starColor; - private String positiveButtonText = "Ok"; - private String negativeButtonText = "Not Now"; - private String neutralButtonText = "Never"; + private String positiveButtonText; + private String negativeButtonText; + private String neutralButtonText; + private boolean inAppReviewMode = false; + private boolean afterNDaysMode = false; public FiveStarsDialog(Context context, String supportEmail) { this.context = context; + negativeButtonText = context.getString(R.string.BtnLater); + positiveButtonText = context.getString(R.string.BtnOK); + neutralButtonText = context.getString(R.string.BtnNever); + defaultTitle = context.getString(R.string.RateApp); + defaultText = context.getString(R.string.DefaultText); sharedPrefs = context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE); this.supportEmail = supportEmail; } @@ -53,17 +79,20 @@ private void build() { AlertDialog.Builder builder = new AlertDialog.Builder(context); LayoutInflater inflater = LayoutInflater.from(context); dialogView = inflater.inflate(R.layout.stars, null); - String titleToAdd = (title == null) ? DEFAULT_TITLE : title; - String textToAdd = (rateText == null) ? DEFAULT_TEXT : rateText; + String titleToAdd = (title == null) ? defaultTitle : title; + String textToAdd = (rateText == null) ? defaultText : rateText; contentTextView = dialogView.findViewById(R.id.text_content); contentTextView.setText(textToAdd); ratingBar = dialogView.findViewById(R.id.ratingBar); ratingBar.setOnRatingBarChangeListener(new RatingBar.OnRatingBarChangeListener() { @Override public void onRatingChanged(RatingBar ratingBar, float v, boolean b) { - Log.d(TAG, "Rating changed : " + v); if (isForceMode && v >= upperBound) { - openMarket(); + if (inAppReviewMode) { + launchInAppReview(); + } else { + openMarket(); + } if (reviewListener != null) reviewListener.onReview((int) ratingBar.getRating()); } @@ -121,9 +150,56 @@ private void show() { } } + private boolean isMaxDateEmpty() { + if (!sharedPrefs.contains(SP_MAX_DATE)) { + return true; + } + else if (TextUtils.isEmpty(sharedPrefs.getString(SP_MAX_DATE, ""))) { + return true; + } + else { + return false; + } + } + public void showAfter(int numberOfAccess) { build(); SharedPreferences.Editor editor = sharedPrefs.edit(); + if (!afterNDaysMode) { + defaultLaunch(numberOfAccess, editor); + } + else { + launchByDates(numberOfAccess, editor); + } + } + + private void launchByDates(int numberOfAccess, SharedPreferences.Editor editor) { + Date maxDate; + if (isMaxDateEmpty()) { + Calendar c = Calendar.getInstance(); + c.add(Calendar.DATE, numberOfAccess); + + SimpleDateFormat formatter = new SimpleDateFormat(PATTERN); + maxDate = c.getTime(); + editor.putString(SP_MAX_DATE, formatter.format(maxDate)); + editor.apply(); + } else { + try { + maxDate = new SimpleDateFormat(PATTERN).parse(sharedPrefs.getString(SP_MAX_DATE, "")); + + long diffInMillie = Math.abs((new Date()).getTime() - maxDate.getTime()); + long diff = TimeUnit.DAYS.convert(diffInMillie, TimeUnit.MILLISECONDS); + + if (diff >= numberOfAccess) { + show(); + } + } catch (ParseException e) { + e.printStackTrace(); + } + } + } + + private void defaultLaunch(int numberOfAccess, SharedPreferences.Editor editor) { int numOfAccess = sharedPrefs.getInt(SP_NUM_OF_ACCESS, 0); editor.putInt(SP_NUM_OF_ACCESS, numOfAccess + 1); editor.apply(); @@ -132,31 +208,35 @@ public void showAfter(int numberOfAccess) { } } - @Override public void onClick(DialogInterface dialogInterface, int i) { - if (i == DialogInterface.BUTTON_POSITIVE) { - if (ratingBar.getRating() < upperBound) { - if (negativeReviewListener == null) { - sendEmail(); - } else { - negativeReviewListener.onNegativeReview((int) ratingBar.getRating()); + switch (i) { + case DialogInterface.BUTTON_POSITIVE: + if (ratingBar.getRating() < upperBound) { + if (negativeReviewListener == null) { + sendEmail(); + } else { + negativeReviewListener.onNegativeReview((int) ratingBar.getRating()); + } + } else if (!isForceMode) { + openMarket(); } - - } else if (!isForceMode) { - openMarket(); - } - disable(); - if (reviewListener != null) - reviewListener.onReview((int) ratingBar.getRating()); - } - if (i == DialogInterface.BUTTON_NEUTRAL) { - disable(); - } - if (i == DialogInterface.BUTTON_NEGATIVE) { - SharedPreferences.Editor editor = sharedPrefs.edit(); - editor.putInt(SP_NUM_OF_ACCESS, 0); - editor.apply(); + disable(); + if (reviewListener != null) { + reviewListener.onReview((int) ratingBar.getRating()); + } + break; + case DialogInterface.BUTTON_NEUTRAL: + disable(); + break; + case DialogInterface.BUTTON_NEGATIVE: + SharedPreferences.Editor editor = sharedPrefs.edit(); + editor.putInt(SP_NUM_OF_ACCESS, 0); + if (sharedPrefs.contains(SP_MAX_DATE)) { + editor.putString(SP_MAX_DATE, ""); + } + editor.apply(); + break; } alertDialog.hide(); } @@ -164,7 +244,6 @@ public void onClick(DialogInterface dialogInterface, int i) { public FiveStarsDialog setTitle(String title) { this.title = title; return this; - } public FiveStarsDialog setSupportEmail(String supportEmail) { @@ -242,4 +321,71 @@ public FiveStarsDialog setReviewListener(ReviewListener listener) { return this; } + /** + * Enable in-app review popup + * + * @param inAppReviewMode + * @return + */ + public FiveStarsDialog setInAppReviewMode(boolean inAppReviewMode) { + this.inAppReviewMode = inAppReviewMode; + return this; + } + + /** + * Set a listener to get notified when in app review flow completed, for example for tracking purposes + * + * Note that, The API does not indicate whether the user reviewed or not, or even whether the review dialog was shown + * + * @param inAppReviewListener + * @return + */ + public FiveStarsDialog setInAppReviewListener(InAppReviewListener inAppReviewListener) { + this.inAppReviewListener = inAppReviewListener; + return this; + } + + /** + * + * Enable launching after N days + * @param afterNDaysMode + * @return + */ + public FiveStarsDialog setAfterNDaysMode(boolean afterNDaysMode) { + this.afterNDaysMode = afterNDaysMode; + return this; + } + + private void launchInAppReview() { + final ReviewManager manager = ReviewManagerFactory.create(context); + Task request = manager.requestReviewFlow(); + request.addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + if (context instanceof Activity) { + Task flow = manager.launchReviewFlow(((Activity) context), task.getResult()); + flow.addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (inAppReviewListener != null) { + inAppReviewListener.onInAppReview(); + } + } + }); + flow.addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(Exception e) { + openMarket(); + } + }); + } else { + openMarket(); + } + } else { + openMarket(); + } + } + }); + } } diff --git a/fivestarslibrary/src/main/java/angtrim/com/fivestarslibrary/InAppReviewListener.java b/fivestarslibrary/src/main/java/angtrim/com/fivestarslibrary/InAppReviewListener.java new file mode 100644 index 0000000..296f4ed --- /dev/null +++ b/fivestarslibrary/src/main/java/angtrim/com/fivestarslibrary/InAppReviewListener.java @@ -0,0 +1,5 @@ +package angtrim.com.fivestarslibrary; + +public interface InAppReviewListener { + void onInAppReview(); +} diff --git a/fivestarslibrary/src/main/res/values/strings.xml b/fivestarslibrary/src/main/res/values/strings.xml index 3c5d2ce..74edfda 100644 --- a/fivestarslibrary/src/main/res/values/strings.xml +++ b/fivestarslibrary/src/main/res/values/strings.xml @@ -1,3 +1,8 @@ FiveStarsLibrary - + Not now + OK + Never + Rate this app + How much do you love this app? + \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 1d3591c..915f0e6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,6 @@ # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true \ No newline at end of file +# org.gradle.parallel=true +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f13c268..d82b40b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Dec 14 15:24:32 EET 2018 +#Mon Aug 10 07:47:22 CEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip diff --git a/sampleapp/build.gradle b/sampleapp/build.gradle index ff8176e..b8bd5c2 100644 --- a/sampleapp/build.gradle +++ b/sampleapp/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 28 - buildToolsVersion '28.0.3' + compileSdkVersion 29 + buildToolsVersion '29.0.2' defaultConfig { applicationId "angtrim.com.sampleapp" minSdkVersion 14 - targetSdkVersion 28 + targetSdkVersion 29 versionCode 1 versionName "1.0" } @@ -21,6 +21,6 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'androidx.appcompat:appcompat:1.2.0' implementation project(':fivestarslibrary') } diff --git a/sampleapp/src/main/java/angtrim/com/sampleapp/MainActivity.java b/sampleapp/src/main/java/angtrim/com/sampleapp/MainActivity.java index 4e67837..50efbb4 100644 --- a/sampleapp/src/main/java/angtrim/com/sampleapp/MainActivity.java +++ b/sampleapp/src/main/java/angtrim/com/sampleapp/MainActivity.java @@ -1,7 +1,7 @@ package angtrim.com.sampleapp; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; import android.util.Log; import android.view.Menu; import android.view.MenuItem; @@ -26,8 +26,9 @@ protected void onCreate(Bundle savedInstanceState) { .setUpperBound(2) .setNegativeReviewListener(this) .setReviewListener(this) + .setInAppReviewMode(true) + .setAfterNDaysMode(true) .showAfter(0); - } @Override