From 7624174cad46ffe60adc5db9c0ca5f9ab8591d18 Mon Sep 17 00:00:00 2001 From: Naveen Date: Mon, 26 Sep 2022 03:02:07 +0530 Subject: [PATCH 01/22] Add schedule send message creation ui Move android-smsmms logic into a separate file --- .../smsmessenger/activities/ThreadActivity.kt | 123 ++++++++----- .../dialogs/ScheduleSendDialog.kt | 161 ++++++++++++++++++ .../smsmessenger/extensions/Context.kt | 11 -- .../smsmessenger/extensions/Date.kt | 11 ++ .../smsmessenger/extensions/Math.kt | 8 + .../smsmessenger/helpers/Constants.kt | 2 + .../smsmessenger/helpers/Messaging.kt | 59 +++++++ .../receivers/DirectReplyReceiver.kt | 2 +- .../receivers/ScheduledMessageReceiver.kt | 11 ++ .../services/HeadlessSmsSendService.kt | 3 +- .../res/drawable/ic_calendar_month_vector.xml | 10 ++ .../res/drawable/ic_schedule_send_vector.xml | 11 ++ app/src/main/res/layout/activity_thread.xml | 52 +++++- .../res/layout/schedule_message_dialog.xml | 74 ++++++++ app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/strings.xml | 6 + 16 files changed, 485 insertions(+), 60 deletions(-) create mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt create mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Math.kt create mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt create mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt create mode 100644 app/src/main/res/drawable/ic_calendar_month_vector.xml create mode 100644 app/src/main/res/drawable/ic_schedule_send_vector.xml create mode 100644 app/src/main/res/layout/schedule_message_dialog.xml diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt index 59c66ebd..b9575c04 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -25,6 +25,7 @@ import android.view.inputmethod.EditorInfo import android.widget.LinearLayout import android.widget.LinearLayout.LayoutParams import android.widget.RelativeLayout +import androidx.core.content.res.ResourcesCompat import com.bumptech.glide.Glide import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.DiskCacheStrategy @@ -37,8 +38,6 @@ import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.target.Target import com.google.gson.Gson import com.google.gson.reflect.TypeToken -import com.klinker.android.send_message.Transaction -import com.klinker.android.send_message.Utils.getNumPages import com.simplemobiletools.commons.dialogs.ConfirmationDialog import com.simplemobiletools.commons.dialogs.RadioGroupDialog import com.simplemobiletools.commons.extensions.* @@ -50,17 +49,17 @@ import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.adapters.AutoCompleteTextViewAdapter import com.simplemobiletools.smsmessenger.adapters.ThreadAdapter +import com.simplemobiletools.smsmessenger.dialogs.ScheduleSendDialog import com.simplemobiletools.smsmessenger.extensions.* import com.simplemobiletools.smsmessenger.helpers.* import com.simplemobiletools.smsmessenger.models.* -import com.simplemobiletools.smsmessenger.receivers.SmsStatusDeliveredReceiver -import com.simplemobiletools.smsmessenger.receivers.SmsStatusSentReceiver import kotlinx.android.synthetic.main.activity_thread.* import kotlinx.android.synthetic.main.item_attachment.view.* import kotlinx.android.synthetic.main.item_selected_contact.view.* import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode +import org.joda.time.DateTime import java.io.File import java.io.InputStream import java.io.OutputStream @@ -92,6 +91,9 @@ class ThreadActivity : SimpleActivity() { private var allMessagesFetched = false private var oldestMessageDate = -1 + private var isScheduledMessage: Boolean = false + private lateinit var scheduledDateTime: DateTime + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_thread) @@ -425,6 +427,12 @@ class ThreadActivity : SimpleActivity() { thread_send_message.setOnClickListener { sendMessage() } + thread_send_message.setOnLongClickListener { + if (!isScheduledMessage) { + launchScheduleSendDialog() + } + true + } thread_send_message.isClickable = false thread_type_message.onTextChangeListener { @@ -468,6 +476,8 @@ class ThreadActivity : SimpleActivity() { addAttachment(it) } } + + setupScheduleSendUi() } private fun setupAttachmentSizes() { @@ -909,9 +919,11 @@ class ThreadActivity : SimpleActivity() { private fun checkSendMessageAvailability() { if (thread_type_message.text!!.isNotEmpty() || (attachmentSelections.isNotEmpty() && !attachmentSelections.values.any { it.isPending })) { + thread_send_message.isEnabled = true thread_send_message.isClickable = true thread_send_message.alpha = 0.9f } else { + thread_send_message.isEnabled = false thread_send_message.isClickable = false thread_send_message.alpha = 0.4f } @@ -919,53 +931,25 @@ class ThreadActivity : SimpleActivity() { } private fun sendMessage() { - var msg = thread_type_message.value - if (msg.isEmpty() && attachmentSelections.isEmpty()) { + var text = thread_type_message.value + if (text.isEmpty() && attachmentSelections.isEmpty()) { showErrorToast(getString(R.string.unknown_error_occurred)) return } - msg = removeDiacriticsIfNeeded(msg) + text = removeDiacriticsIfNeeded(text) - val numbers = ArrayList() - participants.forEach { contact -> - contact.phoneNumbers.forEach { - numbers.add(it.normalizedNumber) - } - } + val addresses = participants + .flatMap { it.phoneNumbers } + .map { it.normalizedNumber } - val settings = getSendMessageSettings() val currentSubscriptionId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId - if (currentSubscriptionId != null) { - settings.subscriptionId = currentSubscriptionId - } - - val transaction = Transaction(this, settings) - val message = com.klinker.android.send_message.Message(msg, numbers.toTypedArray()) - - if (attachmentSelections.isNotEmpty()) { - for (selection in attachmentSelections.values) { - try { - val byteArray = contentResolver.openInputStream(selection.uri)?.readBytes() ?: continue - val mimeType = contentResolver.getType(selection.uri) ?: continue - message.addMedia(byteArray, mimeType) - } catch (e: Exception) { - showErrorToast(e) - } catch (e: Error) { - showErrorToast(e.localizedMessage ?: getString(R.string.unknown_error_occurred)) - } - } - } + val attachments = attachmentSelections.values.map { it.uri } try { - val smsSentIntent = Intent(this, SmsStatusSentReceiver::class.java) - val deliveredIntent = Intent(this, SmsStatusDeliveredReceiver::class.java) - - transaction.setExplicitBroadcastForSentSms(smsSentIntent) - transaction.setExplicitBroadcastForDeliveredSms(deliveredIntent) - refreshedSinceSent = false - transaction.sendNewMessage(message) + sendTransactionMessage(text, addresses, currentSubscriptionId, attachments) + thread_type_message.setText("") attachmentSelections.clear() thread_attachments_holder.beGone() @@ -1146,10 +1130,9 @@ class ThreadActivity : SimpleActivity() { } private fun updateMessageType() { - val settings = getSendMessageSettings() val text = thread_type_message.text.toString() val isGroupMms = participants.size > 1 && config.sendGroupMessageMMS - val isLongMmsMessage = getNumPages(settings, text) > settings.sendLongAsMmsAfter && config.sendLongMessageMMS + val isLongMmsMessage = isLongMmsMessage(text) && config.sendLongMessageMMS val stringId = if (attachmentSelections.isNotEmpty() || isGroupMms || isLongMmsMessage) { R.string.mms } else { @@ -1166,4 +1149,58 @@ class ThreadActivity : SimpleActivity() { } return File.createTempFile("IMG_", ".jpg", outputDirectory) } + + private fun launchScheduleSendDialog(originalDt: DateTime? = null) { + ScheduleSendDialog(this, originalDt) { newDt -> + if (newDt != null) { + scheduledDateTime = newDt + showScheduleSendUi() + } + } + } + + private fun setupScheduleSendUi() { + val textColor = getProperTextColor() + scheduled_message_holder.background.applyColorFilter(getProperBackgroundColor().getContrastColor()) + scheduled_message_button.apply { + val clockDrawable = ResourcesCompat.getDrawable(resources, R.drawable.ic_clock_vector, theme)?.apply { applyColorFilter(textColor) } + setCompoundDrawablesWithIntrinsicBounds(clockDrawable, null, null, null) + setTextColor(textColor) + setOnClickListener { + launchScheduleSendDialog(scheduledDateTime) + } + } + + discard_scheduled_message.apply { + applyColorFilter(textColor) + setOnClickListener { + hideScheduleSendUi() + } + } + } + + private fun showScheduleSendUi() { + isScheduledMessage = true + updateSendButton() + scheduled_message_holder.beVisible() + scheduled_message_button.text = scheduledDateTime.humanize(this) + } + + private fun hideScheduleSendUi() { + isScheduledMessage = false + scheduled_message_holder.beGone() + updateSendButton() + } + + private fun updateSendButton() { + val drawableResId = if (isScheduledMessage) { + R.drawable.ic_schedule_send_vector + } else { + R.drawable.ic_send_vector + } + ResourcesCompat.getDrawable(resources, drawableResId, theme)?.apply { + applyColorFilter(getProperTextColor()) + thread_send_message.setCompoundDrawablesWithIntrinsicBounds(null, this, null, null) + } + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt new file mode 100644 index 00000000..6f0b0808 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt @@ -0,0 +1,161 @@ +package com.simplemobiletools.smsmessenger.dialogs + +import android.app.DatePickerDialog +import android.app.DatePickerDialog.OnDateSetListener +import android.app.TimePickerDialog +import android.app.TimePickerDialog.OnTimeSetListener +import android.text.format.DateFormat +import android.text.style.RelativeSizeSpan +import androidx.appcompat.app.AlertDialog +import androidx.core.text.toSpannable +import com.simplemobiletools.commons.activities.BaseSimpleActivity +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.TIME_FORMAT_12 +import com.simplemobiletools.smsmessenger.R +import com.simplemobiletools.smsmessenger.extensions.round +import com.simplemobiletools.smsmessenger.helpers.DATE_FORMAT_PATTERN +import kotlinx.android.synthetic.main.schedule_message_dialog.view.* +import org.joda.time.DateTime +import java.util.* + +class ScheduleSendDialog(private val activity: BaseSimpleActivity, private var dateTime: DateTime? = null, private val callback: (dt: DateTime?) -> Unit) { + private val view = activity.layoutInflater.inflate(R.layout.schedule_message_dialog, null) + private val textColor = activity.getProperTextColor() + + private var previewDialog: AlertDialog? = null + private var previewShown = false + private var isNewMessage = dateTime == null + + private val calendar = Calendar.getInstance() + + init { + arrayOf(view.subtitle, view.edit_time, view.edit_date).forEach { it.setTextColor(textColor) } + arrayOf(view.dateIcon, view.timeIcon).forEach { it.applyColorFilter(textColor) } + view.edit_date.setOnClickListener { showDatePicker() } + view.edit_time.setOnClickListener { showTimePicker() } + updateTexts(dateTime ?: DateTime.now().plusHours(1)) + + if (isNewMessage) { + showDatePicker() + } else { + showPreview() + } + } + + private fun updateTexts(dt: DateTime) { + val dateText = dt.toString(DATE_FORMAT_PATTERN).toSpannable() + dateText.setSpan(RelativeSizeSpan(0.6f), 2, dateText.length, 0) + val timeText = dt.toString(TIME_FORMAT_12).toSpannable() + timeText.setSpan(RelativeSizeSpan(0.6f), timeText.lastIndex - 2, timeText.length, 0) + + view.edit_date.text = dateText + view.edit_time.text = timeText + } + + private fun showPreview() { + if (previewShown) return + activity.getAlertDialogBuilder() + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.cancel, null) + .apply { + previewShown = true + activity.setupDialogStuff(view, this, R.string.schedule_send) { dialog -> + previewDialog = dialog + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + if (validateDateTime()) { + callback(dateTime) + dialog.dismiss() + } + } + dialog.setOnDismissListener { + previewShown = false + previewDialog = null + } + } + } + } + + private fun showDatePicker() { + val year = dateTime?.year ?: calendar.get(Calendar.YEAR) + val monthOfYear = dateTime?.monthOfYear?.minus(1) ?: calendar.get(Calendar.MONTH) + val dayOfMonth = dateTime?.dayOfMonth ?: calendar.get(Calendar.DAY_OF_MONTH) + + val dateSetListener = OnDateSetListener { _, y, m, d -> dateSet(y, m, d) } + DatePickerDialog( + activity, activity.getDatePickerDialogTheme(), dateSetListener, year, monthOfYear, dayOfMonth + ).apply { + datePicker.minDate = System.currentTimeMillis() + show() + getButton(AlertDialog.BUTTON_NEGATIVE).apply { + text = activity.getString(R.string.back) + setOnClickListener { + showPreview() + dismiss() + } + } + } + } + + private fun showTimePicker() { + val hourOfDay = dateTime?.hourOfDay ?: getNextHour() + val minute = dateTime?.minuteOfHour ?: getNextMinute() + + val timeSetListener = OnTimeSetListener { _, h, m -> timeSet(h, m) } + TimePickerDialog( + activity, activity.getDatePickerDialogTheme(), timeSetListener, hourOfDay, minute, DateFormat.is24HourFormat(activity) + ).apply { + show() + getButton(AlertDialog.BUTTON_NEGATIVE).apply { + text = activity.getString(R.string.back) + setOnClickListener { + showPreview() + dismiss() + } + } + } + } + + private fun dateSet(year: Int, monthOfYear: Int, dayOfMonth: Int) { + if (isNewMessage) { + showTimePicker() + } + + dateTime = DateTime.now() + .withDate(year, monthOfYear + 1, dayOfMonth) + .run { + if (dateTime != null) { + withTime(dateTime!!.hourOfDay, dateTime!!.minuteOfHour, 0, 0) + } else { + withTime(getNextHour(), getNextMinute(), 0, 0) + } + } + if (!isNewMessage) { + validateDateTime() + } + isNewMessage = false + updateTexts(dateTime!!) + } + + private fun timeSet(hourOfDay: Int, minute: Int) { + dateTime = dateTime?.withHourOfDay(hourOfDay)?.withMinuteOfHour(minute) + if (validateDateTime()) { + updateTexts(dateTime!!) + showPreview() + } else { + showTimePicker() + } + } + + private fun validateDateTime(): Boolean { + return if (dateTime?.isAfterNow == false) { + activity.toast(R.string.must_pick_time_in_the_future) + false + } else { + true + } + } + + private fun getNextHour() = calendar.get(Calendar.HOUR_OF_DAY) + 1 + + private fun getNextMinute() = (calendar.get(Calendar.MINUTE) + 5).round(5).coerceIn(0, 59) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt index ddde568f..4dd05681 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt @@ -26,7 +26,6 @@ import android.telephony.SubscriptionManager import android.text.TextUtils import androidx.core.app.NotificationCompat import androidx.core.app.RemoteInput -import com.klinker.android.send_message.Settings import com.klinker.android.send_message.Transaction.getAddressSeparator import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.* @@ -974,16 +973,6 @@ fun Context.getFileSizeFromUri(uri: Uri): Long { } } -fun Context.getSendMessageSettings(): Settings { - val settings = Settings() - settings.useSystemSending = true - settings.deliveryReports = config.enableDeliveryReports - settings.sendLongAsMms = config.sendLongMessageMMS - settings.sendLongAsMmsAfter = 1 - settings.group = config.sendGroupMessageMMS - return settings -} - // fix a glitch at enabling Release version minifying from 5.12.3 // reset messages in 5.14.3 again, as PhoneNumber is no longer minified fun Context.clearAllMessagesIfNeeded() { diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Date.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Date.kt index 7cde1a5b..49182867 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Date.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Date.kt @@ -1,8 +1,19 @@ package com.simplemobiletools.smsmessenger.extensions +import android.content.Context import android.text.format.DateFormat +import android.text.format.DateUtils +import org.joda.time.DateTime import java.util.* fun Date.format(pattern: String): String { return DateFormat.format(pattern, this).toString() } + +fun DateTime.humanize(context: Context, now: DateTime = DateTime.now(), pattern: String = "EEE, MMM dd, YYYY, hh:mm a"): String { + return if (yearOfCentury().get() > now.yearOfCentury().get()) { + toString(pattern) + } else { + DateUtils.getRelativeDateTimeString(context, millis, DateUtils.MINUTE_IN_MILLIS, DateUtils.DAY_IN_MILLIS, 0).toString() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Math.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Math.kt new file mode 100644 index 00000000..e3e61e2e --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Math.kt @@ -0,0 +1,8 @@ +package com.simplemobiletools.smsmessenger.extensions + +import kotlin.math.roundToInt + +/** + * Returns the closest next number divisible by [multipleOf]. + */ +fun Int.round(multipleOf: Int = 1) = (toDouble() / multipleOf).roundToInt() * multipleOf diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt index 973e56bc..f3711dec 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt @@ -34,6 +34,8 @@ private const val PATH = "com.simplemobiletools.smsmessenger.action." const val MARK_AS_READ = PATH + "mark_as_read" const val REPLY = PATH + "reply" +const val DATE_FORMAT_PATTERN = "dd MMM, YYYY" + // view types for the thread list view const val THREAD_DATE_TIME = 1 const val THREAD_RECEIVED_MESSAGE = 2 diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt new file mode 100644 index 00000000..2f0ed4c7 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt @@ -0,0 +1,59 @@ +package com.simplemobiletools.smsmessenger.helpers + +import android.content.Context +import android.content.Intent +import android.net.Uri +import com.klinker.android.send_message.Settings +import com.klinker.android.send_message.Transaction +import com.klinker.android.send_message.Utils +import com.simplemobiletools.commons.extensions.showErrorToast +import com.simplemobiletools.smsmessenger.R +import com.simplemobiletools.smsmessenger.extensions.config +import com.simplemobiletools.smsmessenger.receivers.SmsStatusDeliveredReceiver +import com.simplemobiletools.smsmessenger.receivers.SmsStatusSentReceiver + +fun Context.getSendMessageSettings(): Settings { + val settings = Settings() + settings.useSystemSending = true + settings.deliveryReports = config.enableDeliveryReports + settings.sendLongAsMms = config.sendLongMessageMMS + settings.sendLongAsMmsAfter = 1 + settings.group = config.sendGroupMessageMMS + return settings +} + +fun Context.sendTransactionMessage(text: String, addresses: List, subscriptionId: Int?, attachments: List) { + val settings = getSendMessageSettings() + if (subscriptionId != null) { + settings.subscriptionId = subscriptionId + } + + val transaction = Transaction(this, settings) + val message = com.klinker.android.send_message.Message(text, addresses.toTypedArray()) + + if (attachments.isNotEmpty()) { + for (uri in attachments) { + try { + val byteArray = contentResolver.openInputStream(uri)?.readBytes() ?: continue + val mimeType = contentResolver.getType(uri) ?: continue + message.addMedia(byteArray, mimeType) + } catch (e: Exception) { + showErrorToast(e) + } catch (e: Error) { + showErrorToast(e.localizedMessage ?: getString(R.string.unknown_error_occurred)) + } + } + } + + val smsSentIntent = Intent(this, SmsStatusSentReceiver::class.java) + val deliveredIntent = Intent(this, SmsStatusDeliveredReceiver::class.java) + + transaction.setExplicitBroadcastForSentSms(smsSentIntent) + transaction.setExplicitBroadcastForDeliveredSms(deliveredIntent) + transaction.sendNewMessage(message) +} + +fun Context.isLongMmsMessage(text: String): Boolean { + val settings = getSendMessageSettings() + return Utils.getNumPages(settings, text) > settings.sendLongAsMmsAfter +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/DirectReplyReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/DirectReplyReceiver.kt index caac5115..81114a0f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/DirectReplyReceiver.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/DirectReplyReceiver.kt @@ -4,7 +4,6 @@ import android.annotation.SuppressLint import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.telephony.SubscriptionManager import androidx.core.app.RemoteInput import com.klinker.android.send_message.Transaction import com.simplemobiletools.commons.extensions.notificationManager @@ -14,6 +13,7 @@ import com.simplemobiletools.smsmessenger.extensions.* import com.simplemobiletools.smsmessenger.helpers.REPLY import com.simplemobiletools.smsmessenger.helpers.THREAD_ID import com.simplemobiletools.smsmessenger.helpers.THREAD_NUMBER +import com.simplemobiletools.smsmessenger.helpers.getSendMessageSettings class DirectReplyReceiver : BroadcastReceiver() { @SuppressLint("MissingPermission") diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt new file mode 100644 index 00000000..72aa53d1 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt @@ -0,0 +1,11 @@ +package com.simplemobiletools.smsmessenger.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent + +class ScheduledMessageReceiver: BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + TODO("Not yet implemented") + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/services/HeadlessSmsSendService.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/services/HeadlessSmsSendService.kt index 1ebf9d7a..bdea1dbe 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/services/HeadlessSmsSendService.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/services/HeadlessSmsSendService.kt @@ -4,8 +4,7 @@ import android.app.Service import android.content.Intent import android.net.Uri import com.klinker.android.send_message.Transaction -import com.simplemobiletools.smsmessenger.extensions.getSendMessageSettings -import com.simplemobiletools.smsmessenger.extensions.getThreadId +import com.simplemobiletools.smsmessenger.helpers.getSendMessageSettings import com.simplemobiletools.smsmessenger.receivers.SmsStatusDeliveredReceiver import com.simplemobiletools.smsmessenger.receivers.SmsStatusSentReceiver diff --git a/app/src/main/res/drawable/ic_calendar_month_vector.xml b/app/src/main/res/drawable/ic_calendar_month_vector.xml new file mode 100644 index 00000000..cf05bd36 --- /dev/null +++ b/app/src/main/res/drawable/ic_calendar_month_vector.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_schedule_send_vector.xml b/app/src/main/res/drawable/ic_schedule_send_vector.xml new file mode 100644 index 00000000..4d46188f --- /dev/null +++ b/app/src/main/res/drawable/ic_schedule_send_vector.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/layout/activity_thread.xml b/app/src/main/res/layout/activity_thread.xml index 7854987f..fde37255 100644 --- a/app/src/main/res/layout/activity_thread.xml +++ b/app/src/main/res/layout/activity_thread.xml @@ -119,7 +119,9 @@ android:overScrollMode="ifContentScrolls" android:scrollbars="none" app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager" - app:stackFromEnd="true" /> + app:stackFromEnd="true" + tools:itemCount="3" + tools:listitem="@layout/item_sent_message" /> @@ -127,7 +129,7 @@ android:id="@+id/message_divider" android:layout_width="match_parent" android:layout_height="1px" - android:layout_above="@+id/thread_attachments_holder" + android:layout_above="@+id/scheduled_message_holder" android:background="@color/divider_grey" android:importantForAccessibility="no" /> @@ -145,13 +147,57 @@ android:padding="@dimen/normal_margin" android:src="@drawable/ic_plus_vector" /> + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 68e9a072..b8a87de9 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -7,4 +7,5 @@ 24dp 15dp 64dp + 36sp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 762434d8..4e7ca0ba 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -20,6 +20,7 @@ Unpin Forward Unable to compress image to selected size + Back and %d other @@ -29,6 +30,11 @@ New conversation Add Contact or Number… Suggestions + + Schedule send + Cancel schedule send + You must pick a time in the future + Keep the phone on and make sure nothing\'s killing the app in background Received SMS New message From 16ea540d48353a8816ce778309e30ee4e07055df Mon Sep 17 00:00:00 2001 From: Naveen Date: Tue, 27 Sep 2022 15:43:10 +0530 Subject: [PATCH 02/22] Setup database migrations for scheduled messages --- .../smsmessenger/databases/MessagesDatabase.kt | 11 ++++++++++- .../smsmessenger/interfaces/MessagesDao.kt | 6 ++++++ .../simplemobiletools/smsmessenger/models/Message.kt | 6 +++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/databases/MessagesDatabase.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/databases/MessagesDatabase.kt index d4c4972b..e0d46972 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/databases/MessagesDatabase.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/databases/MessagesDatabase.kt @@ -17,7 +17,7 @@ import com.simplemobiletools.smsmessenger.models.Conversation import com.simplemobiletools.smsmessenger.models.Message import com.simplemobiletools.smsmessenger.models.MessageAttachment -@Database(entities = [Conversation::class, Attachment::class, MessageAttachment::class, Message::class], version = 4) +@Database(entities = [Conversation::class, Attachment::class, MessageAttachment::class, Message::class], version = 5) @TypeConverters(Converters::class) abstract class MessagesDatabase : RoomDatabase() { @@ -41,6 +41,7 @@ abstract class MessagesDatabase : RoomDatabase() { .addMigrations(MIGRATION_1_2) .addMigrations(MIGRATION_2_3) .addMigrations(MIGRATION_3_4) + .addMigrations(MIGRATION_4_5) .build() } } @@ -85,5 +86,13 @@ abstract class MessagesDatabase : RoomDatabase() { } } } + + private val MIGRATION_4_5 = object : Migration(4, 5) { + override fun migrate(database: SupportSQLiteDatabase) { + database.apply { + execSQL("ALTER TABLE messages ADD COLUMN is_scheduled INTEGER NOT NULL DEFAULT 0") + } + } + } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/MessagesDao.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/MessagesDao.kt index 0ddf5b59..e9d6a3b2 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/MessagesDao.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/MessagesDao.kt @@ -23,6 +23,12 @@ interface MessagesDao { @Query("SELECT * FROM messages WHERE thread_id = :threadId") fun getThreadMessages(threadId: Long): List + @Query("SELECT * FROM messages WHERE thread_id = :threadId AND is_scheduled") + fun getScheduledThreadMessages(threadId: Long): List + + @Query("SELECT * FROM messages WHERE thread_id = :threadId AND id = :messageId AND is_scheduled") + fun getScheduledMessageWithId(threadId: Long, messageId: Long): Message + @Query("SELECT * FROM messages WHERE body LIKE :text") fun getMessagesWithText(text: String): List diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt index da89136e..48a25679 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt @@ -20,7 +20,11 @@ data class Message( @ColumnInfo(name = "attachment") val attachment: MessageAttachment?, @ColumnInfo(name = "sender_name") var senderName: String, @ColumnInfo(name = "sender_photo_uri") val senderPhotoUri: String, - @ColumnInfo(name = "subscription_id") var subscriptionId: Int) : ThreadItem() { + @ColumnInfo(name = "subscription_id") var subscriptionId: Int, + @ColumnInfo(name = "is_scheduled") var isScheduled: Boolean = false +) : ThreadItem() { fun isReceivedMessage() = type == Telephony.Sms.MESSAGE_TYPE_INBOX + + fun millis() = date * 1000L } From 2ff0880cb522267eef25d700fde5420785ab7c91 Mon Sep 17 00:00:00 2001 From: Naveen Date: Tue, 27 Sep 2022 16:08:45 +0530 Subject: [PATCH 03/22] Handle sending scheduled messages --- app/src/main/AndroidManifest.xml | 5 + .../smsmessenger/activities/MainActivity.kt | 2 +- .../smsmessenger/activities/ThreadActivity.kt | 160 ++++++++++++++---- .../smsmessenger/adapters/ThreadAdapter.kt | 80 ++++++--- .../smsmessenger/extensions/ArrayList.kt | 6 - .../smsmessenger/extensions/Collections.kt | 2 + .../smsmessenger/extensions/Context.kt | 15 +- .../smsmessenger/extensions/Date.kt | 2 +- .../smsmessenger/extensions/SimpleContact.kt | 8 + .../smsmessenger/helpers/Constants.kt | 1 + .../smsmessenger/helpers/Messaging.kt | 41 ++++- .../receivers/ScheduledMessageReceiver.kt | 47 ++++- app/src/main/res/layout/item_sent_message.xml | 11 ++ app/src/main/res/values/dimens.xml | 1 + 14 files changed, 306 insertions(+), 75 deletions(-) delete mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/ArrayList.kt create mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/SimpleContact.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3eea3fbf..b669ba54 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,6 +11,7 @@ + @@ -212,6 +213,10 @@ + + - val messages = getMessages(threadId, false) + val messages = getMessages(threadId, getImageResolutions = false, includeScheduledMessages = false) messages.chunked(30).forEach { currentMessages -> messagesDB.insertMessages(*currentMessages.toTypedArray()) } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt index b9575c04..11604f6e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -13,6 +13,8 @@ import android.os.Bundle import android.provider.ContactsContract import android.provider.MediaStore import android.provider.Telephony +import android.provider.Telephony.Sms.MESSAGE_TYPE_QUEUED +import android.provider.Telephony.Sms.STATUS_NONE import android.telephony.SmsManager import android.telephony.SmsMessage import android.telephony.SubscriptionInfo @@ -92,6 +94,7 @@ class ThreadActivity : SimpleActivity() { private var oldestMessageDate = -1 private var isScheduledMessage: Boolean = false + private var scheduledMessage: Message? = null private lateinit var scheduledDateTime: DateTime override fun onCreate(savedInstanceState: Bundle?) { @@ -260,8 +263,8 @@ class ThreadActivity : SimpleActivity() { val cachedMessagesCode = messages.clone().hashCode() messages = getMessages(threadId, true) - val hasParticipantWithoutName = participants.any { - it.phoneNumbers.map { it.normalizedNumber }.contains(it.name) + val hasParticipantWithoutName = participants.any { contact -> + contact.phoneNumbers.map { it.normalizedNumber }.contains(contact.name) } try { @@ -327,10 +330,8 @@ class ThreadActivity : SimpleActivity() { val currAdapter = thread_messages_list.adapter if (currAdapter == null) { - ThreadAdapter(this, threadItems, thread_messages_list) { - (it as? ThreadError)?.apply { - thread_type_message.setText(it.messageText) - } + ThreadAdapter(this, threadItems, thread_messages_list) { any -> + handleItemClick(any) }.apply { thread_messages_list.adapter = this } @@ -373,6 +374,13 @@ class ThreadActivity : SimpleActivity() { } } + private fun handleItemClick(any: Any) { + when { + any is Message && any.isScheduled -> showScheduledMessageInfo(any) + any is ThreadError -> thread_type_message.setText(any.messageText) + } + } + private fun fetchNextMessages() { if (messages.isEmpty() || allMessagesFetched || loadingOlderMessages) { return @@ -590,8 +598,7 @@ class ThreadActivity : SimpleActivity() { val defaultSmsSubscriptionId = SmsManager.getDefaultSmsSubscriptionId() val systemPreferredSimIdx = if (defaultSmsSubscriptionId >= 0) { - val defaultSmsSIM = subscriptionManagerCompat().getActiveSubscriptionInfo(defaultSmsSubscriptionId) - availableSIMs.indexOfFirstOrNull { it.subscriptionId == defaultSmsSIM.subscriptionId } + availableSIMs.indexOfFirstOrNull { it.subscriptionId == defaultSmsSubscriptionId } } else { null } @@ -600,13 +607,7 @@ class ThreadActivity : SimpleActivity() { } private fun blockNumber() { - val numbers = ArrayList() - participants.forEach { - it.phoneNumbers.forEach { - numbers.add(it.normalizedNumber) - } - } - + val numbers = participants.getAddresses() val numbersString = TextUtils.join(", ", numbers) val question = String.format(resources.getString(R.string.block_confirmation), numbersString) @@ -939,21 +940,44 @@ class ThreadActivity : SimpleActivity() { text = removeDiacriticsIfNeeded(text) - val addresses = participants - .flatMap { it.phoneNumbers } - .map { it.normalizedNumber } + val subscriptionId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId ?: SmsManager.getDefaultSmsSubscriptionId() - val currentSubscriptionId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId - val attachments = attachmentSelections.values.map { it.uri } + if (isScheduledMessage) { + sendScheduledMessage(text, subscriptionId) + } else { + sendNormalMessage(text, subscriptionId) + } + } + + private fun sendScheduledMessage(text: String, subscriptionId: Int) { + refreshedSinceSent = false + try { + ensureBackgroundThread { + val messageId = scheduledMessage?.id ?: generateRandomMessageId() + val message = buildScheduledMessage(text, subscriptionId, messageId) + messagesDB.insertOrUpdate(message) + scheduleMessage(message) + } + clearCurrentMessage() + hideScheduleSendUi() + + if (!refreshedSinceSent) { + refreshMessages() + } + } catch (e: Exception) { + showErrorToast(e.localizedMessage ?: getString(R.string.unknown_error_occurred)) + } + } + + private fun sendNormalMessage(text: String, subscriptionId: Int) { + val addresses = participants.getAddresses() + val attachments = attachmentSelections.values + .map { it.uri } try { refreshedSinceSent = false - sendTransactionMessage(text, addresses, currentSubscriptionId, attachments) - - thread_type_message.setText("") - attachmentSelections.clear() - thread_attachments_holder.beGone() - thread_attachments_wrapper.removeAllViews() + sendMessage(text, addresses, subscriptionId, attachments) + clearCurrentMessage() if (!refreshedSinceSent) { refreshMessages() @@ -965,6 +989,13 @@ class ThreadActivity : SimpleActivity() { } } + private fun clearCurrentMessage() { + thread_type_message.setText("") + attachmentSelections.clear() + thread_attachments_holder.beGone() + thread_attachments_wrapper.removeAllViews() + } + // show selected contacts, properly split to new lines when appropriate // based on https://stackoverflow.com/a/13505029/1967672 private fun showSelectedContact(views: ArrayList) { @@ -1115,10 +1146,10 @@ class ThreadActivity : SimpleActivity() { messages.filter { !it.isReceivedMessage() && it.id > lastMaxId }.forEach { latestMessage -> // subscriptionIds seem to be not filled out at sending with multiple SIM cards, so fill it manually if ((subscriptionManagerCompat().activeSubscriptionInfoList?.size ?: 0) > 1) { - val SIMId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId - if (SIMId != null) { - updateMessageSubscriptionId(latestMessage.id, SIMId) - latestMessage.subscriptionId = SIMId + val subscriptionId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId + if (subscriptionId != null) { + updateMessageSubscriptionId(latestMessage.id, subscriptionId) + latestMessage.subscriptionId = subscriptionId } } @@ -1129,11 +1160,15 @@ class ThreadActivity : SimpleActivity() { setupSIMSelector() } - private fun updateMessageType() { - val text = thread_type_message.text.toString() + private fun isMmsMessage(text: String): Boolean { val isGroupMms = participants.size > 1 && config.sendGroupMessageMMS val isLongMmsMessage = isLongMmsMessage(text) && config.sendLongMessageMMS - val stringId = if (attachmentSelections.isNotEmpty() || isGroupMms || isLongMmsMessage) { + return attachmentSelections.isNotEmpty() || isGroupMms || isLongMmsMessage + } + + private fun updateMessageType() { + val text = thread_type_message.text.toString() + val stringId = if (isMmsMessage(text)) { R.string.mms } else { R.string.sms @@ -1150,6 +1185,27 @@ class ThreadActivity : SimpleActivity() { return File.createTempFile("IMG_", ".jpg", outputDirectory) } + private fun showScheduledMessageInfo(message: Message) { + // todo: maybe show options to edit, delete, and send the message now + editScheduledMessage(message) + } + + private fun editScheduledMessage(message: Message) { + scheduledMessage = message + clearCurrentMessage() + thread_type_message.setText(message.body) + + val messageAttachment = message.attachment + if (messageAttachment != null) { + for (attachment in messageAttachment.attachments) { + addAttachment(attachment.getUri()) + } + } + + scheduledDateTime = DateTime(message.millis()) + showScheduleSendUi() + } + private fun launchScheduleSendDialog(originalDt: DateTime? = null) { ScheduleSendDialog(this, originalDt) { newDt -> if (newDt != null) { @@ -1175,13 +1231,19 @@ class ThreadActivity : SimpleActivity() { applyColorFilter(textColor) setOnClickListener { hideScheduleSendUi() + if (scheduledMessage != null) { + ensureBackgroundThread { + messagesDB.delete(scheduledMessage!!.id) + refreshMessages() + } + } } } } private fun showScheduleSendUi() { isScheduledMessage = true - updateSendButton() + updateSendButtonDrawable() scheduled_message_holder.beVisible() scheduled_message_button.text = scheduledDateTime.humanize(this) } @@ -1189,10 +1251,10 @@ class ThreadActivity : SimpleActivity() { private fun hideScheduleSendUi() { isScheduledMessage = false scheduled_message_holder.beGone() - updateSendButton() + updateSendButtonDrawable() } - private fun updateSendButton() { + private fun updateSendButtonDrawable() { val drawableResId = if (isScheduledMessage) { R.drawable.ic_schedule_send_vector } else { @@ -1203,4 +1265,30 @@ class ThreadActivity : SimpleActivity() { thread_send_message.setCompoundDrawablesWithIntrinsicBounds(null, this, null, null) } } + + private fun buildScheduledMessage(text: String, subscriptionId: Int, messageId: Long): Message { + return Message( + id = messageId, + body = text, + type = MESSAGE_TYPE_QUEUED, + status = STATUS_NONE, + participants = participants, + date = (scheduledDateTime.millis / 1000).toInt(), + read = false, + threadId = threadId, + isMMS = isMmsMessage(text), + attachment = buildMessageAttachment(text, messageId), + senderName = "", + senderPhotoUri = "", + subscriptionId = subscriptionId, + isScheduled = true + ) + } + + private fun buildMessageAttachment(text: String, messageId: Long): MessageAttachment { + val attachments = attachmentSelections.values + .map { Attachment(null, messageId, it.uri.toString(), "*/*", 0, 0, "") } + .toArrayList() + return MessageAttachment(messageId, text, attachments) + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt index 75eb264d..3b845010 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt @@ -4,10 +4,10 @@ import android.annotation.SuppressLint import android.content.ActivityNotFoundException import android.content.Intent import android.graphics.Color +import android.graphics.Typeface import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.net.Uri -import android.telephony.SubscriptionManager import android.util.TypedValue import android.view.Menu import android.view.View @@ -40,7 +40,12 @@ import com.simplemobiletools.smsmessenger.models.* import kotlinx.android.synthetic.main.item_attachment_image.view.* import kotlinx.android.synthetic.main.item_attachment_vcard.view.* import kotlinx.android.synthetic.main.item_received_message.view.* +import kotlinx.android.synthetic.main.item_received_message.view.thread_mesage_attachments_holder +import kotlinx.android.synthetic.main.item_received_message.view.thread_message_body +import kotlinx.android.synthetic.main.item_received_message.view.thread_message_holder +import kotlinx.android.synthetic.main.item_received_message.view.thread_message_play_outline import kotlinx.android.synthetic.main.item_received_unknown_attachment.view.* +import kotlinx.android.synthetic.main.item_sent_message.view.* import kotlinx.android.synthetic.main.item_sent_unknown_attachment.view.* import kotlinx.android.synthetic.main.item_thread_date_time.view.* import kotlinx.android.synthetic.main.item_thread_error.view.* @@ -252,29 +257,9 @@ class ThreadAdapter( thread_message_body.beVisibleIf(message.body.isNotEmpty()) if (message.isReceivedMessage()) { - thread_message_sender_photo.beVisible() - thread_message_sender_photo.setOnClickListener { - val contact = message.participants.first() - context.getContactFromAddress(contact.phoneNumbers.first().normalizedNumber) { - if (it != null) { - (activity as ThreadActivity).startContactDetailsIntent(it) - } - } - } - thread_message_body.setTextColor(textColor) - thread_message_body.setLinkTextColor(context.getProperPrimaryColor()) - - if (!activity.isFinishing && !activity.isDestroyed) { - SimpleContactsHelper(context).loadContactImage(message.senderPhotoUri, thread_message_sender_photo, message.senderName) - } + setupReceivedMessageView(view, message) } else { - thread_message_sender_photo?.beGone() - val background = context.getProperPrimaryColor() - thread_message_body.background.applyColorFilter(background) - - val contrastColor = background.getContrastColor() - thread_message_body.setTextColor(contrastColor) - thread_message_body.setLinkTextColor(contrastColor) + setupSentMessageView(view, message) } thread_message_body.setOnLongClickListener { @@ -304,6 +289,55 @@ class ThreadAdapter( } } + private fun setupReceivedMessageView(view: View, message: Message) { + view.apply { + thread_message_sender_photo.beVisible() + thread_message_sender_photo.setOnClickListener { + val contact = message.participants.first() + context.getContactFromAddress(contact.phoneNumbers.first().normalizedNumber) { + if (it != null) { + (activity as ThreadActivity).startContactDetailsIntent(it) + } + } + } + thread_message_body.setTextColor(textColor) + thread_message_body.setLinkTextColor(context.getProperPrimaryColor()) + + if (!activity.isFinishing && !activity.isDestroyed) { + SimpleContactsHelper(context).loadContactImage(message.senderPhotoUri, thread_message_sender_photo, message.senderName) + } + } + } + + private fun setupSentMessageView(view: View, message: Message) { + view.apply { + thread_message_sender_photo?.beGone() + val background = context.getProperPrimaryColor() + thread_message_body.background.applyColorFilter(background) + + val contrastColor = background.getContrastColor() + thread_message_body.setTextColor(contrastColor) + thread_message_body.setLinkTextColor(contrastColor) + + val padding = thread_message_body.paddingStart + if (message.isScheduled) { + thread_message_scheduled_icon.beVisible() + thread_message_scheduled_icon.applyColorFilter(contrastColor) + + thread_message_scheduled_icon.onGlobalLayout { + val rightPadding = padding + thread_message_scheduled_icon.measuredWidth + thread_message_body.setPadding(padding, padding, rightPadding, padding) + } + thread_message_body.typeface = Typeface.create(Typeface.DEFAULT, Typeface.ITALIC) + } else { + thread_message_scheduled_icon.beGone() + + thread_message_body.setPadding(padding, padding, padding, padding) + thread_message_body.typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL) + } + } + } + private fun setupImageView(holder: ViewHolder, parent: View, message: Message, attachment: Attachment) { val mimetype = attachment.mimetype val uri = attachment.getUri() diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/ArrayList.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/ArrayList.kt deleted file mode 100644 index dfaf3202..00000000 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/ArrayList.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.simplemobiletools.smsmessenger.extensions - -import android.text.TextUtils -import com.simplemobiletools.commons.models.SimpleContact - -fun ArrayList.getThreadTitle() = TextUtils.join(", ", map { it.name }.toTypedArray()) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Collections.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Collections.kt index 38e3d1b2..2fdbaac4 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Collections.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Collections.kt @@ -30,3 +30,5 @@ fun Map.toContentValues(): ContentValues { return contentValues } + +fun Collection.toArrayList() = ArrayList(this) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt index 4dd05681..bfcc129b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt @@ -57,7 +57,7 @@ val Context.messageAttachmentsDB: MessageAttachmentsDao get() = getMessagesDB(). val Context.messagesDB: MessagesDao get() = getMessagesDB().MessagesDao() -fun Context.getMessages(threadId: Long, getImageResolutions: Boolean, dateFrom: Int = -1): ArrayList { +fun Context.getMessages(threadId: Long, getImageResolutions: Boolean, dateFrom: Int = -1, includeScheduledMessages: Boolean = true): ArrayList { val uri = Sms.CONTENT_URI val projection = arrayOf( Sms._ID, @@ -116,8 +116,17 @@ fun Context.getMessages(threadId: Long, getImageResolutions: Boolean, dateFrom: } messages.addAll(getMMS(threadId, getImageResolutions, sortOrder)) - messages = messages.filter { it.participants.isNotEmpty() } - .sortedWith(compareBy { it.date }.thenBy { it.id }).toMutableList() as ArrayList + + if (includeScheduledMessages) { + val scheduledMessages = messagesDB.getScheduledThreadMessages(threadId) + messages.addAll(scheduledMessages) + } + + messages = messages + .filter { it.participants.isNotEmpty() } + .filterNot { it.isScheduled && it.millis() < System.currentTimeMillis() } + .sortedWith(compareBy { it.date }.thenBy { it.id }) + .toMutableList() as ArrayList return messages } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Date.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Date.kt index 49182867..50989a6e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Date.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Date.kt @@ -14,6 +14,6 @@ fun DateTime.humanize(context: Context, now: DateTime = DateTime.now(), pattern: return if (yearOfCentury().get() > now.yearOfCentury().get()) { toString(pattern) } else { - DateUtils.getRelativeDateTimeString(context, millis, DateUtils.MINUTE_IN_MILLIS, DateUtils.DAY_IN_MILLIS, 0).toString() + DateUtils.getRelativeDateTimeString(context, millis, DateUtils.SECOND_IN_MILLIS, DateUtils.DAY_IN_MILLIS, 0).toString() } } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/SimpleContact.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/SimpleContact.kt new file mode 100644 index 00000000..2efa8e26 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/SimpleContact.kt @@ -0,0 +1,8 @@ +package com.simplemobiletools.smsmessenger.extensions + +import android.text.TextUtils +import com.simplemobiletools.commons.models.SimpleContact + +fun ArrayList.getThreadTitle(): String = TextUtils.join(", ", map { it.name }.toTypedArray()).orEmpty() + +fun ArrayList.getAddresses() = flatMap { it.phoneNumbers }.map { it.normalizedNumber } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt index f3711dec..15baf492 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt @@ -29,6 +29,7 @@ const val IMPORT_SMS = "import_sms" const val IMPORT_MMS = "import_mms" const val WAS_DB_CLEARED = "was_db_cleared_2" const val EXTRA_VCARD_URI = "vcard" +const val SCHEDULED_MESSAGE_ID = "scheduled_message_id" private const val PATH = "com.simplemobiletools.smsmessenger.action." const val MARK_AS_READ = PATH + "mark_as_read" diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt index 2f0ed4c7..8e9be543 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt @@ -1,16 +1,28 @@ package com.simplemobiletools.smsmessenger.helpers +import android.app.AlarmManager +import android.app.PendingIntent import android.content.Context import android.content.Intent import android.net.Uri +import android.os.Handler +import android.os.Looper +import androidx.core.app.AlarmManagerCompat import com.klinker.android.send_message.Settings import com.klinker.android.send_message.Transaction import com.klinker.android.send_message.Utils import com.simplemobiletools.commons.extensions.showErrorToast +import com.simplemobiletools.commons.helpers.isMarshmallowPlus import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.extensions.config +import com.simplemobiletools.smsmessenger.models.Message +import com.simplemobiletools.smsmessenger.receivers.ScheduledMessageReceiver import com.simplemobiletools.smsmessenger.receivers.SmsStatusDeliveredReceiver import com.simplemobiletools.smsmessenger.receivers.SmsStatusSentReceiver +import org.joda.time.DateTime +import org.joda.time.DateTimeZone +import kotlin.math.abs +import kotlin.random.Random fun Context.getSendMessageSettings(): Settings { val settings = Settings() @@ -22,7 +34,7 @@ fun Context.getSendMessageSettings(): Settings { return settings } -fun Context.sendTransactionMessage(text: String, addresses: List, subscriptionId: Int?, attachments: List) { +fun Context.sendMessage(text: String, addresses: List, subscriptionId: Int?, attachments: List) { val settings = getSendMessageSettings() if (subscriptionId != null) { settings.subscriptionId = subscriptionId @@ -50,10 +62,35 @@ fun Context.sendTransactionMessage(text: String, addresses: List, subscr transaction.setExplicitBroadcastForSentSms(smsSentIntent) transaction.setExplicitBroadcastForDeliveredSms(deliveredIntent) - transaction.sendNewMessage(message) + Handler(Looper.getMainLooper()).post { + transaction.sendNewMessage(message) + } +} + +fun Context.scheduleMessage(message: Message) { + val intent = Intent(this, ScheduledMessageReceiver::class.java) + intent.putExtra(THREAD_ID, message.threadId) + intent.putExtra(SCHEDULED_MESSAGE_ID, message.id) + + var flags = PendingIntent.FLAG_UPDATE_CURRENT + if (isMarshmallowPlus()) { + flags = flags or PendingIntent.FLAG_IMMUTABLE + } + val pendingIntent = PendingIntent.getBroadcast(this, message.id.toInt(), intent, flags) + val triggerAtMillis = message.millis() + + val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager + AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent) } fun Context.isLongMmsMessage(text: String): Boolean { val settings = getSendMessageSettings() return Utils.getNumPages(settings, text) > settings.sendLongAsMmsAfter } + +/** Not to be used with real messages persisted in the telephony db. This is for internal use only (e.g. scheduled messages). */ +fun generateRandomMessageId(length: Int = 8): Long { + val millis = DateTime.now(DateTimeZone.UTC).millis + val random = abs(Random(millis).nextLong()) + return random.toString().takeLast(length).toLong() +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt index 72aa53d1..e3c41473 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt @@ -3,9 +3,50 @@ package com.simplemobiletools.smsmessenger.receivers import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.os.PowerManager +import com.simplemobiletools.commons.extensions.showErrorToast +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import com.simplemobiletools.smsmessenger.R +import com.simplemobiletools.smsmessenger.extensions.getAddresses +import com.simplemobiletools.smsmessenger.extensions.messagesDB +import com.simplemobiletools.smsmessenger.helpers.SCHEDULED_MESSAGE_ID +import com.simplemobiletools.smsmessenger.helpers.THREAD_ID +import com.simplemobiletools.smsmessenger.helpers.refreshMessages +import com.simplemobiletools.smsmessenger.helpers.sendMessage -class ScheduledMessageReceiver: BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - TODO("Not yet implemented") +class ScheduledMessageReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager + val wakelock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "simple.messenger:scheduled.message.receiver") + wakelock.acquire(3000) + + + ensureBackgroundThread { + handleIntent(context, intent) + } + } + + private fun handleIntent(context: Context, intent: Intent) { + val threadId = intent.getLongExtra(THREAD_ID, 0L) + val messageId = intent.getLongExtra(SCHEDULED_MESSAGE_ID, 0L) + val message = try { + context.messagesDB.getScheduledMessageWithId(threadId, messageId) + } catch (e: Exception) { + return + } + + val addresses = message.participants.getAddresses() + val attachments = message.attachment?.attachments?.mapNotNull { it.getUri() } ?: emptyList() + + try { + context.sendMessage(message.body, addresses, message.subscriptionId, attachments) + context.messagesDB.delete(messageId) + refreshMessages() + } catch (e: Exception) { + context.showErrorToast(e) + } catch (e: Error) { + context.showErrorToast(e.localizedMessage ?: context.getString(R.string.unknown_error_occurred)) + } } } diff --git a/app/src/main/res/layout/item_sent_message.xml b/app/src/main/res/layout/item_sent_message.xml index a5d57e11..def52b64 100644 --- a/app/src/main/res/layout/item_sent_message.xml +++ b/app/src/main/res/layout/item_sent_message.xml @@ -48,4 +48,15 @@ android:textSize="@dimen/normal_text_size" tools:text="Sent message" /> + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index b8a87de9..d6f8e492 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -8,4 +8,5 @@ 15dp 64dp 36sp + 20dp From 703bb003c1ceb41636eb0f5dfb63ceed3168e8ae Mon Sep 17 00:00:00 2001 From: Naveen Date: Tue, 27 Sep 2022 23:40:39 +0530 Subject: [PATCH 04/22] Coerce hours in range 0 to 23 --- .../smsmessenger/dialogs/ScheduleSendDialog.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt index 6f0b0808..38c8792d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt @@ -155,7 +155,7 @@ class ScheduleSendDialog(private val activity: BaseSimpleActivity, private var d } } - private fun getNextHour() = calendar.get(Calendar.HOUR_OF_DAY) + 1 + private fun getNextHour() = (calendar.get(Calendar.HOUR_OF_DAY) + 1).coerceIn(0, 23) private fun getNextMinute() = (calendar.get(Calendar.MINUTE) + 5).round(5).coerceIn(0, 59) } From f837790948a1d24550d4b916c6068821f4754de1 Mon Sep 17 00:00:00 2001 From: Naveen Date: Wed, 28 Sep 2022 01:51:31 +0530 Subject: [PATCH 05/22] Add a little bottom margin in thread item list --- app/src/main/res/layout/activity_thread.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/layout/activity_thread.xml b/app/src/main/res/layout/activity_thread.xml index fde37255..4f60e8ac 100644 --- a/app/src/main/res/layout/activity_thread.xml +++ b/app/src/main/res/layout/activity_thread.xml @@ -115,6 +115,7 @@ android:id="@+id/thread_messages_list" android:layout_width="match_parent" android:layout_height="match_parent" + android:layout_marginBottom="@dimen/small_margin" android:clipToPadding="false" android:overScrollMode="ifContentScrolls" android:scrollbars="none" From ee8130c76710c92f47006d5f48dd590e1bc3d5d0 Mon Sep 17 00:00:00 2001 From: Naveen Date: Wed, 28 Sep 2022 02:05:06 +0530 Subject: [PATCH 06/22] Handle conversations with scheduled messages only --- .../smsmessenger/activities/MainActivity.kt | 37 ++++++++-- .../smsmessenger/activities/ThreadActivity.kt | 39 ++++++++--- .../adapters/ConversationsAdapter.kt | 13 ++-- .../smsmessenger/adapters/ThreadAdapter.kt | 21 ++++-- .../databases/MessagesDatabase.kt | 7 +- .../smsmessenger/extensions/Context.kt | 70 ++++++++++++++++++- .../smsmessenger/helpers/Messaging.kt | 2 +- .../interfaces/ConversationsDao.kt | 3 + .../smsmessenger/interfaces/MessagesDao.kt | 4 +- .../smsmessenger/models/Conversation.kt | 3 +- .../receivers/ScheduledMessageReceiver.kt | 8 ++- 11 files changed, 171 insertions(+), 36 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt index b3645070..7047ec34 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt @@ -208,29 +208,51 @@ class MainActivity : SimpleActivity() { setupConversations(conversations) getNewConversations(conversations) } + conversations.forEach { + clearExpiredScheduledMessages(it.threadId) + } } } private fun getNewConversations(cachedConversations: ArrayList) { - val privateCursor = getMyContactsCursor(false, true) + val privateCursor = getMyContactsCursor(favoritesOnly = false, withPhoneNumbersOnly = true) ensureBackgroundThread { val privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor) val conversations = getConversations(privateContacts = privateContacts) + val scheduledConversations = cachedConversations.filter { it.isScheduled } + val allConversations = conversations.toArrayList().apply { + addAll(scheduledConversations) + } runOnUiThread { - setupConversations(conversations) + setupConversations(allConversations) } conversations.forEach { clonedConversation -> - if (!cachedConversations.map { it.threadId }.contains(clonedConversation.threadId)) { + val threadIds = cachedConversations.map { it.threadId } + if (!threadIds.contains(clonedConversation.threadId)) { conversationsDB.insertOrUpdate(clonedConversation) cachedConversations.add(clonedConversation) } } cachedConversations.forEach { cachedConversation -> - if (!conversations.map { it.threadId }.contains(cachedConversation.threadId)) { - conversationsDB.deleteThreadId(cachedConversation.threadId) + val threadId = cachedConversation.threadId + + val isTemporaryThread = cachedConversation.isScheduled + val isConversationDeleted = !conversations.map { it.threadId }.contains(threadId) + if (isConversationDeleted && !isTemporaryThread) { + conversationsDB.deleteThreadId(threadId) + } + + val newConversation = conversations.find { it.phoneNumber == cachedConversation.phoneNumber } + if (isTemporaryThread && newConversation != null) { + // delete the original temporary thread and move any scheduled messages to the new thread + conversationsDB.deleteThreadId(threadId) + messagesDB.getScheduledThreadMessages(threadId) + .forEach { message -> + messagesDB.insertOrUpdate(message.copy(threadId = newConversation.threadId)) + } } } @@ -273,8 +295,9 @@ class MainActivity : SimpleActivity() { hideKeyboard() ConversationsAdapter(this, sortedConversations, conversations_list) { Intent(this, ThreadActivity::class.java).apply { - putExtra(THREAD_ID, (it as Conversation).threadId) - putExtra(THREAD_TITLE, it.title) + val conversation = it as Conversation + putExtra(THREAD_ID, conversation.threadId) + putExtra(THREAD_TITLE, conversation.title) startActivity(this) } }.apply { diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt index 11604f6e..9d72bf14 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -232,6 +232,8 @@ class ThreadActivity : SimpleActivity() { } catch (e: Exception) { ArrayList() } + clearExpiredScheduledMessages(threadId, messages) + messages.removeAll { it.isScheduled && it.millis() < System.currentTimeMillis() } messages.sortBy { it.date } if (messages.size > MESSAGES_LIMIT) { @@ -330,9 +332,13 @@ class ThreadActivity : SimpleActivity() { val currAdapter = thread_messages_list.adapter if (currAdapter == null) { - ThreadAdapter(this, threadItems, thread_messages_list) { any -> - handleItemClick(any) - }.apply { + ThreadAdapter( + activity = this, + messages = threadItems, + recyclerView = thread_messages_list, + itemClick = { handleItemClick(it) }, + onThreadIdUpdate = { threadId = it } + ).apply { thread_messages_list.adapter = this } @@ -953,8 +959,13 @@ class ThreadActivity : SimpleActivity() { refreshedSinceSent = false try { ensureBackgroundThread { - val messageId = scheduledMessage?.id ?: generateRandomMessageId() + val messageId = scheduledMessage?.id ?: generateRandomId() val message = buildScheduledMessage(text, subscriptionId, messageId) + if (messages.isEmpty()) { + // create a temporary thread until a real message is sent + threadId = message.threadId + createTemporaryThread(message, message.threadId) + } messagesDB.insertOrUpdate(message) scheduleMessage(message) } @@ -1140,8 +1151,18 @@ class ThreadActivity : SimpleActivity() { notificationManager.cancel(threadId.hashCode()) } - val lastMaxId = messages.maxByOrNull { it.id }?.id ?: 0L - messages = getMessages(threadId, true) + val newThreadId = getThreadId(participants.getAddresses().toSet()) + val newMessages = getMessages(newThreadId, false) + messages = if (messages.all { it.isScheduled } && newMessages.isNotEmpty()) { + threadId = newThreadId + // update scheduled messages with real thread id + updateScheduledMessagesThreadId(messages, newThreadId) + getMessages(newThreadId, true) + } else { + getMessages(threadId, true) + } + + val lastMaxId = messages.filterNot { it.isScheduled }.maxByOrNull { it.id }?.id ?: 0L messages.filter { !it.isReceivedMessage() && it.id > lastMaxId }.forEach { latestMessage -> // subscriptionIds seem to be not filled out at sending with multiple SIM cards, so fill it manually @@ -1233,7 +1254,7 @@ class ThreadActivity : SimpleActivity() { hideScheduleSendUi() if (scheduledMessage != null) { ensureBackgroundThread { - messagesDB.delete(scheduledMessage!!.id) + deleteScheduledMessage(scheduledMessage!!.id) refreshMessages() } } @@ -1267,6 +1288,7 @@ class ThreadActivity : SimpleActivity() { } private fun buildScheduledMessage(text: String, subscriptionId: Int, messageId: Long): Message { + val threadId = if (messages.isEmpty()) messageId else threadId return Message( id = messageId, body = text, @@ -1287,8 +1309,9 @@ class ThreadActivity : SimpleActivity() { private fun buildMessageAttachment(text: String, messageId: Long): MessageAttachment { val attachments = attachmentSelections.values - .map { Attachment(null, messageId, it.uri.toString(), "*/*", 0, 0, "") } + .map { Attachment(null, messageId, it.uri.toString(), contentResolver.getType(it.uri) ?: "*/*", 0, 0, "") } .toArrayList() + return MessageAttachment(messageId, text, attachments) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt index 9a9a47bb..4613f0f1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt @@ -173,7 +173,7 @@ class ConversationsAdapter( } try { - conversations.removeAll(conversationsToRemove) + conversations.removeAll(conversationsToRemove.toSet()) } catch (ignored: Exception) { } @@ -319,15 +319,16 @@ class ConversationsAdapter( setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 0.8f) } - if (conversation.read) { - conversation_address.setTypeface(null, Typeface.NORMAL) - conversation_body_short.setTypeface(null, Typeface.NORMAL) + val style = if (conversation.read) { conversation_body_short.alpha = 0.7f + if (conversation.isScheduled) Typeface.ITALIC else Typeface.NORMAL } else { - conversation_address.setTypeface(null, Typeface.BOLD) - conversation_body_short.setTypeface(null, Typeface.BOLD) conversation_body_short.alpha = 1f + if (conversation.isScheduled) Typeface.BOLD_ITALIC else Typeface.BOLD + } + conversation_address.setTypeface(null, style) + conversation_body_short.setTypeface(null, style) arrayListOf(conversation_address, conversation_body_short, conversation_date).forEach { it.setTextColor(textColor) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt index 3b845010..4685b4cd 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt @@ -51,9 +51,10 @@ import kotlinx.android.synthetic.main.item_thread_date_time.view.* import kotlinx.android.synthetic.main.item_thread_error.view.* import kotlinx.android.synthetic.main.item_thread_sending.view.* import kotlinx.android.synthetic.main.item_thread_success.view.* +import java.util.* class ThreadAdapter( - activity: SimpleActivity, var messages: ArrayList, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit + activity: SimpleActivity, var messages: ArrayList, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit, val onThreadIdUpdate: (Long) -> Unit ) : MyRecyclerViewAdapter(activity, recyclerView, itemClick) { private var fontSize = activity.getTextSize() @@ -204,11 +205,21 @@ class ThreadAdapter( messagesToRemove.forEach { activity.deleteMessage((it as Message).id, it.isMMS) } - messages.removeAll(messagesToRemove) + messages.removeAll(messagesToRemove.toSet()) activity.updateLastConversationMessage(threadId) + val messages = messages.filterIsInstance() + if (messages.isNotEmpty() && messages.all { it.isScheduled }) { + // move all scheduled messages to a temporary thread as there are no real messages left + val message = messages.last() + val newThreadId = generateRandomId() + activity.createTemporaryThread(message, newThreadId) + activity.updateScheduledMessagesThreadId(messages, newThreadId) + onThreadIdUpdate(newThreadId) + } + activity.runOnUiThread { - if (messages.filter { it is Message }.isEmpty()) { + if (messages.isEmpty()) { activity.finish() } else { removeSelectedItems(positions) @@ -333,7 +344,7 @@ class ThreadAdapter( thread_message_scheduled_icon.beGone() thread_message_body.setPadding(padding, padding, padding, padding) - thread_message_body.typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL) + thread_message_body.typeface = Typeface.DEFAULT } } } @@ -495,7 +506,7 @@ class ThreadAdapter( private fun launchViewIntent(uri: Uri, mimetype: String, filename: String) { Intent().apply { action = Intent.ACTION_VIEW - setDataAndType(uri, mimetype.toLowerCase()) + setDataAndType(uri, mimetype.lowercase(Locale.getDefault())) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) try { diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/databases/MessagesDatabase.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/databases/MessagesDatabase.kt index e0d46972..72fda67f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/databases/MessagesDatabase.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/databases/MessagesDatabase.kt @@ -67,8 +67,10 @@ abstract class MessagesDatabase : RoomDatabase() { database.apply { execSQL("CREATE TABLE conversations_new (`thread_id` INTEGER NOT NULL PRIMARY KEY, `snippet` TEXT NOT NULL, `date` INTEGER NOT NULL, `read` INTEGER NOT NULL, `title` TEXT NOT NULL, `photo_uri` TEXT NOT NULL, `is_group_conversation` INTEGER NOT NULL, `phone_number` TEXT NOT NULL)") - execSQL("INSERT OR IGNORE INTO conversations_new (thread_id, snippet, date, read, title, photo_uri, is_group_conversation, phone_number) " + - "SELECT thread_id, snippet, date, read, title, photo_uri, is_group_conversation, phone_number FROM conversations") + execSQL( + "INSERT OR IGNORE INTO conversations_new (thread_id, snippet, date, read, title, photo_uri, is_group_conversation, phone_number) " + + "SELECT thread_id, snippet, date, read, title, photo_uri, is_group_conversation, phone_number FROM conversations" + ) execSQL("DROP TABLE conversations") @@ -91,6 +93,7 @@ abstract class MessagesDatabase : RoomDatabase() { override fun migrate(database: SupportSQLiteDatabase) { database.apply { execSQL("ALTER TABLE messages ADD COLUMN is_scheduled INTEGER NOT NULL DEFAULT 0") + execSQL("ALTER TABLE conversations ADD COLUMN is_scheduled INTEGER NOT NULL DEFAULT 0") } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt index bfcc129b..03287e98 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt @@ -118,8 +118,12 @@ fun Context.getMessages(threadId: Long, getImageResolutions: Boolean, dateFrom: messages.addAll(getMMS(threadId, getImageResolutions, sortOrder)) if (includeScheduledMessages) { - val scheduledMessages = messagesDB.getScheduledThreadMessages(threadId) - messages.addAll(scheduledMessages) + try { + val scheduledMessages = messagesDB.getScheduledThreadMessages(threadId) + messages.addAll(scheduledMessages) + } catch (e: Exception) { + e.printStackTrace() + } } messages = messages @@ -580,7 +584,11 @@ fun Context.deleteConversation(threadId: Long) { } uri = Mms.CONTENT_URI - contentResolver.delete(uri, selection, selectionArgs) + try { + contentResolver.delete(uri, selection, selectionArgs) + } catch (e: Exception) { + e.printStackTrace() + } conversationsDB.deleteThreadId(threadId) messagesDB.deleteThreadMessages(threadId) @@ -598,6 +606,14 @@ fun Context.deleteMessage(id: Long, isMMS: Boolean) { } } +fun Context.deleteScheduledMessage(messageId: Long) { + try { + messagesDB.delete(messageId) + } catch (e: Exception) { + showErrorToast(e) + } +} + fun Context.markMessageRead(id: Long, isMMS: Boolean) { val uri = if (isMMS) Mms.CONTENT_URI else Sms.CONTENT_URI val contentValues = ContentValues().apply { @@ -1001,3 +1017,51 @@ fun Context.subscriptionManagerCompat(): SubscriptionManager { SubscriptionManager.from(this) } } + +fun Context.createTemporaryThread(message: Message, threadId: Long = generateRandomId()) { + val simpleContactHelper = SimpleContactsHelper(this) + val addresses = message.participants.getAddresses() + val photoUri = if (addresses.size == 1) simpleContactHelper.getPhotoUriFromPhoneNumber(addresses.first()) else "" + + val conversation = Conversation( + threadId = threadId, + snippet = message.body, + date = message.date, + read = true, + title = message.participants.getThreadTitle(), + photoUri = photoUri, + isGroupConversation = addresses.size > 1, + phoneNumber = addresses.first(), + isScheduled = true + ) + try { + conversationsDB.insertOrUpdate(conversation) + } catch (e: Exception) { + e.printStackTrace() + } +} + +fun Context.updateScheduledMessagesThreadId(messages: List, newThreadId: Long) { + val scheduledMessages = messages.map { it.copy(threadId = newThreadId) }.toTypedArray() + messagesDB.insertMessages(*scheduledMessages) +} + +fun Context.clearExpiredScheduledMessages(threadId: Long, messagesToDelete: List? = null) { + val messages = messagesToDelete ?: messagesDB.getScheduledThreadMessages(threadId) + + try { + messages.filter { it.isScheduled && it.millis() < System.currentTimeMillis() }.forEach { msg -> + messagesDB.delete(msg.id) + } + if (messages.filterNot { it.isScheduled && it.millis() < System.currentTimeMillis() }.isEmpty()) { + // delete empty temporary thread + val conversation = conversationsDB.getConversationWithThreadId(threadId) + if (conversation != null && conversation.isScheduled) { + conversationsDB.deleteThreadId(threadId) + } + } + } catch (e: Exception) { + e.printStackTrace() + return + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt index 8e9be543..b893fc11 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt @@ -89,7 +89,7 @@ fun Context.isLongMmsMessage(text: String): Boolean { } /** Not to be used with real messages persisted in the telephony db. This is for internal use only (e.g. scheduled messages). */ -fun generateRandomMessageId(length: Int = 8): Long { +fun generateRandomId(length: Int = 9): Long { val millis = DateTime.now(DateTimeZone.UTC).millis val random = abs(Random(millis).nextLong()) return random.toString().takeLast(length).toLong() diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/ConversationsDao.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/ConversationsDao.kt index 534cdc1f..5493eb00 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/ConversationsDao.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/ConversationsDao.kt @@ -14,6 +14,9 @@ interface ConversationsDao { @Query("SELECT * FROM conversations") fun getAll(): List + @Query("SELECT * FROM conversations where thread_id = :threadId") + fun getConversationWithThreadId(threadId: Long): Conversation? + @Query("SELECT * FROM conversations WHERE read = 0") fun getUnreadConversations(): List diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/MessagesDao.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/MessagesDao.kt index e9d6a3b2..7036167b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/MessagesDao.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/MessagesDao.kt @@ -23,10 +23,10 @@ interface MessagesDao { @Query("SELECT * FROM messages WHERE thread_id = :threadId") fun getThreadMessages(threadId: Long): List - @Query("SELECT * FROM messages WHERE thread_id = :threadId AND is_scheduled") + @Query("SELECT * FROM messages WHERE thread_id = :threadId AND is_scheduled = 1") fun getScheduledThreadMessages(threadId: Long): List - @Query("SELECT * FROM messages WHERE thread_id = :threadId AND id = :messageId AND is_scheduled") + @Query("SELECT * FROM messages WHERE thread_id = :threadId AND id = :messageId AND is_scheduled = 1") fun getScheduledMessageWithId(threadId: Long, messageId: Long): Message @Query("SELECT * FROM messages WHERE body LIKE :text") diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt index fc5698f4..dd4d5f4b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt @@ -14,5 +14,6 @@ data class Conversation( @ColumnInfo(name = "title") var title: String, @ColumnInfo(name = "photo_uri") var photoUri: String, @ColumnInfo(name = "is_group_conversation") var isGroupConversation: Boolean, - @ColumnInfo(name = "phone_number") var phoneNumber: String + @ColumnInfo(name = "phone_number") var phoneNumber: String, + @ColumnInfo(name = "is_scheduled") var isScheduled: Boolean = false ) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt index e3c41473..bc89a283 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt @@ -7,6 +7,8 @@ import android.os.PowerManager import com.simplemobiletools.commons.extensions.showErrorToast import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.smsmessenger.R +import com.simplemobiletools.smsmessenger.extensions.conversationsDB +import com.simplemobiletools.smsmessenger.extensions.deleteScheduledMessage import com.simplemobiletools.smsmessenger.extensions.getAddresses import com.simplemobiletools.smsmessenger.extensions.messagesDB import com.simplemobiletools.smsmessenger.helpers.SCHEDULED_MESSAGE_ID @@ -33,6 +35,7 @@ class ScheduledMessageReceiver : BroadcastReceiver() { val message = try { context.messagesDB.getScheduledMessageWithId(threadId, messageId) } catch (e: Exception) { + e.printStackTrace() return } @@ -41,7 +44,10 @@ class ScheduledMessageReceiver : BroadcastReceiver() { try { context.sendMessage(message.body, addresses, message.subscriptionId, attachments) - context.messagesDB.delete(messageId) + + // delete temporary conversation and message as it's already persisted to the telephony db now + context.deleteScheduledMessage(messageId) + context.conversationsDB.deleteThreadId(messageId) refreshMessages() } catch (e: Exception) { context.showErrorToast(e) From 76af74ef1cf519ba0a7152650475edbf43dd78cd Mon Sep 17 00:00:00 2001 From: Naveen Date: Wed, 28 Sep 2022 02:12:29 +0530 Subject: [PATCH 07/22] Minor readability improvement --- .../smsmessenger/interfaces/ConversationsDao.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/ConversationsDao.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/ConversationsDao.kt index 5493eb00..0c8db25c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/ConversationsDao.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/ConversationsDao.kt @@ -14,7 +14,7 @@ interface ConversationsDao { @Query("SELECT * FROM conversations") fun getAll(): List - @Query("SELECT * FROM conversations where thread_id = :threadId") + @Query("SELECT * FROM conversations WHERE thread_id = :threadId") fun getConversationWithThreadId(threadId: Long): Conversation? @Query("SELECT * FROM conversations WHERE read = 0") From 8aee6c560b3d99a8651a4f79c0ea89b6a6ef8b00 Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 1 Oct 2022 00:02:13 +0530 Subject: [PATCH 08/22] Delay clearing of expired messages --- .../com/simplemobiletools/smsmessenger/extensions/Context.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt index 03287e98..25ffe9f6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt @@ -1048,12 +1048,13 @@ fun Context.updateScheduledMessagesThreadId(messages: List, newThreadId fun Context.clearExpiredScheduledMessages(threadId: Long, messagesToDelete: List? = null) { val messages = messagesToDelete ?: messagesDB.getScheduledThreadMessages(threadId) + val now = System.currentTimeMillis() + 500L try { - messages.filter { it.isScheduled && it.millis() < System.currentTimeMillis() }.forEach { msg -> + messages.filter { it.isScheduled && it.millis() < now }.forEach { msg -> messagesDB.delete(msg.id) } - if (messages.filterNot { it.isScheduled && it.millis() < System.currentTimeMillis() }.isEmpty()) { + if (messages.filterNot { it.isScheduled && it.millis() < now }.isEmpty()) { // delete empty temporary thread val conversation = conversationsDB.getConversationWithThreadId(threadId) if (conversation != null && conversation.isScheduled) { From 8142489d87366aac726f6fcaf3cb695745aca740 Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 1 Oct 2022 01:13:13 +0530 Subject: [PATCH 09/22] Use a descriptive function name --- .../smsmessenger/dialogs/ScheduleSendDialog.kt | 4 ++-- .../com/simplemobiletools/smsmessenger/extensions/Math.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt index 38c8792d..71f6496c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt @@ -12,7 +12,7 @@ import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.TIME_FORMAT_12 import com.simplemobiletools.smsmessenger.R -import com.simplemobiletools.smsmessenger.extensions.round +import com.simplemobiletools.smsmessenger.extensions.roundToClosestMultipleOf import com.simplemobiletools.smsmessenger.helpers.DATE_FORMAT_PATTERN import kotlinx.android.synthetic.main.schedule_message_dialog.view.* import org.joda.time.DateTime @@ -157,5 +157,5 @@ class ScheduleSendDialog(private val activity: BaseSimpleActivity, private var d private fun getNextHour() = (calendar.get(Calendar.HOUR_OF_DAY) + 1).coerceIn(0, 23) - private fun getNextMinute() = (calendar.get(Calendar.MINUTE) + 5).round(5).coerceIn(0, 59) + private fun getNextMinute() = (calendar.get(Calendar.MINUTE) + 5).roundToClosestMultipleOf(5).coerceIn(0, 59) } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Math.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Math.kt index e3e61e2e..17462d10 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Math.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Math.kt @@ -3,6 +3,6 @@ package com.simplemobiletools.smsmessenger.extensions import kotlin.math.roundToInt /** - * Returns the closest next number divisible by [multipleOf]. + * Returns the closest number divisible by [multipleOf]. */ -fun Int.round(multipleOf: Int = 1) = (toDouble() / multipleOf).roundToInt() * multipleOf +fun Int.roundToClosestMultipleOf(multipleOf: Int = 1) = (toDouble() / multipleOf).roundToInt() * multipleOf From 3fa03b2998f3f5a7fa975ff788da4e2ffc94f717 Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 1 Oct 2022 01:22:46 +0530 Subject: [PATCH 10/22] Validate datetime before scheduling message --- .../smsmessenger/activities/ThreadActivity.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt index 9d72bf14..b44473b6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -956,6 +956,12 @@ class ThreadActivity : SimpleActivity() { } private fun sendScheduledMessage(text: String, subscriptionId: Int) { + if (scheduledDateTime.millis < System.currentTimeMillis() + 1000L) { + toast(R.string.must_pick_time_in_the_future) + launchScheduleSendDialog(scheduledDateTime) + return + } + refreshedSinceSent = false try { ensureBackgroundThread { From 9284859b8b92e5582ed02574149d32dc56009336 Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 1 Oct 2022 01:46:26 +0530 Subject: [PATCH 11/22] Get icon width from xml resource --- .../smsmessenger/adapters/ThreadAdapter.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt index 4685b4cd..cace2918 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt @@ -335,10 +335,9 @@ class ThreadAdapter( thread_message_scheduled_icon.beVisible() thread_message_scheduled_icon.applyColorFilter(contrastColor) - thread_message_scheduled_icon.onGlobalLayout { - val rightPadding = padding + thread_message_scheduled_icon.measuredWidth - thread_message_body.setPadding(padding, padding, rightPadding, padding) - } + val iconWidth = resources.getDimensionPixelSize(R.dimen.small_icon_size) + val rightPadding = padding + iconWidth + thread_message_body.setPadding(padding, padding, rightPadding, padding) thread_message_body.typeface = Typeface.create(Typeface.DEFAULT, Typeface.ITALIC) } else { thread_message_scheduled_icon.beGone() From 5bfd4c5b3be3d822cf025bf372d12e9909730c71 Mon Sep 17 00:00:00 2001 From: Naveen Date: Mon, 3 Oct 2022 23:29:36 +0530 Subject: [PATCH 12/22] Use padding with clipToPadding set to false --- app/src/main/res/layout/activity_thread.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/activity_thread.xml b/app/src/main/res/layout/activity_thread.xml index 4f60e8ac..4e3baf2c 100644 --- a/app/src/main/res/layout/activity_thread.xml +++ b/app/src/main/res/layout/activity_thread.xml @@ -115,9 +115,9 @@ android:id="@+id/thread_messages_list" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginBottom="@dimen/small_margin" android:clipToPadding="false" android:overScrollMode="ifContentScrolls" + android:paddingBottom="@dimen/small_margin" android:scrollbars="none" app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager" app:stackFromEnd="true" From d6f71619cf0d5da867f70a431bf2940e4ac9f8e0 Mon Sep 17 00:00:00 2001 From: Naveen Date: Tue, 4 Oct 2022 01:10:20 +0530 Subject: [PATCH 13/22] Add options to edit, delete, and send the message now --- .../smsmessenger/activities/ThreadActivity.kt | 48 ++++++++++++++----- .../smsmessenger/extensions/Context.kt | 1 + .../smsmessenger/helpers/Messaging.kt | 19 +++++++- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt index b44473b6..cf92dbab 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -75,6 +75,10 @@ class ThreadActivity : SimpleActivity() { private val TYPE_TAKE_PHOTO = 12 private val TYPE_CHOOSE_PHOTO = 13 + private val TYPE_EDIT = 14 + private val TYPE_SEND = 15 + private val TYPE_DELETE = 16 + private var threadId = 0L private var currentSIMCardIndex = 0 private var isActivityVisible = false @@ -977,6 +981,7 @@ class ThreadActivity : SimpleActivity() { } clearCurrentMessage() hideScheduleSendUi() + scheduledMessage = null if (!refreshedSinceSent) { refreshMessages() @@ -1213,26 +1218,49 @@ class ThreadActivity : SimpleActivity() { } private fun showScheduledMessageInfo(message: Message) { - // todo: maybe show options to edit, delete, and send the message now - editScheduledMessage(message) + val items = arrayListOf( + RadioItem(TYPE_EDIT, getString(R.string.update_message)), + RadioItem(TYPE_SEND, getString(R.string.send_now)), + RadioItem(TYPE_DELETE, getString(R.string.delete)) + ) + RadioGroupDialog(this, items) { + when (it as Int) { + TYPE_DELETE -> cancelScheduledMessageAndRefresh(message.id) + TYPE_EDIT -> editScheduledMessage(message) + TYPE_SEND -> { + extractAttachments(message) + sendNormalMessage(message.body, message.subscriptionId) + cancelScheduledMessageAndRefresh(message.id) + } + } + } } - private fun editScheduledMessage(message: Message) { - scheduledMessage = message - clearCurrentMessage() - thread_type_message.setText(message.body) - + private fun extractAttachments(message: Message) { val messageAttachment = message.attachment if (messageAttachment != null) { for (attachment in messageAttachment.attachments) { addAttachment(attachment.getUri()) } } + } + private fun editScheduledMessage(message: Message) { + scheduledMessage = message + clearCurrentMessage() + thread_type_message.setText(message.body) + extractAttachments(message) scheduledDateTime = DateTime(message.millis()) showScheduleSendUi() } + private fun cancelScheduledMessageAndRefresh(messageId: Long) { + ensureBackgroundThread { + deleteScheduledMessage(messageId) + refreshMessages() + } + } + private fun launchScheduleSendDialog(originalDt: DateTime? = null) { ScheduleSendDialog(this, originalDt) { newDt -> if (newDt != null) { @@ -1259,10 +1287,8 @@ class ThreadActivity : SimpleActivity() { setOnClickListener { hideScheduleSendUi() if (scheduledMessage != null) { - ensureBackgroundThread { - deleteScheduledMessage(scheduledMessage!!.id) - refreshMessages() - } + cancelScheduledMessageAndRefresh(scheduledMessage!!.id) + scheduledMessage = null } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt index 25ffe9f6..b95f2af1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt @@ -609,6 +609,7 @@ fun Context.deleteMessage(id: Long, isMMS: Boolean) { fun Context.deleteScheduledMessage(messageId: Long) { try { messagesDB.delete(messageId) + cancelScheduleSendPendingIntent(messageId) } catch (e: Exception) { showErrorToast(e) } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt index b893fc11..fb048c6c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt @@ -67,7 +67,7 @@ fun Context.sendMessage(text: String, addresses: List, subscriptionId: I } } -fun Context.scheduleMessage(message: Message) { +fun Context.getScheduleSendPendingIntent(message: Message): PendingIntent { val intent = Intent(this, ScheduledMessageReceiver::class.java) intent.putExtra(THREAD_ID, message.threadId) intent.putExtra(SCHEDULED_MESSAGE_ID, message.id) @@ -76,13 +76,28 @@ fun Context.scheduleMessage(message: Message) { if (isMarshmallowPlus()) { flags = flags or PendingIntent.FLAG_IMMUTABLE } - val pendingIntent = PendingIntent.getBroadcast(this, message.id.toInt(), intent, flags) + + return PendingIntent.getBroadcast(this, message.id.toInt(), intent, flags) +} + +fun Context.scheduleMessage(message: Message) { + val pendingIntent = getScheduleSendPendingIntent(message) val triggerAtMillis = message.millis() val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent) } +fun Context.cancelScheduleSendPendingIntent(messageId: Long) { + val intent = Intent(this, ScheduledMessageReceiver::class.java) + var flags = PendingIntent.FLAG_UPDATE_CURRENT + if (isMarshmallowPlus()) { + flags = flags or PendingIntent.FLAG_IMMUTABLE + } + + PendingIntent.getBroadcast(this, messageId.toInt(), intent, flags).cancel() +} + fun Context.isLongMmsMessage(text: String): Boolean { val settings = getSendMessageSettings() return Utils.getNumPages(settings, text) > settings.sendLongAsMmsAfter From e6fa4928776a3b35558d10432c0cb457a83ba934 Mon Sep 17 00:00:00 2001 From: Naveen Date: Tue, 4 Oct 2022 01:30:08 +0530 Subject: [PATCH 14/22] Minor readability improvement --- .../smsmessenger/activities/ThreadActivity.kt | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt index cf92dbab..7fd948a0 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -1034,16 +1034,16 @@ class ThreadActivity : SimpleActivity() { var isFirstRow = true for (i in views.indices) { - val LL = LinearLayout(this) - LL.orientation = LinearLayout.HORIZONTAL - LL.gravity = Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM - LL.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) + val layout = LinearLayout(this) + layout.orientation = LinearLayout.HORIZONTAL + layout.gravity = Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM + layout.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) views[i].measure(0, 0) var params = LayoutParams(views[i].measuredWidth, LayoutParams.WRAP_CONTENT) params.setMargins(0, 0, mediumMargin, 0) - LL.addView(views[i], params) - LL.measure(0, 0) + layout.addView(views[i], params) + layout.measure(0, 0) widthSoFar += views[i].measuredWidth + mediumMargin val checkWidth = if (isFirstRow) firstRowWidth else parentWidth @@ -1053,15 +1053,15 @@ class ThreadActivity : SimpleActivity() { newLinearLayout = LinearLayout(this) newLinearLayout.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) newLinearLayout.orientation = LinearLayout.HORIZONTAL - params = LayoutParams(LL.measuredWidth, LL.measuredHeight) + params = LayoutParams(layout.measuredWidth, layout.measuredHeight) params.topMargin = mediumMargin - newLinearLayout.addView(LL, params) - widthSoFar = LL.measuredWidth + newLinearLayout.addView(layout, params) + widthSoFar = layout.measuredWidth } else { if (!isFirstRow) { - (LL.layoutParams as LayoutParams).topMargin = mediumMargin + (layout.layoutParams as LayoutParams).topMargin = mediumMargin } - newLinearLayout.addView(LL) + newLinearLayout.addView(layout) } } selected_contacts.addView(newLinearLayout) From 338b559823259fc319f73f550a3793193a258e94 Mon Sep 17 00:00:00 2001 From: Naveen Date: Tue, 4 Oct 2022 01:40:50 +0530 Subject: [PATCH 15/22] Add dialog title --- .../simplemobiletools/smsmessenger/activities/ThreadActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt index 7fd948a0..004ef7be 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -1223,7 +1223,7 @@ class ThreadActivity : SimpleActivity() { RadioItem(TYPE_SEND, getString(R.string.send_now)), RadioItem(TYPE_DELETE, getString(R.string.delete)) ) - RadioGroupDialog(this, items) { + RadioGroupDialog(activity = this, items = items, titleId = R.string.scheduled_message) { when (it as Int) { TYPE_DELETE -> cancelScheduledMessageAndRefresh(message.id) TYPE_EDIT -> editScheduledMessage(message) From 203f10618fd768d25a78c385df1dc0ebca144f63 Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 8 Oct 2022 20:50:49 +0530 Subject: [PATCH 16/22] Prefer last modified time over last message time --- .../smsmessenger/activities/MainActivity.kt | 23 ++++++++----------- .../smsmessenger/activities/ThreadActivity.kt | 6 +++++ .../smsmessenger/extensions/Context.kt | 1 - .../smsmessenger/models/Conversation.kt | 13 ++++++++++- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt index 7047ec34..661213a4 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt @@ -36,7 +36,6 @@ import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import java.io.FileOutputStream import java.io.OutputStream -import java.util.* class MainActivity : SimpleActivity() { private val MAKE_DEFAULT_APP_REQUEST = 1 @@ -220,14 +219,6 @@ class MainActivity : SimpleActivity() { val privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor) val conversations = getConversations(privateContacts = privateContacts) - val scheduledConversations = cachedConversations.filter { it.isScheduled } - val allConversations = conversations.toArrayList().apply { - addAll(scheduledConversations) - } - runOnUiThread { - setupConversations(allConversations) - } - conversations.forEach { clonedConversation -> val threadIds = cachedConversations.map { it.threadId } if (!threadIds.contains(clonedConversation.threadId)) { @@ -256,13 +247,19 @@ class MainActivity : SimpleActivity() { } } - cachedConversations.forEach { cachedConversation -> - val conv = conversations.firstOrNull { it.threadId == cachedConversation.threadId && it.toString() != cachedConversation.toString() } + cachedConversations.forEach { cachedConv -> + val conv = conversations.find { it.threadId == cachedConv.threadId && !cachedConv.areContentsTheSame(it) } if (conv != null) { - conversationsDB.insertOrUpdate(conv) + val conversation = conv.copy(date = maxOf(cachedConv.date, conv.date)) + conversationsDB.insertOrUpdate(conversation) } } + val allConversations = conversationsDB.getAll() as ArrayList + runOnUiThread { + setupConversations(allConversations) + } + if (config.appRunCount == 1) { conversations.map { it.threadId }.forEach { threadId -> val messages = getMessages(threadId, getImageResolutions = false, includeScheduledMessages = false) @@ -336,7 +333,7 @@ class MainActivity : SimpleActivity() { val manager = getSystemService(ShortcutManager::class.java) try { - manager.dynamicShortcuts = Arrays.asList(newConversation) + manager.dynamicShortcuts = listOf(newConversation) config.lastHandledShortcutColor = appIconColor } catch (ignored: Exception) { } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt index 004ef7be..72f52dd6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -977,6 +977,11 @@ class ThreadActivity : SimpleActivity() { createTemporaryThread(message, message.threadId) } messagesDB.insertOrUpdate(message) + val conversation = conversationsDB.getConversationWithThreadId(threadId) + if (conversation != null) { + val nowSeconds = (System.currentTimeMillis() / 1000).toInt() + conversationsDB.insertOrUpdate(conversation.copy(date = nowSeconds)) + } scheduleMessage(message) } clearCurrentMessage() @@ -1257,6 +1262,7 @@ class ThreadActivity : SimpleActivity() { private fun cancelScheduledMessageAndRefresh(messageId: Long) { ensureBackgroundThread { deleteScheduledMessage(messageId) + cancelScheduleSendPendingIntent(messageId) refreshMessages() } } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt index b95f2af1..25ffe9f6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt @@ -609,7 +609,6 @@ fun Context.deleteMessage(id: Long, isMMS: Boolean) { fun Context.deleteScheduledMessage(messageId: Long) { try { messagesDB.delete(messageId) - cancelScheduleSendPendingIntent(messageId) } catch (e: Exception) { showErrorToast(e) } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt index dd4d5f4b..86d09777 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt @@ -16,4 +16,15 @@ data class Conversation( @ColumnInfo(name = "is_group_conversation") var isGroupConversation: Boolean, @ColumnInfo(name = "phone_number") var phoneNumber: String, @ColumnInfo(name = "is_scheduled") var isScheduled: Boolean = false -) +) { + + fun areContentsTheSame(other: Conversation): Boolean { + return snippet == other.snippet && + date == other.date && + read == other.read && + title == other.title && + photoUri == other.photoUri && + isGroupConversation == other.isGroupConversation && + phoneNumber == other.phoneNumber + } +} From f72175478534bc5f7b63fd8e35b4c7490c293e73 Mon Sep 17 00:00:00 2001 From: Naveen Date: Fri, 14 Oct 2022 00:37:03 +0530 Subject: [PATCH 17/22] Remove humanised datetime formatting Year is not shown if it's same as the current year. --- .../smsmessenger/activities/ThreadActivity.kt | 15 ++++++++++++++- .../smsmessenger/extensions/Date.kt | 11 ----------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt index 72f52dd6..67863f06 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -19,6 +19,10 @@ import android.telephony.SmsManager import android.telephony.SmsMessage import android.telephony.SubscriptionInfo import android.text.TextUtils +import android.text.format.DateUtils +import android.text.format.DateUtils.FORMAT_NO_YEAR +import android.text.format.DateUtils.FORMAT_SHOW_DATE +import android.text.format.DateUtils.FORMAT_SHOW_TIME import android.util.TypedValue import android.view.Gravity import android.view.View @@ -66,6 +70,7 @@ import java.io.File import java.io.InputStream import java.io.OutputStream + class ThreadActivity : SimpleActivity() { private val MIN_DATE_TIME_DIFF_SECS = 300 private val PICK_ATTACHMENT_INTENT = 1 @@ -1304,7 +1309,15 @@ class ThreadActivity : SimpleActivity() { isScheduledMessage = true updateSendButtonDrawable() scheduled_message_holder.beVisible() - scheduled_message_button.text = scheduledDateTime.humanize(this) + + val dt = scheduledDateTime + val millis = dt.millis + scheduled_message_button.text = if (dt.yearOfCentury().get() > DateTime.now().yearOfCentury().get()) { + millis.formatDate(this) + } else { + val flags = FORMAT_SHOW_TIME or FORMAT_SHOW_DATE or FORMAT_NO_YEAR + DateUtils.formatDateTime(this, millis, flags) + } } private fun hideScheduleSendUi() { diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Date.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Date.kt index 50989a6e..7cde1a5b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Date.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Date.kt @@ -1,19 +1,8 @@ package com.simplemobiletools.smsmessenger.extensions -import android.content.Context import android.text.format.DateFormat -import android.text.format.DateUtils -import org.joda.time.DateTime import java.util.* fun Date.format(pattern: String): String { return DateFormat.format(pattern, this).toString() } - -fun DateTime.humanize(context: Context, now: DateTime = DateTime.now(), pattern: String = "EEE, MMM dd, YYYY, hh:mm a"): String { - return if (yearOfCentury().get() > now.yearOfCentury().get()) { - toString(pattern) - } else { - DateUtils.getRelativeDateTimeString(context, millis, DateUtils.SECOND_IN_MILLIS, DateUtils.DAY_IN_MILLIS, 0).toString() - } -} From 5edb0b099b73ea90a04c6df84d946bdf63cc7047 Mon Sep 17 00:00:00 2001 From: Naveen Date: Fri, 14 Oct 2022 02:01:10 +0530 Subject: [PATCH 18/22] Reduce datetime font size --- .../dialogs/ScheduleSendDialog.kt | 4 ++-- .../res/layout/schedule_message_dialog.xml | 20 +++++++++---------- app/src/main/res/values/dimens.xml | 3 ++- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt index 71f6496c..f3e27921 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt @@ -44,9 +44,9 @@ class ScheduleSendDialog(private val activity: BaseSimpleActivity, private var d private fun updateTexts(dt: DateTime) { val dateText = dt.toString(DATE_FORMAT_PATTERN).toSpannable() - dateText.setSpan(RelativeSizeSpan(0.6f), 2, dateText.length, 0) + dateText.setSpan(RelativeSizeSpan(0.85f), 2, dateText.length, 0) val timeText = dt.toString(TIME_FORMAT_12).toSpannable() - timeText.setSpan(RelativeSizeSpan(0.6f), timeText.lastIndex - 2, timeText.length, 0) + timeText.setSpan(RelativeSizeSpan(0.85f), timeText.lastIndex - 2, timeText.length, 0) view.edit_date.text = dateText view.edit_time.text = timeText diff --git a/app/src/main/res/layout/schedule_message_dialog.xml b/app/src/main/res/layout/schedule_message_dialog.xml index 2bffb768..cdf480f3 100644 --- a/app/src/main/res/layout/schedule_message_dialog.xml +++ b/app/src/main/res/layout/schedule_message_dialog.xml @@ -22,10 +22,10 @@ @@ -34,7 +34,7 @@ android:id="@+id/edit_date" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="@dimen/tiny_margin" + android:layout_marginStart="@dimen/medium_margin" android:background="?attr/selectableItemBackground" android:ellipsize="end" android:includeFontPadding="false" @@ -47,10 +47,10 @@ @@ -59,7 +59,7 @@ android:id="@+id/edit_time" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="@dimen/tiny_margin" + android:layout_marginStart="@dimen/medium_margin" android:background="?attr/selectableItemBackground" android:ellipsize="end" android:includeFontPadding="false" diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index d6f8e492..981b6b02 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -7,6 +7,7 @@ 24dp 15dp 64dp - 36sp + 20dp + 22sp 20dp From 8c67dfa929efb06778314b7458c84f6c785d7623 Mon Sep 17 00:00:00 2001 From: Naveen Date: Fri, 14 Oct 2022 22:14:13 +0530 Subject: [PATCH 19/22] Use consistent font size in date time texts --- .../smsmessenger/dialogs/ScheduleSendDialog.kt | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt index f3e27921..dc318b0c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt @@ -5,9 +5,7 @@ import android.app.DatePickerDialog.OnDateSetListener import android.app.TimePickerDialog import android.app.TimePickerDialog.OnTimeSetListener import android.text.format.DateFormat -import android.text.style.RelativeSizeSpan import androidx.appcompat.app.AlertDialog -import androidx.core.text.toSpannable import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.TIME_FORMAT_12 @@ -43,13 +41,8 @@ class ScheduleSendDialog(private val activity: BaseSimpleActivity, private var d } private fun updateTexts(dt: DateTime) { - val dateText = dt.toString(DATE_FORMAT_PATTERN).toSpannable() - dateText.setSpan(RelativeSizeSpan(0.85f), 2, dateText.length, 0) - val timeText = dt.toString(TIME_FORMAT_12).toSpannable() - timeText.setSpan(RelativeSizeSpan(0.85f), timeText.lastIndex - 2, timeText.length, 0) - - view.edit_date.text = dateText - view.edit_time.text = timeText + view.edit_date.text = dt.toString(DATE_FORMAT_PATTERN) + view.edit_time.text = dt.toString(TIME_FORMAT_12) } private fun showPreview() { From 69e3cfd3b7ea93e0e21f13554421b902b9519216 Mon Sep 17 00:00:00 2001 From: Naveen Date: Fri, 14 Oct 2022 22:19:31 +0530 Subject: [PATCH 20/22] Remove double empty lines --- .../simplemobiletools/smsmessenger/activities/ThreadActivity.kt | 1 - .../smsmessenger/extensions/gson/JsonElement.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt index 67863f06..1b5ebe1d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -70,7 +70,6 @@ import java.io.File import java.io.InputStream import java.io.OutputStream - class ThreadActivity : SimpleActivity() { private val MIN_DATE_TIME_DIFF_SECS = 300 private val PICK_ATTACHMENT_INTENT = 1 diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/gson/JsonElement.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/gson/JsonElement.kt index 876d6353..2b2287fd 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/gson/JsonElement.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/gson/JsonElement.kt @@ -4,7 +4,6 @@ import com.google.gson.* import java.math.BigDecimal import java.math.BigInteger - val JsonElement.optString: String? get() = safeConversion { asString } From 45c7dd77a85dc49a3cd751b1b76f3beab1500585 Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 15 Oct 2022 00:48:57 +0530 Subject: [PATCH 21/22] Use datetime format as per the settings --- .../smsmessenger/dialogs/ScheduleSendDialog.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt index dc318b0c..ddcf9506 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ScheduleSendDialog.kt @@ -8,10 +8,9 @@ import android.text.format.DateFormat import androidx.appcompat.app.AlertDialog import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.extensions.* -import com.simplemobiletools.commons.helpers.TIME_FORMAT_12 import com.simplemobiletools.smsmessenger.R +import com.simplemobiletools.smsmessenger.extensions.config import com.simplemobiletools.smsmessenger.extensions.roundToClosestMultipleOf -import com.simplemobiletools.smsmessenger.helpers.DATE_FORMAT_PATTERN import kotlinx.android.synthetic.main.schedule_message_dialog.view.* import org.joda.time.DateTime import java.util.* @@ -41,8 +40,10 @@ class ScheduleSendDialog(private val activity: BaseSimpleActivity, private var d } private fun updateTexts(dt: DateTime) { - view.edit_date.text = dt.toString(DATE_FORMAT_PATTERN) - view.edit_time.text = dt.toString(TIME_FORMAT_12) + val dateFormat = activity.config.dateFormat + val timeFormat = activity.getTimeFormat() + view.edit_date.text = dt.toString(dateFormat) + view.edit_time.text = dt.toString(timeFormat) } private fun showPreview() { From a0866d28a886d893845d707da0f615a2095f4d1f Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 15 Oct 2022 00:55:49 +0530 Subject: [PATCH 22/22] Expand date time text to match parent width --- app/src/main/res/layout/schedule_message_dialog.xml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/schedule_message_dialog.xml b/app/src/main/res/layout/schedule_message_dialog.xml index cdf480f3..67d7cf14 100644 --- a/app/src/main/res/layout/schedule_message_dialog.xml +++ b/app/src/main/res/layout/schedule_message_dialog.xml @@ -32,15 +32,17 @@ @@ -57,15 +59,17 @@