diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dbd42a6..0d621cc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,28 @@ Changelog ========== +Version 5.1.1 *(2020-05-08)* +---------------------------- + + * Allow moving the app on an SD card + * Added some performance, UI and stability improvements + +Version 5.1.0 *(2020-05-03)* +---------------------------- + + * Adding multi-SIM support + * Properly show the latest messages, not the oldest ones in some cases + * Increased minimal OS version to Android 5.1 + * Fixed many other UI and UX related issues + +Version 5.0.1 *(2020-04-19)* +---------------------------- + + * Properly handle incoming multipart SMS messages + * Fixed a couple coloring issues + * Do not allow attempts to block a number below Android 7 + * A couple other translation, stability and UI improvements + Version 5.0.0 *(2020-04-14)* ---------------------------- diff --git a/app/build.gradle b/app/build.gradle index bcdd855a..8490e2a1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,10 +14,10 @@ android { defaultConfig { applicationId "com.simplemobiletools.smsmessenger" - minSdkVersion 21 + minSdkVersion 22 targetSdkVersion 29 - versionCode 1 - versionName "5.0.0" + versionCode 4 + versionName "5.1.1" setProperty("archivesBaseName", "sms-messenger") } @@ -56,8 +56,7 @@ android { } dependencies { - implementation 'com.simplemobiletools:commons:5.25.19' - implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4' + implementation 'com.simplemobiletools:commons:5.27.24' implementation 'org.greenrobot:eventbus:3.2.0' implementation 'com.klinkerapps:android-smsmms:5.2.6' implementation 'com.github.tibbi:IndicatorFastScroll:08f512858a' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index f1b42451..e6c59860 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,21 +1,6 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile +# EventBus +-keepattributes *Annotation* +-keepclassmembers class ** { + @org.greenrobot.eventbus.Subscribe ; +} +-keep enum org.greenrobot.eventbus.ThreadMode { *; } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index aa47cb62..c2f17766 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ + package="com.simplemobiletools.smsmessenger" + android:installLocation="auto"> @@ -10,6 +11,7 @@ + () + private var allContacts = ArrayList() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -42,6 +47,11 @@ class NewConversationActivity : SimpleActivity() { no_contacts_placeholder_2.underlineText() } + override fun onCreateOptionsMenu(menu: Menu): Boolean { + updateMenuItemColors(menu) + return super.onCreateOptionsMenu(menu) + } + private fun initContacts() { if (isThirdPartyIntent()) { return @@ -50,7 +60,7 @@ class NewConversationActivity : SimpleActivity() { fetchContacts() new_conversation_address.onTextChangeListener { val searchString = it - val filteredContacts = ArrayList() + val filteredContacts = ArrayList() allContacts.forEach { if (it.phoneNumber.contains(searchString, true) || it.name.contains(searchString, true)) { filteredContacts.add(it) @@ -85,7 +95,7 @@ class NewConversationActivity : SimpleActivity() { private fun isThirdPartyIntent(): Boolean { if (intent.action == Intent.ACTION_SENDTO && intent.dataString != null) { val number = intent.dataString!!.removePrefix("sms:").removePrefix("smsto:").removePrefix("mms").removePrefix("mmsto:").trim() - launchThreadActivity(number, "") + launchThreadActivity(URLDecoder.decode(number), "") return true } return false @@ -93,7 +103,7 @@ class NewConversationActivity : SimpleActivity() { private fun fetchContacts() { fillSuggestedContacts { - getAvailableContacts { + SimpleContactsHelper(this).getAvailableContacts { allContacts = it runOnUiThread { @@ -103,7 +113,7 @@ class NewConversationActivity : SimpleActivity() { } } - private fun setupAdapter(contacts: ArrayList) { + private fun setupAdapter(contacts: ArrayList) { val hasContacts = contacts.isNotEmpty() contacts_list.beVisibleIf(hasContacts) no_contacts_placeholder.beVisibleIf(!hasContacts) @@ -116,7 +126,7 @@ class NewConversationActivity : SimpleActivity() { ContactsAdapter(this, contacts, contacts_list, null) { hideKeyboard() - launchThreadActivity((it as Contact).phoneNumber, it.name) + launchThreadActivity((it as SimpleContact).phoneNumber, it.name) }.apply { contacts_list.adapter = this } @@ -139,7 +149,7 @@ class NewConversationActivity : SimpleActivity() { val contact = it layoutInflater.inflate(R.layout.item_suggested_contact, null).apply { suggested_contact_name.text = contact.name - loadImage(contact.photoUri, suggested_contact_image, contact.name) + SimpleContactsHelper(this@NewConversationActivity).loadContactImage(contact.photoUri, suggested_contact_image, contact.name) suggestions_holder.addView(this) setOnClickListener { launchThreadActivity(contact.phoneNumber, contact.name) @@ -152,15 +162,11 @@ class NewConversationActivity : SimpleActivity() { } } - private fun setupLetterFastscroller(contacts: ArrayList) { + private fun setupLetterFastscroller(contacts: ArrayList) { contacts_letter_fastscroller.setupWithRecyclerView(contacts_list, { position -> try { val name = contacts[position].name - var character = if (name.isNotEmpty()) name.substring(0, 1) else "" - if (!character.areLettersOnly()) { - character = "#" - } - + val character = if (name.isNotEmpty()) name.substring(0, 1) else "" FastScrollItemIndicator.Text(character.toUpperCase(Locale.getDefault())) } catch (e: Exception) { FastScrollItemIndicator.Text("") diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/SettingsActivity.kt index 989aa0f9..885e3f69 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/SettingsActivity.kt @@ -4,6 +4,7 @@ import android.annotation.TargetApi import android.content.Intent import android.os.Build import android.os.Bundle +import android.view.Menu import com.simplemobiletools.commons.activities.ManageBlockedNumbersActivity import com.simplemobiletools.commons.dialogs.ChangeDateTimeFormatDialog import com.simplemobiletools.commons.extensions.* @@ -15,7 +16,7 @@ import kotlinx.android.synthetic.main.activity_settings.* import java.util.* class SettingsActivity : SimpleActivity() { - var blockedNumbersAtPause = -1 + private var blockedNumbersAtPause = -1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -42,6 +43,11 @@ class SettingsActivity : SimpleActivity() { blockedNumbersAtPause = getBlockedNumbers().hashCode() } + override fun onCreateOptionsMenu(menu: Menu): Boolean { + updateMenuItemColors(menu) + return super.onCreateOptionsMenu(menu) + } + private fun setupPurchaseThankYou() { settings_purchase_thank_you_holder.beVisibleIf(!isThankYouInstalled()) settings_purchase_thank_you_holder.setOnClickListener { 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 e6572c9e..163efec9 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -1,5 +1,6 @@ package com.simplemobiletools.smsmessenger.activities +import android.annotation.SuppressLint import android.app.Activity import android.content.Intent import android.graphics.BitmapFactory @@ -8,6 +9,7 @@ import android.media.MediaMetadataRetriever import android.net.Uri import android.os.Bundle import android.provider.Telephony +import android.telephony.SubscriptionManager import android.text.TextUtils import android.view.* import android.view.inputmethod.EditorInfo @@ -28,7 +30,11 @@ import com.klinker.android.send_message.Settings import com.klinker.android.send_message.Transaction import com.simplemobiletools.commons.dialogs.ConfirmationDialog import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.PERMISSION_READ_PHONE_STATE +import com.simplemobiletools.commons.helpers.SimpleContactsHelper import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import com.simplemobiletools.commons.helpers.isNougatPlus +import com.simplemobiletools.commons.models.SimpleContact import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.adapters.AutoCompleteTextViewAdapter import com.simplemobiletools.smsmessenger.adapters.ThreadAdapter @@ -47,10 +53,12 @@ class ThreadActivity : SimpleActivity() { private val PICK_ATTACHMENT_INTENT = 1 private var threadId = 0 + private var currentSIMCardIndex = 0 private var threadItems = ArrayList() private var bus: EventBus? = null - private var participants = ArrayList() + private var participants = ArrayList() private var messages = ArrayList() + private val availableSIMCards = ArrayList() private var attachmentUris = LinkedHashSet() override fun onCreate(savedInstanceState: Bundle?) { @@ -71,7 +79,16 @@ class ThreadActivity : SimpleActivity() { bus = EventBus.getDefault() bus!!.register(this) + handlePermission(PERMISSION_READ_PHONE_STATE) { + if (it) { + setupThread() + } else { + finish() + } + } + } + private fun setupThread() { ensureBackgroundThread { messages = getMessages(threadId) participants = if (messages.isEmpty()) { @@ -89,7 +106,7 @@ class ThreadActivity : SimpleActivity() { return@ensureBackgroundThread } - val contact = Contact(0, name, "", number) + val contact = SimpleContact(0, 0, name, "", number) participants.add(contact) } @@ -132,6 +149,8 @@ class ThreadActivity : SimpleActivity() { window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) thread_type_message.requestFocus() } + + setupSIMSelector() } } setupButtons() @@ -146,8 +165,10 @@ class ThreadActivity : SimpleActivity() { menuInflater.inflate(R.menu.menu_thread, menu) menu.apply { findItem(R.id.delete).isVisible = threadItems.isNotEmpty() + findItem(R.id.block_number).isVisible = isNougatPlus() } + updateMenuItemColors(menu) return true } @@ -181,7 +202,7 @@ class ThreadActivity : SimpleActivity() { thread_messages_list.adapter = adapter } - getAvailableContacts { + SimpleContactsHelper(this).getAvailableContacts { runOnUiThread { val adapter = AutoCompleteTextViewAdapter(this, it) add_contact_or_number.setAdapter(adapter) @@ -200,16 +221,17 @@ class ThreadActivity : SimpleActivity() { confirm_inserted_number.setOnClickListener { val number = add_contact_or_number.value - val contact = Contact(number.hashCode(), number, "", number) + val contact = SimpleContact(number.hashCode(), number.hashCode(), number, "", number) addSelectedContact(contact) } } private fun setupButtons() { updateTextColors(thread_holder) - thread_send_message.applyColorFilter(config.textColor) - confirm_manage_contacts.applyColorFilter(config.textColor) - thread_add_attachment.applyColorFilter(config.textColor) + val textColor = config.textColor + thread_send_message.applyColorFilter(textColor) + confirm_manage_contacts.applyColorFilter(textColor) + thread_add_attachment.applyColorFilter(textColor) thread_send_message.setOnClickListener { sendMessage() @@ -250,6 +272,40 @@ class ThreadActivity : SimpleActivity() { } } + @SuppressLint("MissingPermission") + private fun setupSIMSelector() { + val availableSIMs = SubscriptionManager.from(this).activeSubscriptionInfoList + if (availableSIMs.size > 1) { + availableSIMs.forEachIndexed { index, subscriptionInfo -> + var label = subscriptionInfo.displayName.toString() + if (subscriptionInfo.number?.isNotEmpty() == true) { + label += " (${subscriptionInfo.number})" + } + val SIMCard = SIMCard(index + 1, subscriptionInfo.subscriptionId, label) + availableSIMCards.add(SIMCard) + } + + val numbers = participants.map { it.phoneNumber }.toTypedArray() + currentSIMCardIndex = availableSIMs.indexOfFirstOrNull { it.subscriptionId == config.getUseSIMIdAtNumber(numbers.first()) } ?: 0 + + thread_select_sim_icon.applyColorFilter(config.textColor) + thread_select_sim_icon.beVisible() + thread_select_sim_number.beVisible() + + if (availableSIMCards.isNotEmpty()) { + thread_select_sim_icon.setOnClickListener { + currentSIMCardIndex = (currentSIMCardIndex + 1) % availableSIMCards.size + val currentSIMCard = availableSIMCards[currentSIMCardIndex] + thread_select_sim_number.text = currentSIMCard.id.toString() + toast(currentSIMCard.label) + } + } + + thread_select_sim_number.setTextColor(config.textColor.getContrastColor()) + thread_select_sim_number.text = (availableSIMCards[currentSIMCardIndex].id).toString() + } + } + private fun blockNumber() { val baseString = R.string.block_confirmation val numbers = participants.map { it.phoneNumber }.toTypedArray() @@ -294,8 +350,8 @@ class ThreadActivity : SimpleActivity() { layoutInflater.inflate(R.layout.item_selected_contact, null).apply { selected_contact_name.text = contact.name selected_contact_remove.setOnClickListener { - if (contact.id != participants.first().id) { - removeSelectedContact(contact.id) + if (contact.rawId != participants.first().rawId) { + removeSelectedContact(contact.rawId) } } views.add(this) @@ -304,9 +360,9 @@ class ThreadActivity : SimpleActivity() { showSelectedContact(views) } - private fun addSelectedContact(contact: Contact) { + private fun addSelectedContact(contact: SimpleContact) { add_contact_or_number.setText("") - if (participants.map { it.id }.contains(contact.id)) { + if (participants.map { it.rawId }.contains(contact.rawId)) { return } @@ -419,6 +475,15 @@ class ThreadActivity : SimpleActivity() { val numbers = participants.map { it.phoneNumber }.toTypedArray() val settings = Settings() settings.useSystemSending = true + + val SIMId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId + if (SIMId != null) { + settings.subscriptionId = SIMId + numbers.forEach { + config.saveUseSIMIdAtNumber(it, SIMId) + } + } + val transaction = Transaction(this, settings) val message = com.klinker.android.send_message.Message(msg, numbers) @@ -430,12 +495,16 @@ class ThreadActivity : SimpleActivity() { } } - transaction.sendNewMessage(message, threadId.toLong()) + try { + transaction.sendNewMessage(message, threadId.toLong()) - thread_type_message.setText("") - attachmentUris.clear() - thread_attachments_holder.beGone() - thread_attachments_wrapper.removeAllViews() + thread_type_message.setText("") + attachmentUris.clear() + thread_attachments_holder.beGone() + thread_attachments_wrapper.removeAllViews() + } catch (e: Exception) { + showErrorToast(e) + } } // show selected contacts, properly split to new lines when appropriate @@ -488,7 +557,7 @@ class ThreadActivity : SimpleActivity() { } private fun removeSelectedContact(id: Int) { - participants = participants.filter { it.id != id }.toMutableList() as ArrayList + participants = participants.filter { it.rawId != id }.toMutableList() as ArrayList showSelectedContacts() } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/AutoCompleteTextViewAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/AutoCompleteTextViewAdapter.kt index ff02fbc8..75611076 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/AutoCompleteTextViewAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/AutoCompleteTextViewAdapter.kt @@ -5,30 +5,30 @@ import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.Filter +import android.widget.TextView import com.simplemobiletools.commons.extensions.normalizeString +import com.simplemobiletools.commons.helpers.SimpleContactsHelper +import com.simplemobiletools.commons.models.SimpleContact import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.activities.SimpleActivity -import com.simplemobiletools.smsmessenger.extensions.loadImage -import com.simplemobiletools.smsmessenger.models.Contact -import kotlinx.android.synthetic.main.item_contact.view.* -class AutoCompleteTextViewAdapter(val activity: SimpleActivity, val contacts: ArrayList) : - ArrayAdapter(activity, 0, contacts) { - var resultList = ArrayList() +class AutoCompleteTextViewAdapter(val activity: SimpleActivity, val contacts: ArrayList) : ArrayAdapter(activity, 0, contacts) { + + var resultList = ArrayList() override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val contact = resultList[position] var listItem = convertView if (listItem == null || listItem.tag != contact.name.isNotEmpty()) { - listItem = LayoutInflater.from(activity).inflate(R.layout.item_contact, parent, false) + listItem = LayoutInflater.from(activity).inflate(R.layout.item_contact_with_number, parent, false) } listItem!!.apply { tag = contact.name.isNotEmpty() - item_autocomplete_name.text = contact.name - item_autocomplete_number.text = contact.phoneNumber + findViewById(R.id.item_contact_name).text = contact.name + findViewById(R.id.item_contact_number).text = contact.phoneNumber - context.loadImage(contact.photoUri, item_autocomplete_image, contact.name) + SimpleContactsHelper(context).loadContactImage(contact.photoUri, findViewById(R.id.item_contact_image), contact.name) } return listItem @@ -62,7 +62,7 @@ class AutoCompleteTextViewAdapter(val activity: SimpleActivity, val contacts: Ar } } - override fun convertResultToString(resultValue: Any?) = (resultValue as? Contact)?.name + override fun convertResultToString(resultValue: Any?) = (resultValue as? SimpleContact)?.name } override fun getItem(index: Int) = resultList[index] diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ContactsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ContactsAdapter.kt index a7b87bff..c08be665 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ContactsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ContactsAdapter.kt @@ -3,22 +3,20 @@ package com.simplemobiletools.smsmessenger.adapters import android.view.Menu import android.view.View import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView import com.bumptech.glide.Glide import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter +import com.simplemobiletools.commons.helpers.SimpleContactsHelper +import com.simplemobiletools.commons.models.SimpleContact import com.simplemobiletools.commons.views.FastScroller import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.activities.SimpleActivity -import com.simplemobiletools.smsmessenger.extensions.loadImage -import com.simplemobiletools.smsmessenger.models.Contact -import kotlinx.android.synthetic.main.item_contact_with_number.view.* import java.util.* -class ContactsAdapter( - activity: SimpleActivity, var contacts: ArrayList, recyclerView: MyRecyclerView, - fastScroller: FastScroller?, itemClick: (Any) -> Unit -) : - MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { +class ContactsAdapter(activity: SimpleActivity, var contacts: ArrayList, recyclerView: MyRecyclerView, fastScroller: FastScroller?, + itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { override fun getActionMenuId() = 0 @@ -30,9 +28,9 @@ class ContactsAdapter( override fun getIsItemSelectable(position: Int) = true - override fun getItemSelectionKey(position: Int) = contacts.getOrNull(position)?.id + override fun getItemSelectionKey(position: Int) = contacts.getOrNull(position)?.rawId - override fun getItemKeyPosition(key: Int) = contacts.indexOfFirst { it.id == key } + override fun getItemKeyPosition(key: Int) = contacts.indexOfFirst { it.rawId == key } override fun onActionModeCreated() {} @@ -53,19 +51,19 @@ class ContactsAdapter( override fun onViewRecycled(holder: ViewHolder) { super.onViewRecycled(holder) if (!activity.isDestroyed && !activity.isFinishing) { - Glide.with(activity).clear(holder.itemView.contact_tmb) + Glide.with(activity).clear(holder.itemView.findViewById(R.id.item_contact_image)) } } - private fun setupView(view: View, contact: Contact) { + private fun setupView(view: View, contact: SimpleContact) { view.apply { - contact_name.text = contact.name - contact_name.setTextColor(textColor) + findViewById(R.id.item_contact_name).text = contact.name + findViewById(R.id.item_contact_name).setTextColor(textColor) - contact_number.text = contact.phoneNumber - contact_number.setTextColor(textColor) + findViewById(R.id.item_contact_number).text = contact.phoneNumber + findViewById(R.id.item_contact_number).setTextColor(textColor) - context.loadImage(contact.photoUri, contact_tmb, contact.name) + SimpleContactsHelper(context).loadContactImage(contact.photoUri, findViewById(R.id.item_contact_image), contact.name) } } } 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 d33aee50..2677c10f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt @@ -9,24 +9,19 @@ import com.bumptech.glide.Glide import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter import com.simplemobiletools.commons.dialogs.ConfirmationDialog import com.simplemobiletools.commons.extensions.formatDateOrTime -import com.simplemobiletools.commons.extensions.getColoredGroupIcon +import com.simplemobiletools.commons.helpers.SimpleContactsHelper import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.commons.views.FastScroller import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.activities.SimpleActivity import com.simplemobiletools.smsmessenger.extensions.deleteConversation -import com.simplemobiletools.smsmessenger.extensions.loadImage import com.simplemobiletools.smsmessenger.helpers.refreshMessages import com.simplemobiletools.smsmessenger.models.Conversation import kotlinx.android.synthetic.main.item_conversation.view.* -class ConversationsAdapter( - activity: SimpleActivity, var conversations: ArrayList, - recyclerView: MyRecyclerView, - fastScroller: FastScroller, - itemClick: (Any) -> Unit -) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { +class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayList, recyclerView: MyRecyclerView, fastScroller: FastScroller, + itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { init { setupDragListener(true) @@ -141,12 +136,12 @@ class ConversationsAdapter( // at group conversations we use an icon as the placeholder, not any letter val placeholder = if (conversation.isGroupConversation) { - activity.getColoredGroupIcon(conversation.title) + SimpleContactsHelper(context).getColoredGroupIcon(conversation.title) } else { null } - context.loadImage(conversation.photoUri, conversation_image, conversation.title, placeholder) + SimpleContactsHelper(context).loadContactImage(conversation.photoUri, conversation_image, conversation.title, placeholder) } } } 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 ad7443ff..0a609b19 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt @@ -20,13 +20,13 @@ import com.bumptech.glide.request.target.Target import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter import com.simplemobiletools.commons.dialogs.ConfirmationDialog import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.SimpleContactsHelper import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.commons.views.FastScroller import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.activities.SimpleActivity import com.simplemobiletools.smsmessenger.extensions.deleteMessage -import com.simplemobiletools.smsmessenger.extensions.loadImage import com.simplemobiletools.smsmessenger.helpers.* import com.simplemobiletools.smsmessenger.models.Message import com.simplemobiletools.smsmessenger.models.ThreadDateTime @@ -38,12 +38,8 @@ import kotlinx.android.synthetic.main.item_received_unknown_attachment.view.* import kotlinx.android.synthetic.main.item_sent_unknown_attachment.view.* import kotlinx.android.synthetic.main.item_thread_date_time.view.* -class ThreadAdapter( - activity: SimpleActivity, var messages: ArrayList, - recyclerView: MyRecyclerView, - fastScroller: FastScroller, - itemClick: (Any) -> Unit -) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { +class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList, recyclerView: MyRecyclerView, fastScroller: FastScroller, + itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { private val roundedCornersRadius = resources.getDimension(R.dimen.normal_margin).toInt() @@ -186,7 +182,7 @@ class ThreadAdapter( if (message.isReceivedMessage()) { thread_message_sender_photo.beVisible() thread_message_body.setTextColor(textColor) - context.loadImage(message.senderPhotoUri, thread_message_sender_photo, message.senderName) + SimpleContactsHelper(context).loadContactImage(message.senderPhotoUri, thread_message_sender_photo, message.senderName) } else { thread_message_sender_photo?.beGone() val background = context.getAdjustedPrimaryColor() diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/ArrayList.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/ArrayList.kt index 6e71dd87..dfaf3202 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/ArrayList.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/ArrayList.kt @@ -1,6 +1,6 @@ package com.simplemobiletools.smsmessenger.extensions import android.text.TextUtils -import com.simplemobiletools.smsmessenger.models.Contact +import com.simplemobiletools.commons.models.SimpleContact -fun ArrayList.getThreadTitle() = TextUtils.join(", ", map { it.name }.toTypedArray()) +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 new file mode 100644 index 00000000..bc2acca7 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Collections.kt @@ -0,0 +1,11 @@ +package com.simplemobiletools.smsmessenger.extensions + +inline fun List.indexOfFirstOrNull(predicate: (T) -> Boolean): Int? { + var index = 0 + for (item in this) { + if (predicate(item)) + return index + index++ + } + return 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 988b70ef..8634f708 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt @@ -9,27 +9,17 @@ import android.content.ContentValues import android.content.Context import android.content.Intent import android.graphics.Bitmap -import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.Drawable import android.media.AudioAttributes import android.media.AudioManager import android.media.RingtoneManager import android.net.Uri -import android.provider.ContactsContract -import android.provider.ContactsContract.CommonDataKinds -import android.provider.ContactsContract.CommonDataKinds.Organization -import android.provider.ContactsContract.CommonDataKinds.StructuredName import android.provider.ContactsContract.PhoneLookup import android.provider.Telephony.* import android.text.TextUtils -import android.widget.ImageView import androidx.core.app.NotificationCompat -import com.bumptech.glide.Glide -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions -import com.bumptech.glide.request.RequestOptions import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.* +import com.simplemobiletools.commons.models.SimpleContact import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.activities.ThreadActivity import com.simplemobiletools.smsmessenger.helpers.Config @@ -54,12 +44,22 @@ fun Context.getMessages(threadId: Int): ArrayList { val selection = "${Sms.THREAD_ID} = ?" val selectionArgs = arrayOf(threadId.toString()) - val sortOrder = "${Sms._ID} LIMIT 100" + val sortOrder = "${Sms._ID} DESC LIMIT 100" + val blockStatus = HashMap() var messages = ArrayList() queryCursor(uri, projection, selection, selectionArgs, sortOrder, showErrors = true) { cursor -> val senderNumber = cursor.getStringValue(Sms.ADDRESS) - if (isNumberBlocked(senderNumber)) { + + val isNumberBlocked = if (blockStatus.containsKey(senderNumber)) { + blockStatus[senderNumber]!! + } else { + val isBlocked = isNumberBlocked(senderNumber) + blockStatus[senderNumber] = isBlocked + isBlocked + } + + if (isNumberBlocked) { return@queryCursor } @@ -72,7 +72,7 @@ fun Context.getMessages(threadId: Int): ArrayList { val date = (cursor.getLongValue(Sms.DATE) / 1000).toInt() val read = cursor.getIntValue(Sms.READ) == 1 val thread = cursor.getIntValue(Sms.THREAD_ID) - val participant = Contact(0, senderName, photoUri, senderNumber) + val participant = SimpleContact(0, 0, senderName, photoUri, senderNumber) val isMMS = false val message = Message(id, body, type, arrayListOf(participant), date, read, thread, isMMS, null, senderName, photoUri) messages.add(message) @@ -85,7 +85,7 @@ fun Context.getMessages(threadId: Int): ArrayList { return messages } -// as soon as a message contains multiple recipients it count as an MMS instead of SMS +// as soon as a message contains multiple recipients it counts as an MMS instead of SMS fun Context.getMMS(threadId: Int? = null, sortOrder: String? = null): ArrayList { val uri = Mms.CONTENT_URI val projection = arrayOf( @@ -109,14 +109,22 @@ fun Context.getMMS(threadId: Int? = null, sortOrder: String? = null): ArrayList< } val messages = ArrayList() - val contactsMap = HashMap() + val contactsMap = HashMap() + val threadParticipants = HashMap>() queryCursor(uri, projection, selection, selectionArgs, sortOrder, showErrors = true) { cursor -> val mmsId = cursor.getIntValue(Mms._ID) val type = cursor.getIntValue(Mms.MESSAGE_BOX) val date = cursor.getLongValue(Mms.DATE).toInt() val read = cursor.getIntValue(Mms.READ) == 1 val threadId = cursor.getIntValue(Mms.THREAD_ID) - val participants = getThreadParticipants(threadId, contactsMap) + val participants = if (threadParticipants.containsKey(threadId)) { + threadParticipants[threadId]!! + } else { + val parts = getThreadParticipants(threadId, contactsMap) + threadParticipants[threadId] = parts + parts + } + val isMMS = true val attachment = getMmsAttachment(mmsId) val body = attachment?.text ?: "" @@ -136,7 +144,7 @@ fun Context.getMMS(threadId: Int? = null, sortOrder: String? = null): ArrayList< messages.add(message) participants.forEach { - contactsMap.put(it.id, it) + contactsMap.put(it.rawId, it) } } @@ -196,7 +204,7 @@ fun Context.getConversations(): ArrayList { val names = getThreadContactNames(phoneNumbers) val title = TextUtils.join(", ", names.toTypedArray()) - val photoUri = if (phoneNumbers.size == 1) getPhotoUriFromPhoneNumber(phoneNumbers.first()) else "" + val photoUri = if (phoneNumbers.size == 1) SimpleContactsHelper(this).getPhotoUriFromPhoneNumber(phoneNumbers.first()) else "" val isGroupConversation = phoneNumbers.size > 1 val conversation = Conversation(id, snippet, date.toInt(), read, title, photoUri, isGroupConversation) conversations.add(conversation) @@ -279,14 +287,14 @@ fun Context.getThreadSnippet(threadId: Int): String { return snippet } -fun Context.getThreadParticipants(threadId: Int, contactsMap: HashMap?): ArrayList { +fun Context.getThreadParticipants(threadId: Int, contactsMap: HashMap?): ArrayList { val uri = Uri.parse("${MmsSms.CONTENT_CONVERSATIONS_URI}?simple=true") val projection = arrayOf( ThreadsColumns.RECIPIENT_IDS ) val selection = "${Mms._ID} = ?" val selectionArgs = arrayOf(threadId.toString()) - val participants = ArrayList() + val participants = ArrayList() try { val cursor = contentResolver.query(uri, projection, selection, selectionArgs, null) cursor?.use { @@ -303,7 +311,7 @@ fun Context.getThreadParticipants(threadId: Int, contactsMap: HashMap): ArrayList { fun Context.getThreadContactNames(phoneNumbers: List): ArrayList { val names = ArrayList() phoneNumbers.forEach { - names.add(getNameFromPhoneNumber(it)) + names.add(SimpleContactsHelper(this).getNameFromPhoneNumber(it)) } return names } @@ -351,8 +359,8 @@ fun Context.getPhoneNumberFromAddressId(canonicalAddressId: Int): String { return "" } -fun Context.getSuggestedContacts(): ArrayList { - val contacts = ArrayList() +fun Context.getSuggestedContacts(): ArrayList { + val contacts = ArrayList() val uri = Sms.CONTENT_URI val projection = arrayOf( Sms.ADDRESS @@ -371,7 +379,7 @@ fun Context.getSuggestedContacts(): ArrayList { val senderName = namePhoto.name val photoUri = namePhoto.photoUri ?: "" - val contact = Contact(0, senderName, photoUri, senderNumber) + val contact = SimpleContact(0, 0, senderName, photoUri, senderNumber) if (!contacts.map { it.phoneNumber.trimToComparableNumber() }.contains(senderNumber.trimToComparableNumber())) { contacts.add(contact) } @@ -380,34 +388,6 @@ fun Context.getSuggestedContacts(): ArrayList { return contacts } -fun Context.getAvailableContacts(callback: (ArrayList) -> Unit) { - ensureBackgroundThread { - val names = getContactNames() - var allContacts = getContactPhoneNumbers() - allContacts.forEach { - val contactId = it.id - val contact = names.firstOrNull { it.id == contactId } - val name = contact?.name - if (name != null) { - it.name = name - } - - val photoUri = contact?.photoUri - if (photoUri != null) { - it.photoUri = photoUri - } - } - - allContacts = allContacts.filter { it.name.isNotEmpty() }.distinctBy { - val startIndex = Math.max(0, it.phoneNumber.length - 9) - it.phoneNumber.substring(startIndex) - }.toMutableList() as ArrayList - - allContacts.sortBy { it.name.normalizeString().toLowerCase(Locale.getDefault()) } - callback(allContacts) - } -} - fun Context.getNameAndPhotoFromPhoneNumber(number: String): NamePhoto? { if (!hasPermission(PERMISSION_READ_CONTACTS)) { return NamePhoto(number, null) @@ -435,128 +415,6 @@ fun Context.getNameAndPhotoFromPhoneNumber(number: String): NamePhoto? { return NamePhoto(number, null) } -fun Context.getNameFromPhoneNumber(number: String): String { - if (!hasPermission(PERMISSION_READ_CONTACTS)) { - return number - } - - val uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)) - val projection = arrayOf( - PhoneLookup.DISPLAY_NAME - ) - - try { - val cursor = contentResolver.query(uri, projection, null, null, null) - cursor.use { - if (cursor?.moveToFirst() == true) { - return cursor.getStringValue(PhoneLookup.DISPLAY_NAME) - } - } - } catch (e: Exception) { - showErrorToast(e) - } - - return number -} - -fun Context.getPhotoUriFromPhoneNumber(number: String): String { - if (!hasPermission(PERMISSION_READ_CONTACTS)) { - return "" - } - - val uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)) - val projection = arrayOf( - PhoneLookup.PHOTO_URI - ) - - try { - val cursor = contentResolver.query(uri, projection, null, null, null) - cursor.use { - if (cursor?.moveToFirst() == true) { - return cursor.getStringValue(PhoneLookup.PHOTO_URI) ?: "" - } - } - } catch (e: Exception) { - showErrorToast(e) - } - - return "" -} - -fun Context.getContactNames(): List { - val contacts = ArrayList() - val uri = ContactsContract.Data.CONTENT_URI - val projection = arrayOf( - ContactsContract.Data.CONTACT_ID, - StructuredName.PREFIX, - StructuredName.GIVEN_NAME, - StructuredName.MIDDLE_NAME, - StructuredName.FAMILY_NAME, - StructuredName.SUFFIX, - StructuredName.PHOTO_THUMBNAIL_URI, - Organization.COMPANY, - Organization.TITLE, - ContactsContract.Data.MIMETYPE - ) - - val selection = "${ContactsContract.Data.MIMETYPE} = ? OR ${ContactsContract.Data.MIMETYPE} = ?" - val selectionArgs = arrayOf( - StructuredName.CONTENT_ITEM_TYPE, - Organization.CONTENT_ITEM_TYPE - ) - - queryCursor(uri, projection, selection, selectionArgs) { cursor -> - val id = cursor.getIntValue(ContactsContract.Data.CONTACT_ID) - val mimetype = cursor.getStringValue(ContactsContract.Data.MIMETYPE) - val photoUri = cursor.getStringValue(StructuredName.PHOTO_THUMBNAIL_URI) ?: "" - val isPerson = mimetype == StructuredName.CONTENT_ITEM_TYPE - if (isPerson) { - val prefix = cursor.getStringValue(StructuredName.PREFIX) ?: "" - val firstName = cursor.getStringValue(StructuredName.GIVEN_NAME) ?: "" - val middleName = cursor.getStringValue(StructuredName.MIDDLE_NAME) ?: "" - val familyName = cursor.getStringValue(StructuredName.FAMILY_NAME) ?: "" - val suffix = cursor.getStringValue(StructuredName.SUFFIX) ?: "" - if (firstName.isNotEmpty() || middleName.isNotEmpty() || familyName.isNotEmpty()) { - val names = arrayOf(prefix, firstName, middleName, familyName, suffix).filter { it.isNotEmpty() } - val fullName = TextUtils.join(" ", names) - val contact = Contact(id, fullName, photoUri, "") - contacts.add(contact) - } - } - - val isOrganization = mimetype == Organization.CONTENT_ITEM_TYPE - if (isOrganization) { - val company = cursor.getStringValue(Organization.COMPANY) ?: "" - val jobTitle = cursor.getStringValue(Organization.TITLE) ?: "" - if (company.isNotEmpty() || jobTitle.isNotEmpty()) { - val fullName = "$company $jobTitle".trim() - val contact = Contact(id, fullName, photoUri, "") - contacts.add(contact) - } - } - } - return contacts -} - -fun Context.getContactPhoneNumbers(): ArrayList { - val contacts = ArrayList() - val uri = CommonDataKinds.Phone.CONTENT_URI - val projection = arrayOf( - ContactsContract.Data.CONTACT_ID, - CommonDataKinds.Phone.NORMALIZED_NUMBER - ) - - queryCursor(uri, projection) { cursor -> - val id = cursor.getIntValue(ContactsContract.Data.CONTACT_ID) - val phoneNumber = cursor.getStringValue(CommonDataKinds.Phone.NORMALIZED_NUMBER) - if (phoneNumber != null) { - val contact = Contact(id, "", "", phoneNumber) - contacts.add(contact) - } - } - return contacts -} - fun Context.insertNewSMS(address: String, subject: String, body: String, date: Long, read: Int, threadId: Long, type: Int) { val uri = Sms.CONTENT_URI val contentValues = ContentValues().apply { @@ -619,6 +477,10 @@ fun Context.getThreadId(addresses: Set): Long { } fun Context.isNumberBlocked(number: String): Boolean { + if (!isNougatPlus()) { + return false + } + val blockedNumbers = getBlockedNumbers() val numberToCompare = number.trimToComparableNumber() return blockedNumbers.map { it.numberToCompare }.contains(numberToCompare) || blockedNumbers.map { it.number }.contains(numberToCompare) @@ -655,7 +517,7 @@ fun Context.showReceivedMessageNotification(address: String, body: String, threa val summaryText = getString(R.string.new_message) val sender = getNameAndPhotoFromPhoneNumber(address)?.name ?: "" - val largeIcon = bitmap ?: getContactLetterIcon(sender) + val largeIcon = bitmap ?: SimpleContactsHelper(this).getContactLetterIcon(sender) val builder = NotificationCompat.Builder(this, channelId) .setContentTitle(sender) .setContentText(body) @@ -672,20 +534,3 @@ fun Context.showReceivedMessageNotification(address: String, body: String, threa notificationManager.notify(threadID, builder.build()) } - -fun Context.loadImage(path: String, imageView: ImageView, placeholderName: String, placeholderImage: Drawable? = null) { - val placeholder = placeholderImage ?: BitmapDrawable(resources, getContactLetterIcon(placeholderName)) - - val options = RequestOptions() - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .error(placeholder) - .centerCrop() - - Glide.with(this) - .load(path) - .transition(DrawableTransitionOptions.withCrossFade()) - .placeholder(placeholder) - .apply(options) - .apply(RequestOptions.circleCropTransform()) - .into(imageView) -} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Config.kt index 6e7aad09..c5e95e68 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Config.kt @@ -7,4 +7,10 @@ class Config(context: Context) : BaseConfig(context) { companion object { fun newInstance(context: Context) = Config(context) } + + fun saveUseSIMIdAtNumber(number: String, SIMId: Int) { + prefs.edit().putInt(USE_SIM_ID_PREFIX + number, SIMId).apply() + } + + fun getUseSIMIdAtNumber(number: String) = prefs.getInt(USE_SIM_ID_PREFIX + number, 0) } 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 fc9bc46c..33fb77fd 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt @@ -9,6 +9,7 @@ const val THREAD_TEXT = "thread_text" const val THREAD_NUMBER = "thread_number" const val THREAD_ATTACHMENT_URI = "thread_attachment_uri" const val THREAD_ATTACHMENT_URIS = "thread_attachment_uris" +const val USE_SIM_ID_PREFIX = "use_sim_id_" // view types for the thread list view const val THREAD_DATE_TIME = 1 @@ -16,10 +17,6 @@ const val THREAD_RECEIVED_MESSAGE = 2 const val THREAD_SENT_MESSAGE = 3 const val THREAD_SENT_MESSAGE_ERROR = 4 -// constants used at passing info to SmsSentReceiver -const val MESSAGE_BODY = "message_body" -const val MESSAGE_ADDRESS = "message_address" - fun refreshMessages() { EventBus.getDefault().post(Events.RefreshMessages()) } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Contact.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Contact.kt deleted file mode 100644 index a5c186c5..00000000 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Contact.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.simplemobiletools.smsmessenger.models - -data class Contact(val id: Int, var name: String, var photoUri: String, var phoneNumber: String) 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 0b9f797f..2b6f24c2 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt @@ -1,10 +1,10 @@ package com.simplemobiletools.smsmessenger.models import android.provider.Telephony +import com.simplemobiletools.commons.models.SimpleContact data class Message( - val id: Int, val body: String, val type: Int, val participants: ArrayList, val date: Int, val read: Boolean, val thread: Int, - val isMMS: Boolean, val attachment: MessageAttachment?, val senderName: String, val senderPhotoUri: String -) : ThreadItem() { + val id: Int, val body: String, val type: Int, val participants: ArrayList, val date: Int, val read: Boolean, val thread: Int, + val isMMS: Boolean, val attachment: MessageAttachment?, val senderName: String, val senderPhotoUri: String) : ThreadItem() { fun isReceivedMessage() = type == Telephony.Sms.MESSAGE_TYPE_INBOX } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/SIMCard.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/SIMCard.kt new file mode 100644 index 00000000..4d38b14b --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/SIMCard.kt @@ -0,0 +1,3 @@ +package com.simplemobiletools.smsmessenger.models + +data class SIMCard(val id: Int, val subscriptionId: Int, val label: String) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/SmsReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/SmsReceiver.kt index f401ed62..3649bc9d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/SmsReceiver.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/SmsReceiver.kt @@ -13,22 +13,26 @@ import com.simplemobiletools.smsmessenger.helpers.refreshMessages class SmsReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val messages = Telephony.Sms.Intents.getMessagesFromIntent(intent) - messages.forEach { - val address = it.originatingAddress ?: "" - if (context.isNumberBlocked(address)) { - return@forEach - } + var address = "" + var body = "" + var subject = "" + var date = 0L + var threadId = 0L + val type = Telephony.Sms.MESSAGE_TYPE_INBOX + val read = 0 - val subject = it.pseudoSubject - val body = it.messageBody - val date = it.timestampMillis - val threadId = context.getThreadId(address) - val type = Telephony.Sms.MESSAGE_TYPE_INBOX - val read = 0 - context.insertNewSMS(address, subject, body, date, read, threadId, type) - context.showReceivedMessageNotification(address, body, threadId.toInt()) + messages.forEach { + address = it.originatingAddress ?: "" + subject = it.pseudoSubject + body += it.messageBody + date = Math.min(it.timestampMillis, System.currentTimeMillis()) + threadId = context.getThreadId(address) } - refreshMessages() + if (!context.isNumberBlocked(address)) { + context.insertNewSMS(address, subject, body, date, read, threadId, type) + context.showReceivedMessageNotification(address, body, threadId.toInt()) + refreshMessages() + } } } diff --git a/app/src/main/res/layout/activity_thread.xml b/app/src/main/res/layout/activity_thread.xml index de078daf..3978a078 100644 --- a/app/src/main/res/layout/activity_thread.xml +++ b/app/src/main/res/layout/activity_thread.xml @@ -156,13 +156,39 @@ android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginEnd="@dimen/small_margin" - android:layout_toStartOf="@+id/thread_send_message" + android:layout_toStartOf="@+id/thread_select_sim_icon" android:layout_toEndOf="@+id/thread_add_attachment" android:background="@android:color/transparent" android:gravity="center_vertical" android:hint="@string/type_a_message" android:minHeight="@dimen/normal_icon_size" /> + + + + - - - - - - - - - diff --git a/app/src/main/res/layout/item_contact_with_number.xml b/app/src/main/res/layout/item_contact_with_number.xml deleted file mode 100644 index aae30363..00000000 --- a/app/src/main/res/layout/item_contact_with_number.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/layout/item_received_message.xml b/app/src/main/res/layout/item_received_message.xml index 604fdef5..7ea3c0ed 100644 --- a/app/src/main/res/layout/item_received_message.xml +++ b/app/src/main/res/layout/item_received_message.xml @@ -22,8 +22,8 @@ + android:layout_height="wrap_content"> + Paprastas SMS Siuntiklis + SMS Siuntiklis + Rašykite žinutę… + Žinutė neišsiųsta. + Pridėti žmogų + Priedas + Nebuvo rasta išsaugotų pokalbių + Pradėtipokalbį + + + Naujas pokalbis + Pridėti kontaktą arba numerį… + Pasiūlymai + + + Gautos žinutės + Nauja žinutė + Mark as Read + + + Ar tikrai norite ištrinti visas šio pokalbio žinutes? + + + + %d pokalbis + %d pokalbiai + + + + + %d žinutė + %d žinutės + + + + Why does the app require access to the internet? + Sadly it is needed for sending MMS attachments. Not being able to send MMS would be a really huge disadvantage compared to other apps, so we decided to go this way. + However, as usually, there are no ads, tracking or analytics whatsoever, the internet is used only for sending MMS. + + + + Simple SMS Messenger - Manage messages easily + + An easy and quick way of managing SMS and MMS messages without ads. + + A great way to stay in touch with your relatives, by sending both SMS and MMS messages. The app properly handles group messaging too, just like blocking numbers from Android 7+. + + It offers many date formats to choose from, to make you feel comfortable at using it. You can toggle between 12 and 24 hours time format too. + + It has a really tiny app size compared to the competition, making it really fast to download. + + It comes with material design and dark theme by default, provides great user experience for easy usage. The lack of internet access gives you more privacy, security and stability than other apps. + + Neturi reklamų ar nereikalingų leidimų. Programėlė visiškai atviro kodo, yra galimybė keisti spalvas. + Check out the full suite of Simple Tools here: + https://www.simplemobiletools.com + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index b85789dc..e26b0c63 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -4,18 +4,19 @@ Escreva uma mensagem… Mensagem não enviada. Adicionar pessoa - Attachment - No stored conversations have been found - Start a conversation + Anexo + Não foram encontradas conversas + Iniciar uma conversa - New conversation + Nova conversa Adicionar contacto ou número… Sugestões SMS recebida Nova mensagem + Mark as Read Tem a certeza de que deseja eliminar todas as mensagens desta conversa? @@ -39,21 +40,21 @@ - Simple SMS Messenger - Manage messages easily + Simple SMS Messenger - Gestão de mensagens - An easy and quick way of managing SMS and MMS messages without ads. + Aplicação simples para gerir SMS e MMS, sem anúncios. - A great way to stay in touch with your relatives, by sending both SMS and MMS messages. The app properly handles group messaging too, just like blocking numbers from Android 7+. + Uma excelente forma para manter o contacto com os seus amigos e familiares. Também pode ser utilizada para mensagens de grupo e possibilita bloqueio de números de telefone em versões Android 7+. - It offers many date formats to choose from, to make you feel comfortable at using it. You can toggle between 12 and 24 hours time format too. + Permite a utilização de vários formatos de data. Também pode alternar o formato das horas. - It has a really tiny app size compared to the competition, making it really fast to download. + É uma aplicação pequena - comparada com as aplicações do mesmo género - o que torna a sua descarga muito rápida. - It comes with material design and dark theme by default, provides great user experience for easy usage. The lack of internet access gives you more privacy, security and stability than other apps. + Disponibiliza um design atrativo e um tema escuro por omissão. A não utilização da permissão Internet providencia-lhe mais privacidade, segurança e estabilidade do que as outras aplicações. - Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors. + Não contém anúncios nem permissões desnecessárias. É open source e permite a personalização de cores. - Check out the full suite of Simple Tools here: + Consulte o conjunto completo de aplicações Simple aqui: https://www.simplemobiletools.com Facebook: diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 8bf493e2..9c57104f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -16,6 +16,7 @@ Получено SMS Новое сообщение + Mark as Read Вы уверены, что хотите удалить все сообщения в этой переписке? diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 745fd363..81fbe50b 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -16,6 +16,7 @@ Prijatá SMS Nová správa + Označiť ako prečítané Ste si istý, že chcete odstrániť všetky správy tejto konverzácie? diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index d856c3bc..fd6215f3 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,7 +1,6 @@ 72dp - 40dp 64dp 36dp 60dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 20a04b62..a476612e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -16,6 +16,7 @@ Received SMS New message + Mark as Read Are you sure you want to delete all messages of this conversation? diff --git a/build.gradle b/build.gradle index 0b97b0f7..ff843f13 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.6.2' + classpath 'com.android.tools.build:gradle:3.6.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/fastlane/metadata/android/en-US/changelogs/2.txt b/fastlane/metadata/android/en-US/changelogs/2.txt new file mode 100644 index 00000000..1733484b --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/2.txt @@ -0,0 +1,4 @@ + * Properly handle incoming multipart SMS messages + * Fixed a couple coloring issues + * Do not allow attempts to block a number below Android 7 + * A couple other translation, stability and UI improvements diff --git a/fastlane/metadata/android/en-US/changelogs/3.txt b/fastlane/metadata/android/en-US/changelogs/3.txt new file mode 100644 index 00000000..080fbc15 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/3.txt @@ -0,0 +1,4 @@ + * Adding multi-SIM support + * Properly show the latest messages, not the oldest ones in some cases + * Increased minimal OS version to Android 5.1 + * Fixed many other UI and UX related issues diff --git a/fastlane/metadata/android/en-US/changelogs/4.txt b/fastlane/metadata/android/en-US/changelogs/4.txt new file mode 100644 index 00000000..7b45b5f5 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/4.txt @@ -0,0 +1,2 @@ + * Allow moving the app on an SD card + * Added some performance, UI and stability improvements diff --git a/fastlane/metadata/android/pt/full_description.txt b/fastlane/metadata/android/pt/full_description.txt new file mode 100644 index 00000000..9f3741ff --- /dev/null +++ b/fastlane/metadata/android/pt/full_description.txt @@ -0,0 +1,18 @@ +Uma excelente forma para manter o contacto com os seus amigos e familiares. Também pode ser utilizada para mensagens de grupo e possibilita bloqueio de números de telefone em versões Android 7+. + +Permite a utilização de vários formatos de data. Também pode alternar o formato das horas. + +É uma aplicação pequena - comparada com as aplicações do mesmo género - o que torna a sua descarga muito rápida. + +Disponibiliza um design atrativo e um tema escuro por omissão. A não utilização da permissão Internet providencia-lhe mais privacidade, segurança e estabilidade do que as outras aplicações. + +Não contém anúncios nem permissões desnecessárias. É open source e permite a personalização de cores. + +Consulte o conjunto completo de aplicações Simple aqui: +https://www.simplemobiletools.com + +Facebook: +https://www.facebook.com/simplemobiletools + +Reddit: +https://www.reddit.com/r/SimpleMobileTools diff --git a/fastlane/metadata/android/pt/short_description.txt b/fastlane/metadata/android/pt/short_description.txt new file mode 100644 index 00000000..4eadd656 --- /dev/null +++ b/fastlane/metadata/android/pt/short_description.txt @@ -0,0 +1 @@ +Aplicação simples para gerir SMS e MMS, sem anúncios. diff --git a/fastlane/metadata/android/pt/title.txt b/fastlane/metadata/android/pt/title.txt new file mode 100644 index 00000000..9aabacf2 --- /dev/null +++ b/fastlane/metadata/android/pt/title.txt @@ -0,0 +1 @@ +Simple SMS Messenger - Gestão de mensagens diff --git a/fastlane/metadata/android/ru/full_description.txt b/fastlane/metadata/android/ru/full_description.txt new file mode 100644 index 00000000..3e48bb3d --- /dev/null +++ b/fastlane/metadata/android/ru/full_description.txt @@ -0,0 +1,18 @@ +SMS/MMS-сообщения — это отличный способ оставаться на связи со своими родственниками. Приложение также правильно обрабатывает групповые сообщения и блокировку номеров на Android 7+. + +Поддерживается множество вариантов отображения даты, чтобы вам было удобно пользоваться приложением. Также вы можете переключаться между 12- и 24-часовым форматом времени. + +Наше приложение имеет очень маленький размер по сравнению с конкурентами, что делает его загрузку такой быстрой. + +Приложение выпускается с материальным дизайном и тёмной темой по умолчанию, что обеспечивает удобное использование. Отсутствие доступа в интернет обеспечивает вам больше приватности, безопасности и стабильности, чем в других приложениях. + +Не содержит рекламы или ненужных разрешений. Полностью открытый исходный код, есть возможность настроить цвета интерфейса. + +Ознакомьтесь с полным набором инструментов серии Simple здесь: +https://www.simplemobiletools.com + +Facebook: +https://www.facebook.com/simplemobiletools + +Reddit: +https://www.reddit.com/r/SimpleMobileTools diff --git a/fastlane/metadata/android/ru/short_description.txt b/fastlane/metadata/android/ru/short_description.txt new file mode 100644 index 00000000..9fd3c07e --- /dev/null +++ b/fastlane/metadata/android/ru/short_description.txt @@ -0,0 +1 @@ +Простой и быстрый способ управления сообщениями SMS и MMS без рекламы. diff --git a/fastlane/metadata/android/ru/title.txt b/fastlane/metadata/android/ru/title.txt new file mode 100644 index 00000000..90599b4b --- /dev/null +++ b/fastlane/metadata/android/ru/title.txt @@ -0,0 +1 @@ +Simple SMS Messenger - лёгкое управление SMS