diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..7d61466 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: [fanmixco] +custom: ['https://www.buymeacoffee.com/fanmixco'] diff --git a/.gitignore b/.gitignore index 37a96c4..b84cefa 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ captures/ # IntelliJ *.iml +.idea .idea/workspace.xml .idea/tasks.xml .idea/gradle.xml @@ -62,4 +63,5 @@ fastlane/report.xml fastlane/Preview.html fastlane/screenshots fastlane/test_output -fastlane/readme.md \ No newline at end of file +fastlane/readme.md +*.hprof 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..b9b1ecd 100644 --- a/build.gradle +++ b/build.gradle @@ -1,29 +1,18 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - buildscript { repositories { jcenter() - maven { - url 'https://maven.google.com/' - name 'Google' - } + mavenCentral() // Use mavenCentral() instead of jcenter() google() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.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 + classpath 'com.android.tools.build:gradle:8.5.1' + // Update to the latest stable version (7.5.2) } } allprojects { repositories { - jcenter() - maven { - url 'https://maven.google.com/' - name 'Google' - } + mavenCentral() + google() } } diff --git a/fivestarslibrary/build.gradle b/fivestarslibrary/build.gradle index 5a81f61..5015f64 100644 --- a/fivestarslibrary/build.gradle +++ b/fivestarslibrary/build.gradle @@ -1,17 +1,13 @@ apply plugin: 'com.android.library' -apply plugin: 'com.github.dcendents.android-maven' group='com.github.Angtrim' android { - compileSdkVersion 27 - buildToolsVersion '28.0.3' + compileSdk 34 defaultConfig { - minSdkVersion 14 - targetSdkVersion 27 - versionCode 1 - versionName "1.0" + minSdkVersion 21 + targetSdkVersion 34 } buildTypes { release { @@ -19,10 +15,13 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + namespace 'angtrim.com.fivestarslibrary' } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'com.google.android.gms:play-services-tasks:18.2.0' + implementation 'com.google.android.play:review:2.0.1' } diff --git a/fivestarslibrary/src/main/AndroidManifest.xml b/fivestarslibrary/src/main/AndroidManifest.xml index eecb416..6f1f94b 100644 --- a/fivestarslibrary/src/main/AndroidManifest.xml +++ b/fivestarslibrary/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/fivestarslibrary/src/main/java/angtrim/com/fivestarslibrary/FiveStarsDialog.java b/fivestarslibrary/src/main/java/angtrim/com/fivestarslibrary/FiveStarsDialog.java index 01047b4..37fde26 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,45 @@ 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.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.OnFailureListener; +import com.google.android.gms.tasks.Task; +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 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 +57,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 +80,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 +151,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(); @@ -135,28 +212,33 @@ 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(); } @@ -242,4 +324,60 @@ 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(task -> { + if (task.isSuccessful()) { + if (context instanceof Activity) { + Task flow = manager.launchReviewFlow(((Activity) context), task.getResult()); + flow.addOnCompleteListener(task1 -> { + if (inAppReviewListener != null) { + inAppReviewListener.onInAppReview(); + } + }); + flow.addOnFailureListener(e -> openMarket()); + } else { + openMarket(); + } + } else { + openMarket(); + } + }); + } +} \ No newline at end of file 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/layout/stars.xml b/fivestarslibrary/src/main/res/layout/stars.xml index 3c3bd79..fc5f690 100644 --- a/fivestarslibrary/src/main/res/layout/stars.xml +++ b/fivestarslibrary/src/main/res/layout/stars.xml @@ -1,23 +1,22 @@ - - - - - - - - - + \ No newline at end of file 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..8ab82c2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,8 @@ # 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.defaults.buildfeatures.buildconfig=true +android.nonFinalResIds=false +android.nonTransitiveRClass=false +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..3ac0493 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 +#Sun Aug 04 12:01:19 CEST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/sampleapp/build.gradle b/sampleapp/build.gradle index ff8176e..1ac891c 100644 --- a/sampleapp/build.gradle +++ b/sampleapp/build.gradle @@ -1,26 +1,27 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 28 - buildToolsVersion '28.0.3' + compileSdk 34 defaultConfig { applicationId "angtrim.com.sampleapp" - minSdkVersion 14 - targetSdkVersion 28 + minSdkVersion 21 + targetSdkVersion 34 versionCode 1 versionName "1.0" } + buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + namespace 'angtrim.com.sampleapp' } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'androidx.appcompat:appcompat:1.7.0' implementation project(':fivestarslibrary') -} +} \ No newline at end of file diff --git a/sampleapp/src/main/AndroidManifest.xml b/sampleapp/src/main/AndroidManifest.xml index 8976761..634b326 100644 --- a/sampleapp/src/main/AndroidManifest.xml +++ b/sampleapp/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + diff --git a/sampleapp/src/main/java/angtrim/com/sampleapp/MainActivity.java b/sampleapp/src/main/java/angtrim/com/sampleapp/MainActivity.java index 4e67837..4fecb53 100644 --- a/sampleapp/src/main/java/angtrim/com/sampleapp/MainActivity.java +++ b/sampleapp/src/main/java/angtrim/com/sampleapp/MainActivity.java @@ -1,11 +1,10 @@ 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; - import angtrim.com.fivestarslibrary.FiveStarsDialog; import angtrim.com.fivestarslibrary.NegativeReviewListener; import angtrim.com.fivestarslibrary.ReviewListener; @@ -26,6 +25,8 @@ protected void onCreate(Bundle savedInstanceState) { .setUpperBound(2) .setNegativeReviewListener(this) .setReviewListener(this) + .setInAppReviewMode(true) + .setAfterNDaysMode(true) .showAfter(0); }