From 0e52b290a09015be5ea89f8439bb9b95b9f0e712 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Mon, 30 Apr 2018 02:57:04 -0400 Subject: [PATCH] #979 - Delayed sending --- .../main/res/drawable/ic_stop_black_24dp.xml | 23 ++++++++ .../java/repository/MessageRepositoryImpl.kt | 59 +++++++++++++++---- data/src/main/java/util/Preferences.kt | 6 ++ .../java/interactor/CancelDelayedMessage.kt | 33 +++++++++++ .../main/java/repository/MessageRepository.kt | 7 ++- presentation/src/main/AndroidManifest.xml | 3 + .../common/util/extensions/ViewExtensions.kt | 5 ++ .../java/feature/compose/ComposeActivity.kt | 1 + .../main/java/feature/compose/ComposeView.kt | 1 + .../java/feature/compose/ComposeViewModel.kt | 9 +++ .../java/feature/compose/MessagesAdapter.kt | 42 ++++++++++++- .../java/feature/settings/SettingsActivity.kt | 17 ++++-- .../java/feature/settings/SettingsState.kt | 2 + .../java/feature/settings/SettingsView.kt | 6 +- .../feature/settings/SettingsViewModel.kt | 21 ++++++- .../src/main/java/injection/AppComponent.kt | 2 + .../src/main/java/receiver/SendSmsReceiver.kt | 44 ++++++++++++++ .../res/drawable/circular_progress_bar.xml | 39 ++++++++++++ .../main/res/layout/message_list_item_out.xml | 29 +++++++++ .../src/main/res/layout/settings_activity.xml | 8 +++ presentation/src/main/res/values/strings.xml | 31 ++++++---- 21 files changed, 356 insertions(+), 32 deletions(-) create mode 100644 android-smsmms/src/main/res/drawable/ic_stop_black_24dp.xml create mode 100644 domain/src/main/java/interactor/CancelDelayedMessage.kt create mode 100644 presentation/src/main/java/receiver/SendSmsReceiver.kt create mode 100644 presentation/src/main/res/drawable/circular_progress_bar.xml diff --git a/android-smsmms/src/main/res/drawable/ic_stop_black_24dp.xml b/android-smsmms/src/main/res/drawable/ic_stop_black_24dp.xml new file mode 100644 index 00000000..e2c4aa31 --- /dev/null +++ b/android-smsmms/src/main/res/drawable/ic_stop_black_24dp.xml @@ -0,0 +1,23 @@ + + + + diff --git a/data/src/main/java/repository/MessageRepositoryImpl.kt b/data/src/main/java/repository/MessageRepositoryImpl.kt index 105193b5..32d77ac6 100644 --- a/data/src/main/java/repository/MessageRepositoryImpl.kt +++ b/data/src/main/java/repository/MessageRepositoryImpl.kt @@ -18,12 +18,14 @@ */ package repository +import android.app.AlarmManager import android.app.PendingIntent import android.content.ContentUris import android.content.ContentValues import android.content.Context import android.content.Intent import android.net.Uri +import android.os.Build import android.provider.BaseColumns import android.provider.Telephony import android.telephony.PhoneNumberUtils @@ -355,8 +357,29 @@ class MessageRepositoryImpl @Inject constructor( } override fun sendSmsAndPersist(threadId: Long, address: String, body: String) { - val message = insertSentSms(threadId, address, body) - sendSms(message) + if (prefs.sendDelay.get() != Preferences.SEND_DELAY_NONE) { + val delay = when (prefs.sendDelay.get()) { + Preferences.SEND_DELAY_SHORT -> 3000 + Preferences.SEND_DELAY_MEDIUM -> 5000 + Preferences.SEND_DELAY_LONG -> 10000 + else -> 0 + } + + val sendTime = System.currentTimeMillis() + delay + val message = insertSentSms(threadId, address, body, sendTime) + + val intent = getIntentForDelayedSms(message.id) + + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, sendTime, intent) + } else { + alarmManager.setExact(AlarmManager.RTC_WAKEUP, sendTime, intent) + } + } else { + val message = insertSentSms(threadId, address, body, System.currentTimeMillis()) + sendSms(message) + } } override fun sendSms(message: Message) { @@ -383,14 +406,26 @@ class MessageRepositoryImpl @Inject constructor( smsManager.sendMultipartTextMessage(message.address, null, parts, ArrayList(sentIntents), ArrayList(deliveredIntents)) } - override fun insertSentSms(threadId: Long, address: String, body: String): Message { + override fun cancelDelayedSms(id: Long) { + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + alarmManager.cancel(getIntentForDelayedSms(id)) + } + + private fun getIntentForDelayedSms(id: Long): PendingIntent { + val action = "com.moez.QKSMS.SEND_SMS" + val intent = Intent(action).putExtra("id", id) + BroadcastUtils.addClassName(context, intent, action) + return PendingIntent.getBroadcast(context, id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT) + } + + override fun insertSentSms(threadId: Long, address: String, body: String, date: Long): Message { // Insert the message to Realm val message = Message().apply { this.threadId = threadId this.address = address this.body = body - this.date = System.currentTimeMillis() + this.date = date id = messageIds.newId() boxId = Telephony.Sms.MESSAGE_TYPE_OUTBOX @@ -415,7 +450,8 @@ class MessageRepositoryImpl @Inject constructor( val uri = context.contentResolver.insert(Telephony.Sms.CONTENT_URI, values) // Update the contentId after the message has been inserted to the content provider - realm.executeTransaction { managedMessage?.contentId = uri.lastPathSegment.toLong() } + // The message might have been deleted by now, so only proceed if it's valid + realm.executeTransaction { managedMessage?.takeIf { it.isValid }?.contentId = uri.lastPathSegment.toLong() } realm.close() return message @@ -565,13 +601,14 @@ class MessageRepositoryImpl @Inject constructor( } override fun deleteMessage(messageId: Long) { - val realm = Realm.getDefaultInstance() - realm.where(Message::class.java).equalTo("id", messageId).findFirst()?.let { message -> - val uri = message.getUri() - realm.executeTransaction { message.deleteFromRealm() } - context.contentResolver.delete(uri, null, null) + Realm.getDefaultInstance().use { realm -> + realm.refresh() + realm.where(Message::class.java).equalTo("id", messageId).findFirst()?.let { message -> + val uri = message.getUri() + realm.executeTransaction { message.deleteFromRealm() } + context.contentResolver.delete(uri, null, null) + } } - realm.close() } override fun deleteConversation(threadId: Long) { diff --git a/data/src/main/java/util/Preferences.kt b/data/src/main/java/util/Preferences.kt index 5452b6bc..0e8dd443 100644 --- a/data/src/main/java/util/Preferences.kt +++ b/data/src/main/java/util/Preferences.kt @@ -41,6 +41,11 @@ class Preferences @Inject constructor(private val rxPrefs: RxSharedPreferences) const val NOTIFICATION_PREVIEWS_ALL = 0 const val NOTIFICATION_PREVIEWS_NAME = 1 const val NOTIFICATION_PREVIEWS_NONE = 2 + + const val SEND_DELAY_NONE = 0 + const val SEND_DELAY_SHORT = 1 + const val SEND_DELAY_MEDIUM = 2 + const val SEND_DELAY_LONG = 3 } val defaultSms = rxPrefs.getBoolean("defaultSms", false) @@ -50,6 +55,7 @@ class Preferences @Inject constructor(private val rxPrefs: RxSharedPreferences) val nightEnd = rxPrefs.getString("nightEnd", "6:00 AM") val black = rxPrefs.getBoolean("black", false) val sia = rxPrefs.getBoolean("sia", false) + val sendDelay = rxPrefs.getInteger("sendDelay", SEND_DELAY_NONE) val autoEmoji = rxPrefs.getBoolean("autoEmoji", true) val delivery = rxPrefs.getBoolean("delivery", false) val systemFont = rxPrefs.getBoolean("systemFont", false) diff --git a/domain/src/main/java/interactor/CancelDelayedMessage.kt b/domain/src/main/java/interactor/CancelDelayedMessage.kt new file mode 100644 index 00000000..2021ff26 --- /dev/null +++ b/domain/src/main/java/interactor/CancelDelayedMessage.kt @@ -0,0 +1,33 @@ +/* + * 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 interactor + +import io.reactivex.Flowable +import repository.MessageRepository +import javax.inject.Inject + +class CancelDelayedMessage @Inject constructor(private val messageRepo: MessageRepository) : Interactor() { + + override fun buildObservable(params: Long): Flowable<*> { + return Flowable.just(params) + .doOnNext { id -> messageRepo.cancelDelayedSms(id) } + .doOnNext { id -> messageRepo.deleteMessage(id) } + } + +} \ No newline at end of file diff --git a/domain/src/main/java/repository/MessageRepository.kt b/domain/src/main/java/repository/MessageRepository.kt index b732b290..4a660f1d 100644 --- a/domain/src/main/java/repository/MessageRepository.kt +++ b/domain/src/main/java/repository/MessageRepository.kt @@ -98,7 +98,12 @@ interface MessageRepository { */ fun sendSms(message: Message) - fun insertSentSms(threadId: Long, address: String, body: String): Message + /** + * Attempts to cancel sending the message with the given id + */ + fun cancelDelayedSms(id: Long) + + fun insertSentSms(threadId: Long, address: String, body: String, date: Long): Message fun insertReceivedSms(address: String, body: String, sentTime: Long): Message diff --git a/presentation/src/main/AndroidManifest.xml b/presentation/src/main/AndroidManifest.xml index 6ed3f0d6..e52e2621 100644 --- a/presentation/src/main/AndroidManifest.xml +++ b/presentation/src/main/AndroidManifest.xml @@ -103,6 +103,9 @@ + diff --git a/presentation/src/main/java/common/util/extensions/ViewExtensions.kt b/presentation/src/main/java/common/util/extensions/ViewExtensions.kt index 34a2e1a5..f7456bf8 100644 --- a/presentation/src/main/java/common/util/extensions/ViewExtensions.kt +++ b/presentation/src/main/java/common/util/extensions/ViewExtensions.kt @@ -27,6 +27,7 @@ import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.EditText import android.widget.ImageView +import android.widget.ProgressBar fun EditText.showKeyboard() { requestFocus() @@ -38,6 +39,10 @@ fun ImageView.setTint(color: Int) { imageTintList = ColorStateList.valueOf(color) } +fun ProgressBar.setTint(color: Int) { + progressTintList = ColorStateList.valueOf(color) +} + fun View.setBackgroundTint(color: Int) { // API 21 doesn't support this diff --git a/presentation/src/main/java/feature/compose/ComposeActivity.kt b/presentation/src/main/java/feature/compose/ComposeActivity.kt index f1194a2d..2cf52f9e 100644 --- a/presentation/src/main/java/feature/compose/ComposeActivity.kt +++ b/presentation/src/main/java/feature/compose/ComposeActivity.kt @@ -63,6 +63,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override val infoIntent: Subject = PublishSubject.create() override val messageClickIntent: Subject by lazy { messageAdapter.clicks } override val messageLongClickIntent: Subject by lazy { messageAdapter.longClicks } + override val cancelSendingIntent: Subject by lazy { messageAdapter.cancelSending } override val menuItemIntent: Subject by lazy { dialog.adapter.menuItemClicks } override val attachmentDeletedIntent: Subject by lazy { attachmentAdapter.attachmentDeleted } override val textChangedIntent by lazy { message.textChanges() } diff --git a/presentation/src/main/java/feature/compose/ComposeView.kt b/presentation/src/main/java/feature/compose/ComposeView.kt index a974d587..02b6b18d 100644 --- a/presentation/src/main/java/feature/compose/ComposeView.kt +++ b/presentation/src/main/java/feature/compose/ComposeView.kt @@ -40,6 +40,7 @@ interface ComposeView : QkView { val infoIntent: Subject val messageClickIntent: Subject val messageLongClickIntent: Subject + val cancelSendingIntent: Subject val menuItemIntent: Subject val attachmentDeletedIntent: Subject val textChangedIntent: Observable diff --git a/presentation/src/main/java/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/feature/compose/ComposeViewModel.kt index e1628775..b2d77fcc 100644 --- a/presentation/src/main/java/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/feature/compose/ComposeViewModel.kt @@ -37,6 +37,7 @@ import common.util.ClipboardUtils import common.util.extensions.makeToast import common.util.filter.ContactFilter import injection.appComponent +import interactor.CancelDelayedMessage import interactor.ContactSync import interactor.DeleteMessage import interactor.MarkRead @@ -70,6 +71,7 @@ import javax.inject.Inject class ComposeViewModel(intent: Intent) : QkViewModel(ComposeState()) { @Inject lateinit var context: Context + @Inject lateinit var cancelMessage: CancelDelayedMessage @Inject lateinit var contactFilter: ContactFilter @Inject lateinit var contactsRepo: ContactRepository @Inject lateinit var messageRepo: MessageRepository @@ -171,6 +173,7 @@ class ComposeViewModel(intent: Intent) : QkViewModel( } .switchMap { messages -> messages.asObservable() } + disposables += cancelMessage disposables += sendMessage disposables += markRead disposables += conversation.subscribe() @@ -312,6 +315,12 @@ class ComposeViewModel(intent: Intent) : QkViewModel( .autoDisposable(view.scope()) .subscribe { view.showMenu(listOf(menuCopy, menuForward, menuDelete)) } + // Cancel sending a message + view.cancelSendingIntent + .doOnNext { message -> view.setDraft(message.getText()) } + .autoDisposable(view.scope()) + .subscribe { message -> cancelMessage.execute(message.id) } + // Handle long-press menu item click view.menuItemIntent .withLatestFrom(view.messageLongClickIntent, { actionId, message -> diff --git a/presentation/src/main/java/feature/compose/MessagesAdapter.kt b/presentation/src/main/java/feature/compose/MessagesAdapter.kt index 88b89e52..a7cbf60d 100644 --- a/presentation/src/main/java/feature/compose/MessagesAdapter.kt +++ b/presentation/src/main/java/feature/compose/MessagesAdapter.kt @@ -18,6 +18,7 @@ */ package feature.compose +import android.animation.ObjectAnimator import android.content.Context import android.graphics.Typeface import android.os.Build @@ -29,6 +30,8 @@ import android.text.style.StyleSpan import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ImageView +import android.widget.ProgressBar import com.jakewharton.rxbinding2.view.RxView import com.jakewharton.rxbinding2.view.clicks import com.moez.QKSMS.R @@ -42,6 +45,7 @@ import common.util.extensions.dpToPx import common.util.extensions.forwardTouches import common.util.extensions.setBackgroundTint import common.util.extensions.setPadding +import common.util.extensions.setTint import common.util.extensions.setVisible import common.widget.BubbleImageView.Style.* import io.reactivex.disposables.CompositeDisposable @@ -54,6 +58,7 @@ import kotlinx.android.synthetic.main.mms_preview_list_item.view.* import model.Conversation import model.Message import model.Recipient +import util.Preferences import util.extensions.hasThumbnails import util.extensions.isImage import util.extensions.isVideo @@ -64,7 +69,8 @@ class MessagesAdapter @Inject constructor( private val context: Context, private val colors: Colors, private val dateFormatter: DateFormatter, - private val navigator: Navigator + private val navigator: Navigator, + private val prefs: Preferences ) : QkRealmAdapter() { companion object { @@ -75,6 +81,7 @@ class MessagesAdapter @Inject constructor( val clicks: Subject = PublishSubject.create() val longClicks: Subject = PublishSubject.create() + val cancelSending: Subject = PublishSubject.create() var data: Pair>? = null set(value) { @@ -119,7 +126,15 @@ class MessagesAdapter @Inject constructor( if (viewType == VIEW_TYPE_MESSAGE_OUT) { view = layoutInflater.inflate(R.layout.message_list_item_out, parent, false) disposables += colors.bubble - .subscribe { color -> view.body.setBackgroundTint(color) } + .subscribe { color -> + view.body.setBackgroundTint(color) + view.findViewById(R.id.cancel).setBackgroundTint(color) + } + disposables += theme + .subscribe { color -> + view.findViewById(R.id.cancelIcon).setTint(color) + view.findViewById(R.id.cancel).setTint(color) + } } else { view = layoutInflater.inflate(R.layout.message_list_item_in, parent, false) view.avatar.threadId = conversation?.id ?: 0 @@ -152,6 +167,29 @@ class MessagesAdapter @Inject constructor( RxView.longClicks(view).subscribe { longClicks.onNext(message) } + // Bind the cancel view + view.findViewById(R.id.cancel)?.let { cancel -> + val isCancellable = message.isSending() && message.date > System.currentTimeMillis() + cancel.setVisible(isCancellable) + cancel.clicks().subscribe { cancelSending.onNext(message) } + cancel.progress = 2 + + if (isCancellable) { + val delay = when (prefs.sendDelay.get()) { + Preferences.SEND_DELAY_SHORT -> 3000 + Preferences.SEND_DELAY_MEDIUM -> 5000 + Preferences.SEND_DELAY_LONG -> 10000 + else -> 0 + } + val progress = (1 - (message.date - System.currentTimeMillis()) / delay.toFloat()) * 100 + + ObjectAnimator.ofInt(cancel, "progress", progress.toInt(), 100) + .setDuration(message.date - System.currentTimeMillis()) + .start() + } + } + + // Bind the message status bindStatus(viewHolder, position) diff --git a/presentation/src/main/java/feature/settings/SettingsActivity.kt b/presentation/src/main/java/feature/settings/SettingsActivity.kt index d9c4109c..6299b1f6 100644 --- a/presentation/src/main/java/feature/settings/SettingsActivity.kt +++ b/presentation/src/main/java/feature/settings/SettingsActivity.kt @@ -46,15 +46,17 @@ class SettingsActivity : QkThemedActivity(), SettingsView { @Inject lateinit var nightModeDialog: QkDialog @Inject lateinit var textSizeDialog: QkDialog + @Inject lateinit var sendDelayDialog: QkDialog @Inject lateinit var mmsSizeDialog: QkDialog override val viewModelClass = SettingsViewModel::class override val preferenceClickIntent: Subject = PublishSubject.create() - override val nightModeSelectedIntent by lazy { nightModeDialog.adapter.menuItemClicks } override val viewQksmsPlusIntent: Subject = PublishSubject.create() + override val nightModeSelectedIntent by lazy { nightModeDialog.adapter.menuItemClicks } override val startTimeSelectedIntent: Subject> = PublishSubject.create() override val endTimeSelectedIntent: Subject> = PublishSubject.create() override val textSizeSelectedIntent by lazy { textSizeDialog.adapter.menuItemClicks } + override val sendDelayChangedIntent by lazy { sendDelayDialog.adapter.menuItemClicks } override val mmsSizeSelectedIntent: Subject by lazy { mmsSizeDialog.adapter.menuItemClicks } // TODO remove this @@ -79,6 +81,7 @@ class SettingsActivity : QkThemedActivity(), SettingsView { nightModeDialog.adapter.setData(R.array.night_modes) textSizeDialog.adapter.setData(R.array.text_sizes) + sendDelayDialog.adapter.setData(R.array.delayed_sending_labels) mmsSizeDialog.adapter.setData(R.array.mms_sizes, R.array.mms_sizes_ids) about.summary = getString(R.string.settings_version, BuildConfig.VERSION_NAME) @@ -119,6 +122,10 @@ class SettingsActivity : QkThemedActivity(), SettingsView { black.checkbox.isChecked = state.black autoEmoji.checkbox.isChecked = state.autoEmojiEnabled + + delayed.summary = state.sendDelaySummary + sendDelayDialog.adapter.selectedItem = state.sendDelayId + delivery.checkbox.isChecked = state.deliveryEnabled textSize.summary = state.textSizeSummary @@ -131,9 +138,6 @@ class SettingsActivity : QkThemedActivity(), SettingsView { mmsSizeDialog.adapter.selectedItem = state.maxMmsSizeId } - // TODO change this to a PopupWindow - override fun showNightModeDialog() = nightModeDialog.show(this) - override fun showQksmsPlusSnackbar() { Snackbar.make(contentView, R.string.toast_qksms_plus, Snackbar.LENGTH_LONG).run { setAction(R.string.button_more, { viewQksmsPlusIntent.onNext(Unit) }) @@ -141,6 +145,9 @@ class SettingsActivity : QkThemedActivity(), SettingsView { } } + // TODO change this to a PopupWindow + override fun showNightModeDialog() = nightModeDialog.show(this) + override fun showStartTimePicker(hour: Int, minute: Int) { TimePickerDialog(this, TimePickerDialog.OnTimeSetListener { _, newHour, newMinute -> startTimeSelectedIntent.onNext(Pair(newHour, newMinute)) @@ -155,6 +162,8 @@ class SettingsActivity : QkThemedActivity(), SettingsView { override fun showTextSizePicker() = textSizeDialog.show(this) + override fun showDelayDurationDialog() = sendDelayDialog.show(this) + override fun showMmsSizePicker() = mmsSizeDialog.show(this) } \ No newline at end of file diff --git a/presentation/src/main/java/feature/settings/SettingsState.kt b/presentation/src/main/java/feature/settings/SettingsState.kt index 41f49a67..997ca888 100644 --- a/presentation/src/main/java/feature/settings/SettingsState.kt +++ b/presentation/src/main/java/feature/settings/SettingsState.kt @@ -31,6 +31,8 @@ data class SettingsState( val black: Boolean = false, val autoEmojiEnabled: Boolean = true, val notificationsEnabled: Boolean = true, + val sendDelaySummary: String = "", + val sendDelayId: Int = 0, val deliveryEnabled: Boolean = false, val textSizeSummary: String = "", val textSizeId: Int = Preferences.TEXT_SIZE_NORMAL, diff --git a/presentation/src/main/java/feature/settings/SettingsView.kt b/presentation/src/main/java/feature/settings/SettingsView.kt index 0253bcfb..85747c00 100644 --- a/presentation/src/main/java/feature/settings/SettingsView.kt +++ b/presentation/src/main/java/feature/settings/SettingsView.kt @@ -26,17 +26,19 @@ import io.reactivex.subjects.Subject interface SettingsView : QkView { val preferenceClickIntent: Subject - val nightModeSelectedIntent: Observable val viewQksmsPlusIntent: Subject + val nightModeSelectedIntent: Observable val startTimeSelectedIntent: Subject> val endTimeSelectedIntent: Subject> val textSizeSelectedIntent: Subject + val sendDelayChangedIntent: Observable val mmsSizeSelectedIntent: Observable - fun showNightModeDialog() fun showQksmsPlusSnackbar() + fun showNightModeDialog() fun showStartTimePicker(hour: Int, minute: Int) fun showEndTimePicker(hour: Int, minute: Int) fun showTextSizePicker() + fun showDelayDurationDialog() fun showMmsSizePicker() } diff --git a/presentation/src/main/java/feature/settings/SettingsViewModel.kt b/presentation/src/main/java/feature/settings/SettingsViewModel.kt index bf957f6b..1a9405fd 100644 --- a/presentation/src/main/java/feature/settings/SettingsViewModel.kt +++ b/presentation/src/main/java/feature/settings/SettingsViewModel.kt @@ -75,11 +75,15 @@ class SettingsViewModel : QkViewModel(SettingsState disposables += prefs.black.asObservable() .subscribe { black -> newState { it.copy(black = black) } } + disposables += prefs.notifications().asObservable() + .subscribe { enabled -> newState { it.copy(notificationsEnabled = enabled) } } + disposables += prefs.autoEmoji.asObservable() .subscribe { enabled -> newState { it.copy(autoEmojiEnabled = enabled) } } - disposables += prefs.notifications().asObservable() - .subscribe { enabled -> newState { it.copy(notificationsEnabled = enabled) } } + val delayedSendingLabels = context.resources.getStringArray(R.array.delayed_sending_labels) + disposables += prefs.sendDelay.asObservable() + .subscribe { id -> newState { it.copy(sendDelaySummary = delayedSendingLabels[id], sendDelayId = id) } } disposables += prefs.delivery.asObservable() .subscribe { enabled -> newState { it.copy(deliveryEnabled = enabled) } } @@ -140,6 +144,8 @@ class SettingsViewModel : QkViewModel(SettingsState R.id.blocked -> navigator.showBlockedConversations() + R.id.delayed -> view.showDelayDurationDialog() + R.id.delivery -> prefs.delivery.set(!prefs.delivery.get()) R.id.textSize -> view.showTextSizePicker() @@ -192,6 +198,17 @@ class SettingsViewModel : QkViewModel(SettingsState .autoDisposable(view.scope()) .subscribe { prefs.textSize.set(it) } + view.sendDelayChangedIntent + .withLatestFrom(billingManager.upgradeStatus, { duration, upgraded -> + if (!upgraded && duration != 0) { + view.showQksmsPlusSnackbar() + } else { + prefs.sendDelay.set(duration) + } + }) + .autoDisposable(view.scope()) + .subscribe() + view.mmsSizeSelectedIntent .autoDisposable(view.scope()) .subscribe { prefs.mmsSize.set(it) } diff --git a/presentation/src/main/java/injection/AppComponent.kt b/presentation/src/main/java/injection/AppComponent.kt index 8178d274..bd201096 100644 --- a/presentation/src/main/java/injection/AppComponent.kt +++ b/presentation/src/main/java/injection/AppComponent.kt @@ -64,6 +64,7 @@ import receiver.MmsSentReceiver import receiver.MmsUpdatedReceiver import receiver.NightModeReceiver import receiver.RemoteMessagingReceiver +import receiver.SendSmsReceiver import receiver.SmsDeliveredReceiver import receiver.SmsProviderChangedReceiver import receiver.SmsReceiver @@ -103,6 +104,7 @@ interface AppComponent { fun inject(receiver: MmsUpdatedReceiver) fun inject(receiver: NightModeReceiver) fun inject(receiver: RemoteMessagingReceiver) + fun inject(receiver: SendSmsReceiver) fun inject(receiver: SmsProviderChangedReceiver) fun inject(receiver: SmsReceiver) fun inject(receiver: WidgetProvider) diff --git a/presentation/src/main/java/receiver/SendSmsReceiver.kt b/presentation/src/main/java/receiver/SendSmsReceiver.kt new file mode 100644 index 00000000..9b9c830e --- /dev/null +++ b/presentation/src/main/java/receiver/SendSmsReceiver.kt @@ -0,0 +1,44 @@ +/* + * 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 receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import injection.appComponent +import interactor.RetrySending +import repository.MessageRepository +import javax.inject.Inject + +class SendSmsReceiver : BroadcastReceiver() { + + @Inject lateinit var messageRepo: MessageRepository + @Inject lateinit var retrySending: RetrySending + + override fun onReceive(context: Context, intent: Intent) { + appComponent.inject(this) + + val id = intent.getLongExtra("id", 0L) + messageRepo.getMessage(id)?.let { message -> + val result = goAsync() + retrySending.execute(message) { result.finish() } + } + } + +} \ No newline at end of file diff --git a/presentation/src/main/res/drawable/circular_progress_bar.xml b/presentation/src/main/res/drawable/circular_progress_bar.xml new file mode 100644 index 00000000..a89458f7 --- /dev/null +++ b/presentation/src/main/res/drawable/circular_progress_bar.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/message_list_item_out.xml b/presentation/src/main/res/layout/message_list_item_out.xml index 93a92bf4..6ba106fa 100644 --- a/presentation/src/main/res/layout/message_list_item_out.xml +++ b/presentation/src/main/res/layout/message_list_item_out.xml @@ -75,6 +75,35 @@ tools:backgroundTint="@color/white" tools:text="Hey! How's it going?" /> + + + + + + Pure black night mode Start time End time + Font size + Use system font Automatic emoji Notifications Notification previews Vibration Sound - Blocking - - Should I Answer? - Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app - Delivery confirmations - Confirm that messages were sent successfully QK Reply Popup for new messages Tap to dismiss Tap outside of the popup to close it - Font size - Use system font + Blocking + + Should I Answer? + Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app + Delayed sending + Delivery confirmations + Confirm that messages were sent successfully Strip accents Remove accents from characters in outgoing SMS messages Max MMS attachment size @@ -131,6 +132,8 @@ About QKSMS Version %s + Enter duration (seconds) + Blocking Your blocked conversations will appear here Unblock @@ -194,9 +197,10 @@ Cancel Delete + More + Set Unblock Undo - More Copied Archived conversation @@ -214,7 +218,7 @@ Always on Automatic - + Show name and message Show name @@ -228,6 +232,13 @@ Larger + + No delay + Short + Medium + Long + + 100KB 200KB