Skip to content

Commit

Permalink
Merge pull request #18 from SuddenH4X/release/2.2.0
Browse files Browse the repository at this point in the history
Release/2.2.0
  • Loading branch information
SuddenH4X authored Aug 31, 2020
2 parents 6ab51c2 + 677825f commit 0be17d8
Show file tree
Hide file tree
Showing 14 changed files with 318 additions and 60 deletions.
31 changes: 29 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@ A highly customizable Android library providing a dialog, which asks the user to

![showcase](https://github.com/SuddenH4X/awesome-app-rating/raw/develop/preview/showcase.png)



You can also use this library to show the [Google in-app review](https://developer.android.com/guide/playcore/in-app-review) easily under certain conditions:

<img src="https://developer.android.com/images/google/play/in-app-review/iar-flow.jpg" alt="In app review workflow for a user" />

(Source: https://developer.android.com/guide/playcore/in-app-review)

## Features
- Let the dialog (or the [Google in-app review](https://developer.android.com/guide/playcore/in-app-review)) show up at a defined app session, after n days of usage and/or if your custom conditions meet
- Auto fetches the app icon to use it in the dialog
- Let the dialog show up at a defined app session, after n days of usage and/or if your custom conditions meet
- Ask the user to mail his feedback or show a custom feedback form if the user rates below the defined minimum threshold
- All titles, messages and buttons are customizable
- You can override all click listeners to fit your needs (or to implement extensive tracking)
Expand All @@ -32,7 +40,7 @@ The library supports API level 14 and higher. You can simply include it in your
```groovy
dependencies {
...
implementation 'com.suddenh4x.ratingdialog:awesome-app-rating:2.1.1'
implementation 'com.suddenh4x.ratingdialog:awesome-app-rating:2.2.0'
}
```

Expand Down Expand Up @@ -83,6 +91,22 @@ ratingBuilder.showNow()

Between the constructor and the show or create method you can adjust the dialog to suit your preferences. You have the following options:

#### Google in-app review

If you want to use the in-app review from Google instead of the library dialog, call the following function:

```kotlin
.useGoogleInAppReview()
```

You should also add a `completeListener` which gets called if the in-app review flow has been completed. The boolean indicates if the flow started correctly, but not if the in-app review was displayed to the user.

```kotlin
.setGoogleInAppReviewCompleteListener(googleInAppReviewCompleteListener: (Boolean) -> Unit)
```

Note: After the first in-app review flow was completed successfully the `toShowAgain` conditions will be used. For example `.setMinimumLaunchTimesToShowAgain(launchTimesToShowAgain: Int)` instead of `.setMinimumLaunchTimes(launchTimes: Int)`.

#### When to show up

- Change the number of days the app has to be installed
Expand Down Expand Up @@ -129,6 +153,8 @@ Between the constructor and the show or create method you can adjust the dialog

#### Design

The following settings will only take effect if the library dialog is used (and not the Google in-app review).

##### General

- Change the icon of the dialog
Expand Down Expand Up @@ -411,6 +437,7 @@ If you want to show the dialog on app start, but with your custom conditions, yo

## Note

* If the in-app review from Google will be used: After the first in-app review flow was completed successfully the `toShowAgain` conditions will be used. For example `.setMinimumLaunchTimesToShowAgain(launchTimesToShowAgain: Int)` instead of `.setMinimumLaunchTimes(launchTimes: Int)`
* Use a MaterialComponent theme for better design
* Don't forget to set up the mail settings if you want to use the mail feedback dialog (otherwise nothing will happen)
* Use `setRatingThreshold(RatingThreshold.NONE)` if you don't want to show the feedback form to the user
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
ext.kotlin_version = '1.3.72'
ext.kotlin_version = '1.4.0'
repositories {
google()
jcenter()
Expand Down
6 changes: 3 additions & 3 deletions exampleapp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0'
implementation project(':library')

testImplementation 'junit:junit:4.13'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ class MainActivity : AppCompatActivity() {
Toast.makeText(this, R.string.toast_reset, Toast.LENGTH_SHORT).show()
}

fun onGoogleInAppReviewExampleButtonClicked(@Suppress("UNUSED_PARAMETER") view: View) {
AppRating.Builder(this)
.useGoogleInAppReview()
.setGoogleInAppReviewCompleteListener { successful ->
Toast.makeText(
this@MainActivity,
"Google in-app review completed (successful: $successful)",
Toast.LENGTH_LONG
).show()
}
.setDebug(true)
.showIfMeetsConditions()
}

fun onDefaultExampleButtonClicked(@Suppress("UNUSED_PARAMETER") view: View) {
AppRating.Builder(this)
.setDebug(true)
Expand Down
12 changes: 12 additions & 0 deletions exampleapp/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@
android:onClick="onResetButtonClicked"
android:text="@string/button_example_reset" />

<TextView
style="@style/ExampleText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/text_example_google_in_app_review" />

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onGoogleInAppReviewExampleButtonClicked"
android:text="@string/button_example_google_in_app_review" />

<TextView
style="@style/ExampleText"
android:layout_width="match_parent"
Expand Down
4 changes: 4 additions & 0 deletions exampleapp/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
<!-- Example Buttons and Descriptions -->
<string name="text_example_note_app_reset">Note: Use the reset button below or restart the app to clear all settings.</string>
<string name="button_example_reset">Reset</string>
<string name="text_example_google_in_app_review">This example uses the Google in-app review instead of the library
dialog. Debug is enabled. (note: this example doesn\'t work at the moment because the example app hasn\'t been uploaded
to the Google Play Store)</string>
<string name="button_example_google_in_app_review">Google In-App Review</string>
<string name="text_example_default">This example has no customizations. Debug is enabled.</string>
<string name="button_example_default">Default</string>
<string name="text_example_custom_icon">In this example the default icon (app icon) has been replaced with a cusom icon.
Expand Down
14 changes: 9 additions & 5 deletions library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: "de.mannodermaus.android-junit5"
apply plugin: 'com.novoda.bintray-release'

def version = "2.1.1"
def version = "2.2.0"

android {
compileSdkVersion 29
Expand All @@ -30,12 +30,16 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.core:core-ktx:1.3.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0'
implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.annotation:annotation:1.1.0'

// needed for in-app review
implementation 'com.google.android.play:core:1.8.0'
implementation 'com.google.android.play:core-ktx:1.8.1'

testImplementation "org.junit.jupiter:junit-jupiter-api:5.5.0"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.5.0"
testImplementation "io.mockk:mockk:1.9.3"
Expand Down
6 changes: 6 additions & 0 deletions library/lint.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<lint>
<!-- Temporary until https://github.com/Kotlin/kotlinx.coroutines/issues/2004 is resolved. -->
<issue id="InvalidPackage">
<ignore path="**/kotlinx-coroutines-core-*.jar"/>
</issue>
</lint>
87 changes: 80 additions & 7 deletions library/src/main/java/com/suddenh4x/ratingdialog/AppRating.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import android.graphics.drawable.Drawable
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment
import com.google.android.play.core.review.ReviewManager
import com.google.android.play.core.review.ReviewManagerFactory
import com.suddenh4x.ratingdialog.buttons.ConfirmButtonClickListener
import com.suddenh4x.ratingdialog.buttons.CustomFeedbackButtonClickListener
import com.suddenh4x.ratingdialog.buttons.RateButton
Expand Down Expand Up @@ -41,6 +43,7 @@ object AppRating {

data class Builder(var activity: AppCompatActivity) {
internal var isDebug = false
internal var reviewManger: ReviewManager? = null
private var dialogOptions = DialogOptions()

internal constructor(activity: AppCompatActivity, dialogOptions: DialogOptions) :
Expand Down Expand Up @@ -163,7 +166,9 @@ object AppRating {
mailFeedbackButtonClickListener
}

fun setAdditionalMailFeedbackButtonClickListener(additionalMailFeedbackButtonClickListener: RateDialogClickListener) =
fun setAdditionalMailFeedbackButtonClickListener(
additionalMailFeedbackButtonClickListener: RateDialogClickListener
) =
apply {
dialogOptions.additionalMailFeedbackButtonClickListener =
additionalMailFeedbackButtonClickListener
Expand Down Expand Up @@ -221,17 +226,26 @@ object AppRating {

fun setCustomCondition(customCondition: () -> Boolean) = apply {
dialogOptions.customCondition = customCondition
RatingLogger.debug("Custom condition set. This condition will be removed next time you call the Builder constructor.")
RatingLogger.debug(
"Custom condition set. This condition will be removed next" +
" time you call the Builder constructor."
)
}

fun setCustomConditionToShowAgain(customConditionToShowAgain: () -> Boolean) = apply {
dialogOptions.customConditionToShowAgain = customConditionToShowAgain
RatingLogger.debug("Custom condition to show again set. This condition will be removed next time you call the Builder constructor.")
RatingLogger.debug(
"Custom condition to show again set. This condition will" +
"be removed next time you call the Builder constructor."
)
}

fun dontCountThisAsAppLaunch() = apply {
dialogOptions.countAppLaunch = false
RatingLogger.debug("countAppLaunch is now set to false. This setting will be reset next time you call the Builder constructor.")
RatingLogger.debug(
"countAppLaunch is now set to false. This setting will be " +
"reset next time you call the Builder constructor."
)
}

fun setLoggingEnabled(isLoggingEnabled: Boolean) = apply {
Expand All @@ -243,10 +257,49 @@ object AppRating {
RatingLogger.warn("Set debug to $isDebug. Don't use this for production.")
}

fun create(): DialogFragment = RateDialogFragment.newInstance(dialogOptions)
// Google in-app review
/**
* If this method is called, the in-app review from Google will be used instead of
* the library dialog.
*/
fun useGoogleInAppReview() = apply {
reviewManger = ReviewManagerFactory.create(activity)
dialogOptions.useGoogleInAppReview = true
RatingLogger.info("Use in-app review from Google instead of the library dialog.")
}

fun showNow() =
RateDialogFragment.newInstance(dialogOptions).show(activity.supportFragmentManager, TAG)
/**
* The completion listener will be invoked with true if the in-app review flow started
* correctly (otherwise false).
* Note: true doesn't mean that the in-app review from Google was displayed.
*/
fun setGoogleInAppReviewCompleteListener(googleInAppReviewCompleteListener: (Boolean) -> Unit) =
apply {
dialogOptions.googleInAppReviewCompleteListener = googleInAppReviewCompleteListener
}

/**
* This method will return null if the in-app review from Google is used.
*/
fun create(): DialogFragment? {
return if (dialogOptions.useGoogleInAppReview) {
RatingLogger.warn("In-app review from Google will be used. Can't create the library dialog.")
null
} else {
RateDialogFragment.newInstance(dialogOptions)
}
}

fun showNow() {
if (dialogOptions.useGoogleInAppReview) {
RatingLogger.info("In-app review from Google will be displayed now.")
showGoogleInAppReview()
} else {
RatingLogger.debug("In-app review from Google hasn't been activated. Showing library dialog now.")
RateDialogFragment.newInstance(dialogOptions)
.show(activity.supportFragmentManager, TAG)
}
}

fun showIfMeetsConditions() {
if (dialogOptions.countAppLaunch) {
Expand All @@ -264,6 +317,26 @@ object AppRating {
}
}

internal fun showGoogleInAppReview() {
val requestTask = reviewManger?.requestReviewFlow()
requestTask?.addOnCompleteListener { request ->
if (request.isSuccessful) {
val reviewInfo = request.result
val flow = reviewManger?.launchReviewFlow(activity, reviewInfo)
flow?.addOnCompleteListener { task ->
RatingLogger.info("Google in-app review request completed.")
PreferenceUtil.onGoogleInAppReviewFlowCompleted(activity)
dialogOptions.googleInAppReviewCompleteListener?.invoke(task.isSuccessful)
?: RatingLogger.warn("There's no completeListener for Google's in-app review.")
}
} else {
RatingLogger.info("Google in-app review request wasn't successful.")
dialogOptions.googleInAppReviewCompleteListener?.invoke(false)
?: RatingLogger.warn("There's no completeListener for Google's in-app review.")
}
}
}

companion object {
private val TAG = AppRating::class.java.simpleName
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,10 @@ internal object DialogManager {
val numberOfLaterButtonClicks = PreferenceUtil.getNumberOfLaterButtonClicks(context)
RatingLogger.debug("Rate later button was clicked $numberOfLaterButtonClicks times.")
if (countOfLaterButtonClicksToShowNeverButton > numberOfLaterButtonClicks) {
RatingLogger.info("Less than $countOfLaterButtonClicksToShowNeverButton later button clicks. Rate never button won't be displayed.")
RatingLogger.info(
"Less than $countOfLaterButtonClicksToShowNeverButton later " +
"button clicks. Rate never button won't be displayed."
)
return
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,8 @@ internal class DialogOptions : Serializable {

// other settings
var cancelable = false

// Google in-app review
var useGoogleInAppReview = false
var googleInAppReviewCompleteListener: ((Boolean) -> Unit)? = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ internal object ConditionsChecker {
RatingLogger.verbose("Is dialog agreed: $isDialogAgreed.")
RatingLogger.verbose("Do not show again: $isDoNotShowAgain.")

if (!checkCustomCondition(dialogOptions)) return false

if (wasLaterButtonClicked) {
RatingLogger.debug("Show later button has already been clicked.")
RatingLogger.verbose("Days between later button click and now: $daysBetween.")
Expand All @@ -34,6 +32,8 @@ internal object ConditionsChecker {
PreferenceUtil.getMinimumLaunchTimesToShowAgain(context)))
}

if (!checkCustomCondition(dialogOptions)) return false

RatingLogger.verbose("Days between first app start and now: $daysBetween.")
RatingLogger.debug("Show later button hasn't been clicked until now.")
return (!isDialogAgreed &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,18 @@ internal object PreferenceUtil {
increaseNumberOfLaterButtonClicks(context)
}

fun onGoogleInAppReviewFlowCompleted(context: Context) {
RatingLogger.verbose(
"Google in-app review flow has been completed. Update remind timestamp " +
"and set launch times to 0."
)
getPreferences(context).edit {
putLong(PREF_KEY_REMIND_TIMESTAMP, System.currentTimeMillis())
putInt(PREF_KEY_LAUNCH_TIMES, 0)
putBoolean(PREF_KEY_DIALOG_SHOW_LATER, true)
}
}

fun getRemindTimestamp(context: Context): Long {
val remindTimestamp = getPreferences(context).getLong(PREF_KEY_REMIND_TIMESTAMP, -1)
if (remindTimestamp == -1L) {
Expand Down
Loading

0 comments on commit 0be17d8

Please sign in to comment.