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