diff --git a/data/src/main/java/com/moez/QKSMS/util/Preferences.kt b/data/src/main/java/com/moez/QKSMS/util/Preferences.kt index 84be9307..fd1b0d2e 100644 --- a/data/src/main/java/com/moez/QKSMS/util/Preferences.kt +++ b/data/src/main/java/com/moez/QKSMS/util/Preferences.kt @@ -42,6 +42,12 @@ class Preferences @Inject constructor(private val rxPrefs: RxSharedPreferences) const val NOTIFICATION_PREVIEWS_NAME = 1 const val NOTIFICATION_PREVIEWS_NONE = 2 + const val NOTIFICATION_ACTION_NONE = 0 + const val NOTIFICATION_ACTION_READ = 1 + const val NOTIFICATION_ACTION_REPLY = 2 + const val NOTIFICATION_ACTION_CALL = 3 + const val NOTIFICATION_ACTION_DELETE = 4 + const val SEND_DELAY_NONE = 0 const val SEND_DELAY_SHORT = 1 const val SEND_DELAY_MEDIUM = 2 @@ -63,6 +69,9 @@ class Preferences @Inject constructor(private val rxPrefs: RxSharedPreferences) val delivery = rxPrefs.getBoolean("delivery", false) val systemFont = rxPrefs.getBoolean("systemFont", false) val textSize = rxPrefs.getInteger("textSize", TEXT_SIZE_NORMAL) + val notifAction1 = rxPrefs.getInteger("notifAction1", 1) + val notifAction2 = rxPrefs.getInteger("notifAction2", 2) + val notifAction3 = rxPrefs.getInteger("notifAction3", 0) val qkreply = rxPrefs.getBoolean("qkreply", Build.VERSION.SDK_INT < Build.VERSION_CODES.N) val qkreplyTapDismiss = rxPrefs.getBoolean("qkreplyTapDismiss", true) val unicode = rxPrefs.getBoolean("unicode", false) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt index d4b114d4..4c34b73e 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt @@ -109,10 +109,6 @@ class NotificationManagerImpl @Inject constructor( val seenIntent = Intent(context, MarkSeenReceiver::class.java).putExtra("threadId", threadId) val seenPI = PendingIntent.getBroadcast(context, threadId.toInt() + 20000, seenIntent, PendingIntent.FLAG_UPDATE_CURRENT) - val readIntent = Intent(context, MarkReadReceiver::class.java).putExtra("threadId", threadId) - val readPI = PendingIntent.getBroadcast(context, threadId.toInt() + 30000, readIntent, PendingIntent.FLAG_UPDATE_CURRENT) - val readAction = NotificationCompat.Action(R.drawable.ic_check_white_24dp, context.getString(R.string.notification_read), readPI) - // We can't store a null preference, so map it to a null Uri if the pref string is empty val ringtone = prefs.ringtone(threadId).get() .takeIf { it.isNotEmpty() } @@ -127,17 +123,18 @@ class NotificationManagerImpl @Inject constructor( .setAutoCancel(true) .setContentIntent(contentPI) .setDeleteIntent(seenPI) - .addAction(readAction) .setSound(ringtone) .setLights(Color.WHITE, 500, 2000) .setVibrate(if (prefs.vibration(threadId).get()) VIBRATE_PATTERN else longArrayOf(0)) + // Tell the notification if it's a group message val messagingStyle = NotificationCompat.MessagingStyle("Me") if (conversation.recipients.size >= 2) { messagingStyle.isGroupConversation = true messagingStyle.conversationTitle = conversation.getTitle() } + // Add the messages to the notification messages.forEach { message -> val name = when { message.isMe() -> null @@ -156,6 +153,7 @@ class NotificationManagerImpl @Inject constructor( } } + // Set the large icon val avatar = conversation.recipients.takeIf { it.size == 1 } ?.first()?.address ?.let { address -> @@ -167,6 +165,7 @@ class NotificationManagerImpl @Inject constructor( } ?.let { futureGet -> tryOrNull(false) { futureGet.get() } } + // Bind the notification contents based on the notification preview mode when (prefs.notificationPreviews(threadId).get()) { Preferences.NOTIFICATION_PREVIEWS_ALL -> { notification @@ -194,14 +193,40 @@ class NotificationManagerImpl @Inject constructor( .mapNotNull { recipient -> recipient.contact?.lookupKey } .forEach { uri -> notification.addPerson(uri) } - if (Build.VERSION.SDK_INT >= 24) { - notification.addAction(getReplyAction(threadId)) - } else { - val replyIntent = Intent(context, QkReplyActivity::class.java).putExtra("threadId", threadId) - val replyPI = PendingIntent.getActivity(context, threadId.toInt() + 40000, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT) - val replyAction = NotificationCompat.Action(R.drawable.ic_reply_white_24dp, context.getString(R.string.notification_reply), replyPI) - notification.addAction(replyAction) - } + // Add the action buttons + val actionLabels = context.resources.getStringArray(R.array.notification_actions) + listOf(prefs.notifAction1, prefs.notifAction2, prefs.notifAction3) + .map { preference -> preference.get() } + .distinct() + .mapNotNull { action -> + when (action) { + Preferences.NOTIFICATION_ACTION_READ -> { + val intent = Intent(context, MarkReadReceiver::class.java).putExtra("threadId", threadId) + val pi = PendingIntent.getBroadcast(context, threadId.toInt() + 30000, intent, PendingIntent.FLAG_UPDATE_CURRENT) + NotificationCompat.Action(R.drawable.ic_check_white_24dp, actionLabels[action], pi) + } + + Preferences.NOTIFICATION_ACTION_REPLY -> { + if (Build.VERSION.SDK_INT >= 24) { + getReplyAction(threadId) + } else { + val intent = Intent(context, QkReplyActivity::class.java).putExtra("threadId", threadId) + val pi = PendingIntent.getActivity(context, threadId.toInt() + 40000, intent, PendingIntent.FLAG_UPDATE_CURRENT) + NotificationCompat.Action(R.drawable.ic_reply_white_24dp, actionLabels[action], pi) + } + } + + Preferences.NOTIFICATION_ACTION_CALL -> { + val address = conversation.recipients[0]?.address + val intent = Intent(Intent.ACTION_DIAL, Uri.parse("tel:$address")) + val pi = PendingIntent.getActivity(context, threadId.toInt() + 50000, intent, PendingIntent.FLAG_UPDATE_CURRENT) + NotificationCompat.Action(R.drawable.ic_call_white_24dp, actionLabels[action], pi) + } + + else -> null + } + } + .forEach { notification.addAction(it) } if (prefs.qkreply.get()) { notification.priority = NotificationCompat.PRIORITY_DEFAULT @@ -251,15 +276,14 @@ class NotificationManagerImpl @Inject constructor( val replyIntent = Intent(context, RemoteMessagingReceiver::class.java).putExtra("threadId", threadId) val replyPI = PendingIntent.getBroadcast(context, threadId.toInt() + 40000, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT) + val title = context.resources.getStringArray(R.array.notification_actions)[Preferences.NOTIFICATION_ACTION_REPLY] val responseSet = context.resources.getStringArray(R.array.qk_responses) val remoteInput = RemoteInput.Builder("body") - .setLabel(context.getString(R.string.notification_reply)) + .setLabel(title) .setChoices(responseSet) .build() - return NotificationCompat.Action.Builder( - R.drawable.ic_reply_white_24dp, - context.getString(R.string.notification_reply), replyPI) + return NotificationCompat.Action.Builder(R.drawable.ic_reply_white_24dp, title, replyPI) .addRemoteInput(remoteInput) .build() } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsActivity.kt index d3d1d7f0..6863ce79 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsActivity.kt @@ -19,21 +19,25 @@ package com.moez.QKSMS.feature.notificationprefs import android.app.Activity -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.ViewModelProviders import android.content.Intent import android.media.RingtoneManager import android.net.Uri import android.os.Build import android.os.Bundle +import androidx.core.view.isVisible +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelProviders import com.jakewharton.rxbinding2.view.clicks import com.moez.QKSMS.R import com.moez.QKSMS.common.QkDialog +import com.moez.QKSMS.common.androidxcompat.scope import com.moez.QKSMS.common.base.QkThemedActivity import com.moez.QKSMS.common.util.extensions.animateLayoutChanges import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.common.widget.PreferenceView +import com.uber.autodispose.kotlin.autoDisposable import dagger.android.AndroidInjection +import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject import kotlinx.android.synthetic.main.notification_prefs_activity.* @@ -43,11 +47,13 @@ import javax.inject.Inject class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView { @Inject lateinit var previewModeDialog: QkDialog + @Inject lateinit var actionsDialog: QkDialog @Inject lateinit var viewModelFactory: ViewModelProvider.Factory override val preferenceClickIntent: Subject = PublishSubject.create() override val previewModeSelectedIntent by lazy { previewModeDialog.adapter.menuItemClicks } override val ringtoneSelectedIntent: Subject = PublishSubject.create() + override val actionsSelectedIntent by lazy { actionsDialog.adapter.menuItemClicks } private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[NotificationPrefsViewModel::class.java] } @@ -70,30 +76,43 @@ class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView { previewModeDialog.setTitle(R.string.settings_notification_previews_title) previewModeDialog.adapter.setData(R.array.notification_preview_options) + actionsDialog.adapter.setData(R.array.notification_actions) // Listen to clicks for all of the preferences (0 until preferences.childCount) .map { index -> preferences.getChildAt(index) } - .filter { view -> view is PreferenceView } - .forEach { preference -> - preference.clicks().subscribe { - preferenceClickIntent.onNext(preference as PreferenceView) - } - } + .mapNotNull { view -> view as? PreferenceView } + .map { preference -> preference.clicks().map { preference } } + .let { Observable.merge(it) } + .autoDisposable(scope()) + .subscribe(preferenceClickIntent) } override fun render(state: NotificationPrefsState) { - if (state.conversationTitle.isNotEmpty()) { + if (state.threadId != 0L) { title = state.conversationTitle } notifications.checkbox.isChecked = state.notificationsEnabled - notificationPreviews.summary = state.previewSummary + previews.summary = state.previewSummary previewModeDialog.adapter.selectedItem = state.previewId vibration.checkbox.isChecked = state.vibrationEnabled ringtone.summary = state.ringtoneName + + actionsDivider.isVisible = state.threadId == 0L + actionsTitle.isVisible = state.threadId == 0L + action1.isVisible = state.threadId == 0L + action1.summary = state.action1Summary + action2.isVisible = state.threadId == 0L + action2.summary = state.action2Summary + action3.isVisible = state.threadId == 0L + action3.summary = state.action3Summary + + qkreplyDivider.isVisible = state.threadId == 0L + qkreplyTitle.isVisible = state.threadId == 0L qkreply.checkbox.isChecked = state.qkReplyEnabled - qkreplyTapDismiss.setVisible(state.qkReplyEnabled) + qkreply.isVisible = state.threadId == 0L + qkreplyTapDismiss.setVisible(state.threadId == 0L && state.qkReplyEnabled) qkreplyTapDismiss.checkbox.isChecked = state.qkReplyTapDismiss } @@ -108,6 +127,11 @@ class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView { startActivityForResult(intent, 123) } + override fun showActionDialog(selected: Int) { + actionsDialog.adapter.selectedItem = selected + actionsDialog.show(this) + } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == 123 && resultCode == Activity.RESULT_OK) { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsActivityModule.kt b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsActivityModule.kt index 0762c17d..8ebbf376 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsActivityModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsActivityModule.kt @@ -1,17 +1,18 @@ package com.moez.QKSMS.feature.notificationprefs import androidx.lifecycle.ViewModel -import android.content.Intent import com.moez.QKSMS.injection.ViewModelKey import dagger.Module import dagger.Provides import dagger.multibindings.IntoMap +import javax.inject.Named @Module class NotificationPrefsActivityModule { @Provides - fun provideIntent(activity: NotificationPrefsActivity): Intent = activity.intent + @Named("threadId") + fun provideThreadId(activity: NotificationPrefsActivity): Long = activity.intent.extras?.getLong("threadId") ?: 0L @Provides @IntoMap diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsState.kt index 9235ffea..0f3367f8 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsState.kt @@ -22,10 +22,14 @@ import android.os.Build import com.moez.QKSMS.util.Preferences data class NotificationPrefsState( + val threadId: Long = 0, val conversationTitle: String = "", val notificationsEnabled: Boolean = true, val previewSummary: String = "", val previewId: Int = Preferences.NOTIFICATION_PREVIEWS_ALL, + val action1Summary: String = "", + val action2Summary: String = "", + val action3Summary: String = "", val vibrationEnabled: Boolean = true, val ringtoneName: String = "", val qkReplyEnabled: Boolean = Build.VERSION.SDK_INT < Build.VERSION_CODES.N, diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsView.kt index d2693afa..7857b803 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsView.kt @@ -29,7 +29,9 @@ interface NotificationPrefsView : QkView { val preferenceClickIntent: Subject val previewModeSelectedIntent: Subject val ringtoneSelectedIntent: Observable + val actionsSelectedIntent: Subject fun showPreviewModeDialog() fun showRingtonePicker(default: Uri?) + fun showActionDialog(selected: Int) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsViewModel.kt index 7c5c69ed..52ed3fd0 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsViewModel.kt @@ -19,10 +19,8 @@ package com.moez.QKSMS.feature.notificationprefs import android.content.Context -import android.content.Intent import android.media.RingtoneManager import android.net.Uri -import com.f2prateek.rx.preferences2.Preference import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.androidxcompat.scope @@ -33,30 +31,25 @@ import com.moez.QKSMS.util.Preferences import com.uber.autodispose.kotlin.autoDisposable import io.reactivex.Flowable import io.reactivex.rxkotlin.plusAssign +import io.reactivex.rxkotlin.withLatestFrom import io.reactivex.schedulers.Schedulers import javax.inject.Inject +import javax.inject.Named class NotificationPrefsViewModel @Inject constructor( - private val intent: Intent, + @Named("threadId") private val threadId: Long, private val context: Context, private val conversationRepo: ConversationRepository, private val navigator: Navigator, private val prefs: Preferences -) : QkViewModel(NotificationPrefsState()) { +) : QkViewModel(NotificationPrefsState(threadId = threadId)) { - private val threadId = intent.extras?.getLong("threadId") ?: 0L - - private val notifications: Preference - private val previews: Preference - private val vibration: Preference - private val ringtone: Preference + private val notifications = prefs.notifications(threadId) + private val previews = prefs.notificationPreviews(threadId) + private val vibration = prefs.vibration(threadId) + private val ringtone = prefs.ringtone(threadId) init { - notifications = prefs.notifications(threadId) - previews = prefs.notificationPreviews(threadId) - vibration = prefs.vibration(threadId) - ringtone = prefs.ringtone(threadId) - disposables += Flowable.just(threadId) .mapNotNull { threadId -> conversationRepo.getConversation(threadId) } .map { conversation -> conversation.getTitle() } @@ -72,6 +65,16 @@ class NotificationPrefsViewModel @Inject constructor( newState { copy(previewSummary = previewLabels[previewId], previewId = previewId) } } + val actionLabels = context.resources.getStringArray(R.array.notification_actions) + disposables += prefs.notifAction1.asObservable() + .subscribe { previewId -> newState { copy(action1Summary = actionLabels[previewId]) } } + + disposables += prefs.notifAction2.asObservable() + .subscribe { previewId -> newState { copy(action2Summary = actionLabels[previewId]) } } + + disposables += prefs.notifAction3.asObservable() + .subscribe { previewId -> newState { copy(action3Summary = actionLabels[previewId]) } } + disposables += vibration.asObservable() .subscribe { enabled -> newState { copy(vibrationEnabled = enabled) } } @@ -102,12 +105,18 @@ class NotificationPrefsViewModel @Inject constructor( R.id.notifications -> notifications.set(!notifications.get()) - R.id.notificationPreviews -> view.showPreviewModeDialog() + R.id.previews -> view.showPreviewModeDialog() R.id.vibration -> vibration.set(!vibration.get()) R.id.ringtone -> view.showRingtonePicker(ringtone.get().takeIf { it.isNotEmpty() }?.let(Uri::parse)) + R.id.action1 -> view.showActionDialog(prefs.notifAction1.get()) + + R.id.action2 -> view.showActionDialog(prefs.notifAction2.get()) + + R.id.action3 -> view.showActionDialog(prefs.notifAction3.get()) + R.id.qkreply -> prefs.qkreply.set(!prefs.qkreply.get()) R.id.qkreplyTapDismiss -> prefs.qkreplyTapDismiss.set(!prefs.qkreplyTapDismiss.get()) @@ -121,5 +130,16 @@ class NotificationPrefsViewModel @Inject constructor( view.ringtoneSelectedIntent .autoDisposable(view.scope()) .subscribe { ringtone -> this.ringtone.set(ringtone) } + + view.actionsSelectedIntent + .withLatestFrom(view.preferenceClickIntent) { action, preference -> + when (preference.id) { + R.id.action1 -> prefs.notifAction1.set(action) + R.id.action2 -> prefs.notifAction2.set(action) + R.id.action3 -> prefs.notifAction3.set(action) + } + } + .autoDisposable(view.scope()) + .subscribe() } } \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_call_black_24dp.xml b/presentation/src/main/res/drawable/ic_call_white_24dp.xml similarity index 97% rename from presentation/src/main/res/drawable/ic_call_black_24dp.xml rename to presentation/src/main/res/drawable/ic_call_white_24dp.xml index ae670d7c..aa7bbb8e 100644 --- a/presentation/src/main/res/drawable/ic_call_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_call_white_24dp.xml @@ -22,6 +22,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/presentation/src/main/res/drawable/ic_delete_black_24dp.xml b/presentation/src/main/res/drawable/ic_delete_white_24dp.xml similarity index 96% rename from presentation/src/main/res/drawable/ic_delete_black_24dp.xml rename to presentation/src/main/res/drawable/ic_delete_white_24dp.xml index e35e2b36..1ffbab9f 100644 --- a/presentation/src/main/res/drawable/ic_delete_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_delete_white_24dp.xml @@ -22,6 +22,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/presentation/src/main/res/layout/conversation_info_activity.xml b/presentation/src/main/res/layout/conversation_info_activity.xml index a85d3870..52ae2189 100644 --- a/presentation/src/main/res/layout/conversation_info_activity.xml +++ b/presentation/src/main/res/layout/conversation_info_activity.xml @@ -101,7 +101,7 @@ android:id="@+id/delete" android:layout_width="match_parent" android:layout_height="wrap_content" - app:icon="@drawable/ic_delete_black_24dp" + app:icon="@drawable/ic_delete_white_24dp" app:title="@string/info_delete" /> @@ -61,12 +60,11 @@ android:id="@+id/notifications" android:layout_width="match_parent" android:layout_height="wrap_content" - app:icon="@drawable/ic_notifications_black_24dp" app:title="@string/settings_notifications_title" app:widget="@layout/settings_switch_widget" /> + + + + + + + + + + @@ -100,7 +134,6 @@ android:id="@+id/qkreply" android:layout_width="match_parent" android:layout_height="wrap_content" - app:icon="@drawable/ic_reply_white_24dp" app:summary="@string/settings_qkreply_summary" app:title="@string/settings_qkreply_title" app:widget="@layout/settings_switch_widget" /> diff --git a/presentation/src/main/res/menu/compose.xml b/presentation/src/main/res/menu/compose.xml index c217aa77..eeeffebb 100644 --- a/presentation/src/main/res/menu/compose.xml +++ b/presentation/src/main/res/menu/compose.xml @@ -21,7 +21,7 @@ @@ -42,7 +42,7 @@ diff --git a/presentation/src/main/res/menu/main.xml b/presentation/src/main/res/menu/main.xml index f2059761..bf6435f0 100644 --- a/presentation/src/main/res/menu/main.xml +++ b/presentation/src/main/res/menu/main.xml @@ -35,7 +35,7 @@ diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index ea064c02..ffa7ffe3 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -163,6 +163,10 @@ Use system font Automatic emoji Notifications + Actions + Button 1 + Button 2 + Button 3 Notification previews Vibration Sound @@ -246,8 +250,12 @@ HEX Apply - Reply - Mark read + + None + Mark read + Reply + Call + Cancel Delete