Merge pull request #1 from SimpleMobileTools/master

upd
This commit is contained in:
solokot
2020-05-12 14:53:00 +03:00
committed by GitHub
48 changed files with 446 additions and 453 deletions

View File

@@ -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)*
----------------------------

View File

@@ -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'

View File

@@ -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 <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.simplemobiletools.smsmessenger">
package="com.simplemobiletools.smsmessenger"
android:installLocation="auto">
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.WRITE_SMS" />
@@ -10,6 +11,7 @@
<uses-permission android:name="android.permission.RECEIVE_MMS" />
<uses-permission android:name="android.provider.Telephony.SMS_RECEIVED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission

View File

@@ -48,11 +48,16 @@ class MainActivity : SimpleActivity() {
if (isQPlus()) {
val roleManager = getSystemService(RoleManager::class.java)
if (roleManager!!.isRoleHeld(RoleManager.ROLE_SMS)) {
askPermissions()
if (roleManager!!.isRoleAvailable(RoleManager.ROLE_SMS)) {
if (roleManager.isRoleHeld(RoleManager.ROLE_SMS)) {
askPermissions()
} else {
val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS)
startActivityForResult(intent, MAKE_DEFAULT_APP_REQUEST)
}
} else {
val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS)
startActivityForResult(intent, MAKE_DEFAULT_APP_REQUEST)
toast(R.string.unknown_error_occurred)
finish()
}
} else {
if (Telephony.Sms.getDefaultSmsPackage(this) == packageName) {

View File

@@ -3,23 +3,28 @@ package com.simplemobiletools.smsmessenger.activities
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.WindowManager
import com.reddit.indicatorfastscroll.FastScrollItemIndicator
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.PERMISSION_READ_CONTACTS
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.adapters.ContactsAdapter
import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.extensions.getSuggestedContacts
import com.simplemobiletools.smsmessenger.extensions.getThreadId
import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.models.Contact
import kotlinx.android.synthetic.main.activity_conversation.*
import kotlinx.android.synthetic.main.item_suggested_contact.view.*
import java.net.URLDecoder
import java.util.*
import kotlin.collections.ArrayList
class NewConversationActivity : SimpleActivity() {
private var allContacts = ArrayList<Contact>()
private var allContacts = ArrayList<SimpleContact>()
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<Contact>()
val filteredContacts = ArrayList<SimpleContact>()
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<Contact>) {
private fun setupAdapter(contacts: ArrayList<SimpleContact>) {
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<Contact>) {
private fun setupLetterFastscroller(contacts: ArrayList<SimpleContact>) {
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("")

View File

@@ -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 {

View File

@@ -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<ThreadItem>()
private var bus: EventBus? = null
private var participants = ArrayList<Contact>()
private var participants = ArrayList<SimpleContact>()
private var messages = ArrayList<Message>()
private val availableSIMCards = ArrayList<SIMCard>()
private var attachmentUris = LinkedHashSet<Uri>()
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<Contact>
participants = participants.filter { it.rawId != id }.toMutableList() as ArrayList<SimpleContact>
showSelectedContacts()
}

View File

@@ -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<Contact>) :
ArrayAdapter<Contact>(activity, 0, contacts) {
var resultList = ArrayList<Contact>()
class AutoCompleteTextViewAdapter(val activity: SimpleActivity, val contacts: ArrayList<SimpleContact>) : ArrayAdapter<SimpleContact>(activity, 0, contacts) {
var resultList = ArrayList<SimpleContact>()
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<TextView>(R.id.item_contact_name).text = contact.name
findViewById<TextView>(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]

View File

@@ -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<Contact>, recyclerView: MyRecyclerView,
fastScroller: FastScroller?, itemClick: (Any) -> Unit
) :
MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) {
class ContactsAdapter(activity: SimpleActivity, var contacts: ArrayList<SimpleContact>, 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<ImageView>(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<TextView>(R.id.item_contact_name).text = contact.name
findViewById<TextView>(R.id.item_contact_name).setTextColor(textColor)
contact_number.text = contact.phoneNumber
contact_number.setTextColor(textColor)
findViewById<TextView>(R.id.item_contact_number).text = contact.phoneNumber
findViewById<TextView>(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)
}
}
}

View File

@@ -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<Conversation>,
recyclerView: MyRecyclerView,
fastScroller: FastScroller,
itemClick: (Any) -> Unit
) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) {
class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayList<Conversation>, 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)
}
}
}

View File

@@ -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<ThreadItem>,
recyclerView: MyRecyclerView,
fastScroller: FastScroller,
itemClick: (Any) -> Unit
) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) {
class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem>, 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()

View File

@@ -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<Contact>.getThreadTitle() = TextUtils.join(", ", map { it.name }.toTypedArray())
fun ArrayList<SimpleContact>.getThreadTitle() = TextUtils.join(", ", map { it.name }.toTypedArray())

View File

@@ -0,0 +1,11 @@
package com.simplemobiletools.smsmessenger.extensions
inline fun <T> List<T>.indexOfFirstOrNull(predicate: (T) -> Boolean): Int? {
var index = 0
for (item in this) {
if (predicate(item))
return index
index++
}
return null
}

View File

@@ -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<Message> {
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<String, Boolean>()
var messages = ArrayList<Message>()
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<Message> {
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<Message> {
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<Message> {
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<Message>()
val contactsMap = HashMap<Int, Contact>()
val contactsMap = HashMap<Int, SimpleContact>()
val threadParticipants = HashMap<Int, ArrayList<SimpleContact>>()
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<Conversation> {
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<Int, Contact>?): ArrayList<Contact> {
fun Context.getThreadParticipants(threadId: Int, contactsMap: HashMap<Int, SimpleContact>?): ArrayList<SimpleContact> {
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<Contact>()
val participants = ArrayList<SimpleContact>()
try {
val cursor = contentResolver.query(uri, projection, selection, selectionArgs, null)
cursor?.use {
@@ -303,7 +311,7 @@ fun Context.getThreadParticipants(threadId: Int, contactsMap: HashMap<Int, Conta
val namePhoto = getNameAndPhotoFromPhoneNumber(phoneNumber)
val name = namePhoto?.name ?: ""
val photoUri = namePhoto?.photoUri ?: ""
val contact = Contact(addressId, name, photoUri, phoneNumber)
val contact = SimpleContact(addressId, addressId, name, photoUri, phoneNumber)
participants.add(contact)
}
}
@@ -325,7 +333,7 @@ fun Context.getThreadPhoneNumbers(recipientIds: List<Int>): ArrayList<String> {
fun Context.getThreadContactNames(phoneNumbers: List<String>): ArrayList<String> {
val names = ArrayList<String>()
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<Contact> {
val contacts = ArrayList<Contact>()
fun Context.getSuggestedContacts(): ArrayList<SimpleContact> {
val contacts = ArrayList<SimpleContact>()
val uri = Sms.CONTENT_URI
val projection = arrayOf(
Sms.ADDRESS
@@ -371,7 +379,7 @@ fun Context.getSuggestedContacts(): ArrayList<Contact> {
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<Contact> {
return contacts
}
fun Context.getAvailableContacts(callback: (ArrayList<Contact>) -> 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<Contact>
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<Contact> {
val contacts = ArrayList<Contact>()
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<Contact> {
val contacts = ArrayList<Contact>()
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<String>): 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)
}

View File

@@ -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)
}

View File

@@ -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())
}

View File

@@ -1,3 +0,0 @@
package com.simplemobiletools.smsmessenger.models
data class Contact(val id: Int, var name: String, var photoUri: String, var phoneNumber: String)

View File

@@ -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<Contact>, 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<SimpleContact>, 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
}

View File

@@ -0,0 +1,3 @@
package com.simplemobiletools.smsmessenger.models
data class SIMCard(val id: Int, val subscriptionId: Int, val label: String)

View File

@@ -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()
}
}
}

View File

@@ -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" />
<ImageView
android:id="@+id/thread_select_sim_icon"
android:layout_width="@dimen/normal_icon_size"
android:layout_height="@dimen/normal_icon_size"
android:layout_alignParentBottom="true"
android:layout_marginTop="@dimen/small_margin"
android:layout_toStartOf="@+id/thread_send_message"
android:alpha="0.9"
android:background="?selectableItemBackgroundBorderless"
android:padding="@dimen/normal_margin"
android:src="@drawable/ic_sim_vector"
android:visibility="gone" />
<TextView
android:id="@+id/thread_select_sim_number"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignStart="@+id/thread_select_sim_icon"
android:layout_alignTop="@+id/thread_select_sim_icon"
android:layout_alignEnd="@+id/thread_select_sim_icon"
android:layout_alignBottom="@+id/thread_select_sim_icon"
android:gravity="center"
android:textSize="@dimen/normal_text_size"
android:visibility="gone"
tools:text="1" />
<ImageView
android:id="@+id/thread_send_message"
android:layout_width="@dimen/normal_icon_size"

View File

@@ -1,57 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/item_autocomplete_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/small_margin"
android:paddingTop="@dimen/medium_margin"
android:paddingEnd="@dimen/medium_margin"
android:paddingBottom="@dimen/medium_margin">
<ImageView
android:id="@+id/item_autocomplete_image"
android:layout_width="@dimen/avatar_size"
android:layout_height="@dimen/avatar_size"
android:layout_margin="@dimen/tiny_margin"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/item_autocomplete_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:lines="1"
android:maxLines="1"
android:paddingStart="@dimen/medium_margin"
android:paddingEnd="@dimen/medium_margin"
android:singleLine="true"
android:textSize="@dimen/bigger_text_size"
app:layout_constraintBottom_toTopOf="@+id/item_autocomplete_number"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/item_autocomplete_image"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Simple Mobile" />
<TextView
android:id="@+id/item_autocomplete_number"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="@+id/item_autocomplete_name"
android:layout_marginEnd="8dp"
android:alpha="0.8"
android:lines="1"
android:maxLines="1"
android:paddingStart="@dimen/medium_margin"
android:paddingEnd="@dimen/medium_margin"
android:singleLine="true"
android:textSize="@dimen/normal_text_size"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/item_autocomplete_image"
app:layout_constraintTop_toBottomOf="@+id/item_autocomplete_name"
tools:text="123456" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/contact_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/medium_margin"
android:paddingEnd="@dimen/activity_margin"
android:paddingBottom="@dimen/medium_margin">
<ImageView
android:id="@+id/contact_tmb"
android:layout_width="@dimen/normal_icon_size"
android:layout_height="@dimen/normal_icon_size"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/small_margin"
android:layout_marginEnd="@dimen/small_margin"
android:padding="@dimen/small_margin"
android:src="@drawable/ic_person_vector" />
<TextView
android:id="@+id/contact_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toEndOf="@+id/contact_tmb"
android:ellipsize="end"
android:maxLines="1"
android:textSize="@dimen/big_text_size"
tools:text="John Doe" />
<TextView
android:id="@+id/contact_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/contact_name"
android:layout_alignStart="@+id/contact_name"
android:layout_toEndOf="@+id/contact_tmb"
android:alpha="0.6"
android:maxLines="1"
android:textSize="@dimen/normal_text_size"
tools:text="0123 456 789" />
</RelativeLayout>

View File

@@ -22,8 +22,8 @@
<ImageView
android:id="@+id/thread_message_sender_photo"
android:layout_width="@dimen/avatar_size"
android:layout_height="@dimen/avatar_size"
android:layout_width="@dimen/list_avatar_size"
android:layout_height="@dimen/list_avatar_size"
android:layout_alignBottom="@+id/thread_message_body"
android:layout_alignParentStart="true"
android:layout_marginEnd="@dimen/medium_margin"
@@ -33,7 +33,7 @@
android:id="@+id/thread_mesage_attachments_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignStart="@+id/thread_message_body"
android:layout_toEndOf="@+id/thread_message_sender_photo"
android:orientation="vertical" />
<ImageView
@@ -44,7 +44,7 @@
android:layout_alignBottom="@+id/thread_mesage_attachments_holder"
android:layout_marginStart="@dimen/medium_margin"
android:layout_marginBottom="@dimen/activity_margin"
android:src="@drawable/ic_play_outline"
android:src="@drawable/ic_play_outline_vector"
android:visibility="gone" />
<TextView

View File

@@ -2,9 +2,7 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/thread_received_attachment_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/medium_margin"
android:paddingBottom="@dimen/medium_margin">
android:layout_height="wrap_content">
<TextView
android:id="@+id/thread_received_attachment_label"

View File

@@ -34,7 +34,7 @@
android:layout_alignBottom="@+id/thread_mesage_attachments_holder"
android:layout_marginEnd="@dimen/medium_margin"
android:layout_marginBottom="@dimen/activity_margin"
android:src="@drawable/ic_play_outline"
android:src="@drawable/ic_play_outline_vector"
android:visibility="gone" />
<TextView

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -0,0 +1,70 @@
<resources>
<string name="app_name">Paprastas SMS Siuntiklis</string>
<string name="app_launcher_name">SMS Siuntiklis</string>
<string name="type_a_message">Rašykite žinutę…</string>
<string name="message_not_sent">Žinutė neišsiųsta.</string>
<string name="add_person">Pridėti žmogų</string>
<string name="attachment">Priedas</string>
<string name="no_conversations_found">Nebuvo rasta išsaugotų pokalbių</string>
<string name="start_conversation">Pradėtipokalbį</string>
<!-- New conversation -->
<string name="new_conversation">Naujas pokalbis</string>
<string name="add_contact_or_number">Pridėti kontaktą arba numerį…</string>
<string name="suggestions">Pasiūlymai</string>
<!-- Notifications -->
<string name="channel_received_sms">Gautos žinutės</string>
<string name="new_message">Nauja žinutė</string>
<string name="mark_as_read">Mark as Read</string>
<!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Ar tikrai norite ištrinti visas šio pokalbio žinutes?</string>
<!-- Are you sure you want to delete 5 conversations? -->
<plurals name="delete_conversations">
<item quantity="one">%d pokalbis</item>
<item quantity="other">%d pokalbiai</item>
</plurals>
<!-- Are you sure you want to delete 5 messages? -->
<plurals name="delete_messages">
<item quantity="one">%d žinutė</item>
<item quantity="other">%d žinutės</item>
</plurals>
<!-- FAQ -->
<string name="faq_1_title">Why does the app require access to the internet?</string>
<string name="faq_1_text">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.</string>
<!-- Strings displayed only on Google Playstore. Optional, but good to have -->
<!-- App title has to have less than 50 characters. If you cannot squeeze it, just remove a part of it -->
<string name="app_title">Simple SMS Messenger - Manage messages easily</string>
<!-- Short description has to have max 80 characters -->
<string name="app_short_description">An easy and quick way of managing SMS and MMS messages without ads.</string>
<string name="app_long_description">
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.
<b>Check out the full suite of Simple Tools here:</b>
https://www.simplemobiletools.com
<b>Facebook:</b>
https://www.facebook.com/simplemobiletools
<b>Reddit:</b>
https://www.reddit.com/r/SimpleMobileTools
</string>
<!--
Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
</resources>

View File

@@ -4,18 +4,19 @@
<string name="type_a_message">Escreva uma mensagem…</string>
<string name="message_not_sent">Mensagem não enviada.</string>
<string name="add_person">Adicionar pessoa</string>
<string name="attachment">Attachment</string>
<string name="no_conversations_found">No stored conversations have been found</string>
<string name="start_conversation">Start a conversation</string>
<string name="attachment">Anexo</string>
<string name="no_conversations_found">Não foram encontradas conversas</string>
<string name="start_conversation">Iniciar uma conversa</string>
<!-- New message -->
<string name="new_conversation">New conversation</string>
<string name="new_conversation">Nova conversa</string>
<string name="add_contact_or_number">Adicionar contacto ou número…</string>
<string name="suggestions">Sugestões</string>
<!-- Notifications -->
<string name="channel_received_sms">SMS recebida</string>
<string name="new_message">Nova mensagem</string>
<string name="mark_as_read">Mark as Read</string>
<!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Tem a certeza de que deseja eliminar todas as mensagens desta conversa?</string>
@@ -39,21 +40,21 @@
<!-- Strings displayed only on Google Playstore. Optional, but good to have -->
<!-- App title has to have less than 50 characters. If you cannot squeeze it, just remove a part of it -->
<string name="app_title">Simple SMS Messenger - Manage messages easily</string>
<string name="app_title">Simple SMS Messenger - Gestão de mensagens</string>
<!-- Short description has to have max 80 characters -->
<string name="app_short_description">An easy and quick way of managing SMS and MMS messages without ads.</string>
<string name="app_short_description">Aplicação simples para gerir SMS e MMS, sem anúncios.</string>
<string name="app_long_description">
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.
<b>Check out the full suite of Simple Tools here:</b>
<b>Consulte o conjunto completo de aplicações Simple aqui:</b>
https://www.simplemobiletools.com
<b>Facebook:</b>

View File

@@ -16,6 +16,7 @@
<!-- Notifications -->
<string name="channel_received_sms">Получено SMS</string>
<string name="new_message">Новое сообщение</string>
<string name="mark_as_read">Mark as Read</string>
<!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Вы уверены, что хотите удалить все сообщения в этой переписке?</string>

View File

@@ -16,6 +16,7 @@
<!-- Notifications -->
<string name="channel_received_sms">Prijatá SMS</string>
<string name="new_message">Nová správa</string>
<string name="mark_as_read">Označiť ako prečítané</string>
<!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Ste si istý, že chcete odstrániť všetky správy tejto konverzácie?</string>

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="notification_large_icon_size">72dp</dimen>
<dimen name="avatar_size">40dp</dimen>
<dimen name="bigger_avatar_size">64dp</dimen>
<dimen name="play_outline_size">36dp</dimen>
<dimen name="attachment_preview_size">60dp</dimen>

View File

@@ -16,6 +16,7 @@
<!-- Notifications -->
<string name="channel_received_sms">Received SMS</string>
<string name="new_message">New message</string>
<string name="mark_as_read">Mark as Read</string>
<!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Are you sure you want to delete all messages of this conversation?</string>

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,2 @@
* Allow moving the app on an SD card
* Added some performance, UI and stability improvements

View File

@@ -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.
<b>Consulte o conjunto completo de aplicações Simple aqui:</b>
https://www.simplemobiletools.com
<b>Facebook:</b>
https://www.facebook.com/simplemobiletools
<b>Reddit:</b>
https://www.reddit.com/r/SimpleMobileTools

View File

@@ -0,0 +1 @@
Aplicação simples para gerir SMS e MMS, sem anúncios.

View File

@@ -0,0 +1 @@
Simple SMS Messenger - Gestão de mensagens

View File

@@ -0,0 +1,18 @@
SMS/MMS-сообщения — это отличный способ оставаться на связи со своими родственниками. Приложение также правильно обрабатывает групповые сообщения и блокировку номеров на Android 7+.
Поддерживается множество вариантов отображения даты, чтобы вам было удобно пользоваться приложением. Также вы можете переключаться между 12- и 24-часовым форматом времени.
Наше приложение имеет очень маленький размер по сравнению с конкурентами, что делает его загрузку такой быстрой.
Приложение выпускается с материальным дизайном и тёмной темой по умолчанию, что обеспечивает удобное использование. Отсутствие доступа в интернет обеспечивает вам больше приватности, безопасности и стабильности, чем в других приложениях.
Не содержит рекламы или ненужных разрешений. Полностью открытый исходный код, есть возможность настроить цвета интерфейса.
<b>Ознакомьтесь с полным набором инструментов серии Simple здесь:</b>
https://www.simplemobiletools.com
<b>Facebook:</b>
https://www.facebook.com/simplemobiletools
<b>Reddit:</b>
https://www.reddit.com/r/SimpleMobileTools

View File

@@ -0,0 +1 @@
Простой и быстрый способ управления сообщениями SMS и MMS без рекламы.

View File

@@ -0,0 +1 @@
Simple SMS Messenger - лёгкое управление SMS