diff --git a/data/src/main/java/mapper/RatingManagerImpl.kt b/data/src/main/java/mapper/RatingManagerImpl.kt new file mode 100644 index 00000000..1ded4219 --- /dev/null +++ b/data/src/main/java/mapper/RatingManagerImpl.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package mapper + +import com.f2prateek.rx.preferences2.RxSharedPreferences +import io.reactivex.rxkotlin.Observables +import manager.AnalyticsManager +import manager.RatingManager +import javax.inject.Inject + +class RatingManagerImpl @Inject constructor( + rxPrefs: RxSharedPreferences, + private val analyticsManager: AnalyticsManager +) : RatingManager { + + companion object { + private const val SESSION_THRESHOLD = 100 + } + + private val sessions = rxPrefs.getInteger("sessions", 0) + private val rated = rxPrefs.getBoolean("rated", false) + private val dismissed = rxPrefs.getBoolean("dismissed", false) + + override val shouldShowRating = Observables.combineLatest( + sessions.asObservable(), + rated.asObservable(), + dismissed.asObservable(), { sessions, rated, dismissed -> + + sessions > SESSION_THRESHOLD && !rated && !dismissed + }) + + override fun addSession() { + sessions.set(sessions.get() + 1) + } + + override fun rate() { + analyticsManager.track("Clicked Rate") + rated.set(true) + } + + override fun dismiss() { + analyticsManager.track("Clicked Rate (Dismiss)") + dismissed.set(true) + } + +} \ No newline at end of file diff --git a/domain/src/main/java/manager/RatingManager.kt b/domain/src/main/java/manager/RatingManager.kt new file mode 100644 index 00000000..c9ecba0d --- /dev/null +++ b/domain/src/main/java/manager/RatingManager.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package manager + +import io.reactivex.Observable + +interface RatingManager { + + val shouldShowRating: Observable + + /** + * Whether or not we should show the rating UI should depend on the number of sessions + */ + fun addSession() + + fun rate() + + fun dismiss() + +} \ No newline at end of file diff --git a/presentation/src/main/java/common/Navigator.kt b/presentation/src/main/java/common/Navigator.kt index 95243590..30065ef1 100644 --- a/presentation/src/main/java/common/Navigator.kt +++ b/presentation/src/main/java/common/Navigator.kt @@ -22,6 +22,7 @@ import android.app.Activity import android.app.ActivityOptions import android.arch.lifecycle.ViewModel import android.arch.lifecycle.ViewModelProvider +import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.net.Uri @@ -56,6 +57,7 @@ import manager.NotificationManager import javax.inject.Inject import javax.inject.Singleton + @Singleton class Navigator @Inject constructor(private val context: Context, private val notificationManager: NotificationManager) { @@ -189,6 +191,20 @@ class Navigator @Inject constructor(private val context: Context, private val no startActivity(intent) } + + fun showRating() { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=${context.packageName}")) + .addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY + or Intent.FLAG_ACTIVITY_NEW_DOCUMENT + or Intent.FLAG_ACTIVITY_MULTIPLE_TASK) + + try { + startActivity(intent) + } catch (e: ActivityNotFoundException) { + startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=" + context.packageName))) + } + } + /** * Launch the Play Store and display the Should I Answer? listing */ diff --git a/presentation/src/main/java/feature/main/MainActivity.kt b/presentation/src/main/java/feature/main/MainActivity.kt index 43ba5c5c..3797dba6 100644 --- a/presentation/src/main/java/feature/main/MainActivity.kt +++ b/presentation/src/main/java/feature/main/MainActivity.kt @@ -81,6 +81,8 @@ class MainActivity : QkThemedActivity(), MainView { plus.clicks().map { DrawerItem.PLUS }, help.clicks().map { DrawerItem.HELP })) } + override val dismissRatingIntent by lazy { rateDismiss.clicks() } + override val rateIntent by lazy { rateOkay.clicks() } override val conversationClickIntent by lazy { conversationsAdapter.clicks } override val conversationLongClickIntent by lazy { conversationsAdapter.longClicks } override val conversationMenuItemIntent by lazy { dialog.adapter.menuItemClicks } @@ -137,6 +139,7 @@ class MainActivity : QkThemedActivity(), MainView { colors.theme .doOnNext { color -> syncingProgress.indeterminateTintList = ColorStateList.valueOf(color) } .doOnNext { color -> itemTouchCallback.color = color } + .doOnNext { color -> rateIcon.setTint(color) } .doOnNext { color -> compose.setBackgroundTint(color) } .autoDisposable(scope()) .subscribe() @@ -172,6 +175,7 @@ class MainActivity : QkThemedActivity(), MainView { .doOnNext { color -> inbox.background = rowBackground(color) } .doOnNext { color -> archived.background = rowBackground(color) } .doOnNext { color -> scheduled.background = rowBackground(color) } + .doOnNext { color -> rateLayout.setBackgroundTint(color) } .autoDisposable(scope()) .subscribe() @@ -189,6 +193,7 @@ class MainActivity : QkThemedActivity(), MainView { syncing.setVisible(state.syncing) synced.setVisible(!state.syncing) + rateLayout.setVisible(state.showRating) when (state.page) { is Inbox -> { diff --git a/presentation/src/main/java/feature/main/MainState.kt b/presentation/src/main/java/feature/main/MainState.kt index e1451da1..f89e4492 100644 --- a/presentation/src/main/java/feature/main/MainState.kt +++ b/presentation/src/main/java/feature/main/MainState.kt @@ -24,7 +24,8 @@ import model.Conversation data class MainState( val page: MainPage = Inbox(), val drawerOpen: Boolean = false, - val syncing: Boolean = false + val syncing: Boolean = false, + val showRating: Boolean = false ) sealed class MainPage diff --git a/presentation/src/main/java/feature/main/MainView.kt b/presentation/src/main/java/feature/main/MainView.kt index 3bfd59c3..7237ef06 100644 --- a/presentation/src/main/java/feature/main/MainView.kt +++ b/presentation/src/main/java/feature/main/MainView.kt @@ -29,6 +29,8 @@ interface MainView : QkView { val composeIntent: Observable val drawerOpenIntent: Observable val drawerItemIntent: Observable + val dismissRatingIntent: Observable<*> + val rateIntent: Observable<*> val conversationClickIntent: Observable val conversationLongClickIntent: Observable val conversationMenuItemIntent: Observable diff --git a/presentation/src/main/java/feature/main/MainViewModel.kt b/presentation/src/main/java/feature/main/MainViewModel.kt index 9ceeff02..67429870 100644 --- a/presentation/src/main/java/feature/main/MainViewModel.kt +++ b/presentation/src/main/java/feature/main/MainViewModel.kt @@ -42,6 +42,7 @@ import io.reactivex.Observable import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.withLatestFrom import io.realm.Realm +import manager.RatingManager import model.SyncLog import repository.MessageRepository import util.Preferences @@ -52,7 +53,6 @@ import javax.inject.Inject class MainViewModel : QkViewModel(MainState()) { @Inject lateinit var context: Context - @Inject lateinit var navigator: Navigator @Inject lateinit var conversationFilter: ConversationFilter @Inject lateinit var messageRepo: MessageRepository @Inject lateinit var markAllSeen: MarkAllSeen @@ -61,8 +61,10 @@ class MainViewModel : QkViewModel(MainState()) { @Inject lateinit var markUnarchived: MarkUnarchived @Inject lateinit var markBlocked: MarkBlocked @Inject lateinit var migratePreferences: MigratePreferences + @Inject lateinit var navigator: Navigator @Inject lateinit var partialSync: PartialSync @Inject lateinit var prefs: Preferences + @Inject lateinit var ratingManager: RatingManager private val menuArchive by lazy { MenuItem(context.getString(R.string.inbox_menu_archive), 0) } private val menuUnarchive by lazy { MenuItem(context.getString(R.string.inbox_menu_unarchive), 1) } @@ -95,6 +97,9 @@ class MainViewModel : QkViewModel(MainState()) { .distinctUntilChanged() .subscribe { syncing -> newState { it.copy(syncing = syncing) } } + disposables += ratingManager.shouldShowRating + .subscribe { show -> newState { it.copy(showRating = show) } } + // Migrate the preferences from 2.7.3 if necessary migratePreferences.execute(Unit) @@ -110,6 +115,7 @@ class MainViewModel : QkViewModel(MainState()) { navigator.showSetupActivity() } + ratingManager.addSession() markAllSeen.execute(Unit) } @@ -162,6 +168,17 @@ class MainViewModel : QkViewModel(MainState()) { .autoDisposable(view.scope()) .subscribe() + view.rateIntent + .autoDisposable(view.scope()) + .subscribe { + navigator.showRating() + ratingManager.rate() + } + + view.dismissRatingIntent + .autoDisposable(view.scope()) + .subscribe { ratingManager.dismiss() } + view.conversationClickIntent .doOnNext { view.clearSearch() } .doOnNext { threadId -> navigator.showConversation(threadId) } diff --git a/presentation/src/main/java/injection/AppModule.kt b/presentation/src/main/java/injection/AppModule.kt index 7c20229b..e9d45e6e 100644 --- a/presentation/src/main/java/injection/AppModule.kt +++ b/presentation/src/main/java/injection/AppModule.kt @@ -35,6 +35,7 @@ import manager.KeyManagerImpl import manager.NotificationManager import manager.PermissionManager import manager.PermissionManagerImpl +import manager.RatingManager import manager.WidgetManager import manager.WidgetManagerImpl import mapper.CursorToContact @@ -45,8 +46,9 @@ import mapper.CursorToMessage import mapper.CursorToMessageImpl import mapper.CursorToPart import mapper.CursorToPartImpl -import mapper.CursorToRecipientImpl import mapper.CursorToRecipient +import mapper.CursorToRecipientImpl +import mapper.RatingManagerImpl import repository.ContactRepository import repository.ContactRepositoryImpl import repository.ImageRepository @@ -95,6 +97,9 @@ class AppModule(private var application: Application) { @Provides fun providePermissionsManager(manager: PermissionManagerImpl): PermissionManager = manager + @Provides + fun provideRatingManager(manager: RatingManagerImpl): RatingManager = manager + @Provides fun provideWidgetManager(manager: WidgetManagerImpl): WidgetManager = manager diff --git a/presentation/src/main/res/layout/drawer_view.xml b/presentation/src/main/res/layout/drawer_view.xml index e5e1109a..e204623f 100644 --- a/presentation/src/main/res/layout/drawer_view.xml +++ b/presentation/src/main/res/layout/drawer_view.xml @@ -18,12 +18,13 @@ ~ along with QKSMS. If not, see . --> + android:paddingTop="8dp" + tools:background="@color/white"> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 1ed8d159..62b43e6f 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -50,6 +50,11 @@ QKSMS+ Help & feedback + Enjoying QKSMS? + Share some love and rate us on Google Play! + OKAY! + DISMISS + Your conversations will appear here No results Archive