Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transcribe in Kotlin? #113

Open
webserveis opened this issue Feb 11, 2020 · 1 comment
Open

Transcribe in Kotlin? #113

webserveis opened this issue Feb 11, 2020 · 1 comment

Comments

@webserveis
Copy link

No description provided.

@webserveis
Copy link
Author

I transcribe this library and modify parts

Change AlertDialog for MaterialDialogBuilder

import android.content.*
import android.content.SharedPreferences.Editor
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.text.TextUtils
import android.util.Log
import android.view.KeyEvent
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.lang.ref.WeakReference
import java.util.*
import java.util.concurrent.TimeUnit


class RateThisApp(val context: Context) {
    companion object {
        private val TAG = RateThisApp::class.java.simpleName

        private const val PREF_NAME = "RateThisApp"
        private const val KEY_INSTALL_DATE = "rta_install_date"
        private const val KEY_LAUNCH_TIMES = "rta_launch_times"
        private const val KEY_OPT_OUT = "rta_opt_out"
        private const val KEY_ASK_LATER_DATE = "rta_ask_later_date"
    }

    private var mInstallDate: Date = Date()
    private var mLaunchTimes = 0
    private var mOptOut = false
    private var mAskLaterDate: Date = Date()

    private var sBuilder: Builder = Builder()
    private var sCallback: Callback? = null

    // Weak ref to avoid leaking the context
    private var sDialogRef: WeakReference<AlertDialog>? = null

    val DEBUG = false

    fun init(builder: Builder) {
        sBuilder = builder
    }


    /**
     * Set callback instance.
     * The callback will receive yes/no/later events.
     * @param callback
     */
    fun setCallback(callback: Callback) {
        sCallback = callback
    }

    /**
     * Call this API when the launcher activity is launched.<br></br>
     * It is better to call this API in onCreate() of the launcher activity.
     */
    init {
        val pref: SharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
        val editor = pref.edit()
        // If it is the first launch, save the date in shared preference.
        if (pref.getLong(KEY_INSTALL_DATE, 0) == 0L) {
            storeInstallDate(editor)
        }
        // Increment launch times
        var launchTimes = pref.getInt(KEY_LAUNCH_TIMES, 0)
        launchTimes++
        editor.putInt(KEY_LAUNCH_TIMES, launchTimes)

        log("Launch times; $launchTimes")
        editor.apply()
        mInstallDate = Date(pref.getLong(KEY_INSTALL_DATE, 0))
        mLaunchTimes = pref.getInt(KEY_LAUNCH_TIMES, 0)
        mOptOut = pref.getBoolean(KEY_OPT_OUT, false)
        mAskLaterDate = Date(pref.getLong(KEY_ASK_LATER_DATE, 0))
        printStatus()
    }

    /**
     * Show the rate dialog if the criteria is satisfied.
     * @return true if shown, false otherwise.
     */
    fun showRateDialogIfNeeded(): Boolean {
        return if (shouldShowRateDialog()) {
            showRateDialog()
            true
        } else {
            false
        }
    }

    /**
     * Show the rate dialog if the criteria is satisfied.
     * @param themeId Theme ID
     * @return true if shown, false otherwise.
     */
    fun showRateDialogIfNeeded(themeId: Int): Boolean {
        return if (shouldShowRateDialog()) {
            showRateDialog(themeId)
            true
        } else {
            false
        }
    }

    /**
     * Check whether the rate dialog should be shown or not.
     * Developers may call this method directly if they want to show their own view instead of
     * dialog provided by this library.
     * @return
     */
    fun shouldShowRateDialog(): Boolean {
        return if (mOptOut) {
            false
        } else {
            if (mLaunchTimes >= sBuilder.mCriteriaLaunchTimes) {
                return true
            }
            val threshold: Long = TimeUnit.DAYS.toMillis(sBuilder.mCriteriaInstallDays.toLong()) // msec
            Date().time - mInstallDate.time >= threshold &&
                    Date().time - mAskLaterDate.time >= threshold
        }
    }

    /**
     * Show the rate dialog
     */
    fun showRateDialog() {
        val builder = MaterialAlertDialogBuilder(context)
        showRateDialog(builder)
    }

    /**
     * Show the rate dialog
     * @param themeId
     */
    fun showRateDialog(themeId: Int) {
        val builder = MaterialAlertDialogBuilder(context, themeId)
        showRateDialog(builder)
    }

    /**
     * Stop showing the rate dialog
     */
    fun stopRateDialog() {
        setOptOut(true)
    }

    /**
     * Get count number of the rate dialog launches
     * @return
     */
    fun getLaunchCount(): Int {
        val pref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
        return pref.getInt(KEY_LAUNCH_TIMES, 0)
    }

    private fun showRateDialog(builder: MaterialAlertDialogBuilder) {
        if (sDialogRef != null && sDialogRef?.get() != null) {
            // Dialog is already present
            return
        }
        val titleId = if (sBuilder.mTitleId != 0) sBuilder.mTitleId else R.string.rta_dialog_title
        val messageId = if (sBuilder.mMessageId != 0) sBuilder.mMessageId else R.string.rta_dialog_message
        val cancelButtonID = if (sBuilder.mCancelButton != 0) sBuilder.mCancelButton else R.string.rta_dialog_cancel
        val thanksButtonID = if (sBuilder.mNoButtonId != 0) sBuilder.mNoButtonId else R.string.rta_dialog_no
        val rateButtonID = if (sBuilder.mYesButtonId != 0) sBuilder.mYesButtonId else R.string.rta_dialog_ok
        builder.setTitle(titleId)
        builder.setMessage(messageId)
        when (sBuilder.mCancelMode) {
            Builder.CANCEL_MODE_BACK_KEY_OR_TOUCH_OUTSIDE -> builder.setCancelable(true) // It's the default anyway
            Builder.CANCEL_MODE_BACK_KEY -> {
                builder.setCancelable(false)
                builder.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
                    if (keyCode == KeyEvent.KEYCODE_BACK) {
                        dialog.cancel()
                        true
                    } else {
                        false
                    }
                })
            }
            Builder.CANCEL_MODE_NONE -> builder.setCancelable(false)
        }
        builder.setPositiveButton(rateButtonID) { dialog, which ->
            if (sCallback != null) {
                sCallback!!.onRateNowClicked()
            }
            val appPackage = context.packageName
            var url: String? = "market://details?id=$appPackage"
            if (!TextUtils.isEmpty(sBuilder.mUrl)) {
                url = sBuilder.mUrl
            }
            try {
                context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
            } catch (anfe: ActivityNotFoundException) {
                context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=" + context.packageName)))
            }
            setOptOut(true)
        }
        builder.setNeutralButton(cancelButtonID) { dialog, which ->
            if (sCallback != null) {
                sCallback!!.onLaterClicked()
            }
            clearSharedPreferences()
            storeAskLaterDate()
        }
        builder.setNegativeButton(thanksButtonID) { dialog, which ->
            if (sCallback != null) {
                sCallback!!.onNoThanksClicked()
            }
            setOptOut(true)
        }
        builder.setOnCancelListener {
            if (sCallback != null) {
                sCallback!!.onLaterClicked()
            }
            clearSharedPreferences()
            storeAskLaterDate()
        }
        builder.setOnDismissListener { sDialogRef?.clear() }
        sDialogRef = WeakReference(builder.show())
    }


    /**
     * Clear data in shared preferences.<br></br>
     * This API is called when the "Later" is pressed or canceled.
     */
    private fun clearSharedPreferences() {
        val pref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
        val editor = pref.edit()
        editor.remove(KEY_INSTALL_DATE)
        editor.remove(KEY_LAUNCH_TIMES)
        editor.apply()
    }

    /**
     * Set opt out flag.
     * If it is true, the rate dialog will never shown unless app data is cleared.
     * This method is called when Yes or No is pressed.
     * @param optOut
     */
    private fun setOptOut(optOut: Boolean) {
        val pref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
        val editor = pref.edit()
        editor.putBoolean(KEY_OPT_OUT, optOut)
        editor.apply()
        mOptOut = optOut
    }

    /**
     * Store install date.
     * Install date is retrieved from package manager if possible.
     * @param editor
     */
    private fun storeInstallDate(editor: Editor) {
        var installDate = Date()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
            val packMan = context.packageManager
            try {
                val pkgInfo = packMan.getPackageInfo(context.packageName, 0)
                installDate = Date(pkgInfo.firstInstallTime)
            } catch (e: PackageManager.NameNotFoundException) {
                e.printStackTrace()
            }
        }
        editor.putLong(KEY_INSTALL_DATE, installDate.time)
        log("First install: $installDate")
    }

    /**
     * Store the date the user asked for being asked again later.
     */
    private fun storeAskLaterDate() {
        val pref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
        val editor = pref.edit()
        editor.putLong(KEY_ASK_LATER_DATE, System.currentTimeMillis())
        editor.apply()
    }

    /**
     * Print values in SharedPreferences (used for debug)
     */
    private fun printStatus() {
        val pref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
        log("*** RateThisApp Status ***")
        log("Install Date: " + Date(pref.getLong(KEY_INSTALL_DATE, 0)))
        log("Launch Times: " + pref.getInt(KEY_LAUNCH_TIMES, 0))
        log("Opt out: " + pref.getBoolean(KEY_OPT_OUT, false))
    }

    /**
     * Print log if enabled
     */
    private fun log(message: String) {
        if (DEBUG) {
            Log.v(TAG, message)
        }
    }

    class Builder(internal val mCriteriaInstallDays: Int = 7, internal val mCriteriaLaunchTimes: Int = 10) {

        companion object {
            const val CANCEL_MODE_BACK_KEY_OR_TOUCH_OUTSIDE = 0
            const val CANCEL_MODE_BACK_KEY = 1
            const val CANCEL_MODE_NONE = 2
        }

        internal var mUrl: String? = null
        internal var mTitleId = 0
        internal var mMessageId = 0
        internal var mYesButtonId = 0
        internal var mNoButtonId = 0
        internal var mCancelButton = 0
        internal var mCancelMode = CANCEL_MODE_BACK_KEY_OR_TOUCH_OUTSIDE

        /**
         * Set title string ID.
         * @param stringId
         */
        fun setTitle(@StringRes stringId: Int) {
            mTitleId = stringId
        }

        /**
         * Set message string ID.
         * @param stringId
         */
        fun setMessage(@StringRes stringId: Int) {
            mMessageId = stringId
        }

        /**
         * Set rate now string ID.
         * @param stringId
         */
        fun setYesButtonText(@StringRes stringId: Int) {
            mYesButtonId = stringId
        }

        /**
         * Set no thanks string ID.
         * @param stringId
         */
        fun setNoButtonText(@StringRes stringId: Int) {
            mNoButtonId = stringId
        }

        /**
         * Set cancel string ID.
         * @param stringId
         */
        fun setCancelButtonText(@StringRes stringId: Int) {
            mCancelButton = stringId
        }

        /**
         * Set navigation url when user clicks rate button.
         * Typically, url will be https://play.google.com/store/apps/details?id=PACKAGE_NAME for Google Play.
         * @param url
         */
        fun setUrl(url: String?) {
            mUrl = url
        }

        /**
         * Set the cancel mode; namely, which ways the user can cancel the dialog.
         * @param cancelMode
         */
        fun setCancelMode(cancelMode: Int) {
            mCancelMode = cancelMode
        }

    }


    interface Callback {
        /**
         * "Rate now" event
         */
        fun onRateNowClicked()

        /**
         * "No, thanks" event
         */
        fun onNoThanksClicked()

        /**
         * "Later" event
         */
        fun onLaterClicked()
    }
}

Usage:

val rateThisAppDialog = RateThisApp(this)
val builder = RateThisApp.Builder(3, 5)
rateThisAppDialog.init(builder)
rateThisAppDialog.showRateDialogIfNeeded()

Personalize with Material Design 2.0

 <!-- Dialogs rounded -->
    <style name="ThemeOverlay.MaterialAlertDialog.Rounded" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog">
        <item name="alertDialogStyle">@style/MaterialAlertDialog.Rounded</item>
    </style>

    <style name="MaterialAlertDialog.Rounded" parent="MaterialAlertDialog.MaterialComponents">
        <item name="shapeAppearance">@style/ShapeAppearance.MaterialAlertDialog.Rounded</item>
    </style>

    <style name="ShapeAppearance.MaterialAlertDialog.Rounded" parent="">
        <item name="cornerFamily">rounded</item>
        <item name="cornerSize">16dp</item>
    </style>

In base theme asign materialAlertDialogTheme

<item name="materialAlertDialogTheme">@style/ThemeOverlay.MaterialAlertDialog.Rounded</item>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant