Merge pull request #3 from SimpleMobileTools/master

upd
This commit is contained in:
solokot 2020-06-07 14:36:58 +03:00 committed by GitHub
commit 0c2186da24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 734 additions and 95 deletions

View File

@ -1,6 +1,29 @@
Changelog Changelog
========== ==========
Version 5.2.0 *(2020-05-30)*
----------------------------
* Cache the conversations shown on the main screen to drastically improve their loading speed
* Allow adding newlines with the Enter button
* Properly handle a new View third party intent
* Added some other translation, UI and stability improvements
Version 5.1.4 *(2020-05-23)*
----------------------------
* Improved the performance at checking if a number is blocked
* Use capital letters at sentences at writing a message
* Allow blocking numbers from the main screen
* Fixed a glitch with adding new people to a conversation
Version 5.1.3 *(2020-05-20)*
----------------------------
* Adding support for accessing private contacts stored in Simple Contacts Pro (to be added soon)
* Make sure the conversations are sorted properly
* Added some other UI and translation improvements
Version 5.1.2 *(2020-05-13)* Version 5.1.2 *(2020-05-13)*
---------------------------- ----------------------------

View File

@ -21,6 +21,7 @@ Reddit:
https://www.reddit.com/r/SimpleMobileTools https://www.reddit.com/r/SimpleMobileTools
<a href='https://play.google.com/store/apps/details?id=com.simplemobiletools.smsmessenger'><img src='https://simplemobiletools.com/assets/images/google-play.png' alt='Get it on Google Play' height=45/></a> <a href='https://play.google.com/store/apps/details?id=com.simplemobiletools.smsmessenger'><img src='https://simplemobiletools.com/assets/images/google-play.png' alt='Get it on Google Play' height=45/></a>
<a href='https://f-droid.org/packages/com.simplemobiletools.smsmessenger'><img src='https://simplemobiletools.com/assets/images/f-droid.png' alt='Get it on F-Droid' height='45' /></a>
<div style="display:flex;"> <div style="display:flex;">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app_1.jpg" width="30%"> <img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app_1.jpg" width="30%">

View File

@ -1,6 +1,7 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
def keystorePropertiesFile = rootProject.file("keystore.properties") def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties() def keystoreProperties = new Properties()
@ -16,8 +17,8 @@ android {
applicationId "com.simplemobiletools.smsmessenger" applicationId "com.simplemobiletools.smsmessenger"
minSdkVersion 22 minSdkVersion 22
targetSdkVersion 29 targetSdkVersion 29
versionCode 5 versionCode 9
versionName "5.1.2" versionName "5.2.0"
setProperty("archivesBaseName", "sms-messenger") setProperty("archivesBaseName", "sms-messenger")
} }
@ -56,8 +57,12 @@ android {
} }
dependencies { dependencies {
implementation 'com.simplemobiletools:commons:5.27.29' implementation 'com.simplemobiletools:commons:5.28.25'
implementation 'org.greenrobot:eventbus:3.2.0' implementation 'org.greenrobot:eventbus:3.2.0'
implementation 'com.klinkerapps:android-smsmms:5.2.6' implementation 'com.klinkerapps:android-smsmms:5.2.6'
implementation 'com.github.tibbi:IndicatorFastScroll:08f512858a' implementation 'com.github.tibbi:IndicatorFastScroll:08f512858a'
kapt "androidx.room:room-compiler:2.2.5"
implementation "androidx.room:room-runtime:2.2.5"
annotationProcessor "androidx.room:room-compiler:2.2.5"
} }

View File

@ -40,12 +40,12 @@
<activity <activity
android:name=".activities.NewConversationActivity" android:name=".activities.NewConversationActivity"
android:noHistory="true"
android:parentActivityName=".activities.MainActivity"> android:parentActivityName=".activities.MainActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SENDTO" /> <action android:name="android.intent.action.SENDTO" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />

View File

@ -19,6 +19,7 @@ import com.simplemobiletools.smsmessenger.BuildConfig
import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.adapters.ConversationsAdapter import com.simplemobiletools.smsmessenger.adapters.ConversationsAdapter
import com.simplemobiletools.smsmessenger.extensions.config import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.extensions.conversationsDB
import com.simplemobiletools.smsmessenger.extensions.getConversations import com.simplemobiletools.smsmessenger.extensions.getConversations
import com.simplemobiletools.smsmessenger.helpers.THREAD_ID import com.simplemobiletools.smsmessenger.helpers.THREAD_ID
import com.simplemobiletools.smsmessenger.helpers.THREAD_TITLE import com.simplemobiletools.smsmessenger.helpers.THREAD_TITLE
@ -128,9 +129,9 @@ class MainActivity : SimpleActivity() {
handlePermission(PERMISSION_SEND_SMS) { handlePermission(PERMISSION_SEND_SMS) {
if (it) { if (it) {
handlePermission(PERMISSION_READ_CONTACTS) { handlePermission(PERMISSION_READ_CONTACTS) {
initMessenger()
bus = EventBus.getDefault() bus = EventBus.getDefault()
bus!!.register(this) bus!!.register(this)
initMessenger()
} }
} else { } else {
finish() finish()
@ -144,26 +145,7 @@ class MainActivity : SimpleActivity() {
private fun initMessenger() { private fun initMessenger() {
storeStateVariables() storeStateVariables()
ensureBackgroundThread { getCachedConversations()
val conversations = getConversations()
runOnUiThread {
val hasConversations = conversations.isNotEmpty()
conversations_list.beVisibleIf(hasConversations)
no_conversations_placeholder.beVisibleIf(!hasConversations)
no_conversations_placeholder_2.beVisibleIf(!hasConversations)
ConversationsAdapter(this, conversations, conversations_list, conversations_fastscroller) {
Intent(this, ThreadActivity::class.java).apply {
putExtra(THREAD_ID, (it as Conversation).id)
putExtra(THREAD_TITLE, it.title)
startActivity(this)
}
}.apply {
conversations_list.adapter = this
}
}
}
no_conversations_placeholder_2.setOnClickListener { no_conversations_placeholder_2.setOnClickListener {
launchNewConversation() launchNewConversation()
@ -174,6 +156,80 @@ class MainActivity : SimpleActivity() {
} }
} }
private fun getCachedConversations() {
ensureBackgroundThread {
val conversations = conversationsDB.getAll().sortedByDescending { it.date }.toMutableList() as ArrayList<Conversation>
runOnUiThread {
setupConversations(conversations)
getNewConversations(conversations)
}
}
}
private fun getNewConversations(cachedConversations: ArrayList<Conversation>) {
val privateCursor = getMyContactsContentProviderCursorLoader().loadInBackground()
ensureBackgroundThread {
val conversations = getConversations()
// check if no message came from a privately stored contact in Simple Contacts
val privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor)
if (privateContacts.isNotEmpty()) {
conversations.filter { it.title == it.phoneNumber }.forEach { conversation ->
privateContacts.firstOrNull { it.phoneNumber == conversation.phoneNumber }?.apply {
conversation.title = name
conversation.photoUri = photoUri
}
}
}
runOnUiThread {
setupConversations(conversations)
}
conversations.forEach { clonedConversation ->
if (!cachedConversations.map { it.thread_id }.contains(clonedConversation.thread_id)) {
conversationsDB.insertOrUpdate(clonedConversation)
cachedConversations.add(clonedConversation)
}
}
cachedConversations.forEach { cachedConversation ->
if (!conversations.map { it.thread_id }.contains(cachedConversation.thread_id)) {
conversationsDB.delete(cachedConversation.id!!)
}
}
cachedConversations.forEach { cachedConversation ->
val conv = conversations.firstOrNull { it.thread_id == cachedConversation.thread_id && it.getStringToCompare() != cachedConversation.getStringToCompare() }
if (conv != null) {
conversationsDB.insertOrUpdate(conv)
}
}
}
}
private fun setupConversations(conversations: ArrayList<Conversation>) {
val hasConversations = conversations.isNotEmpty()
conversations_list.beVisibleIf(hasConversations)
no_conversations_placeholder.beVisibleIf(!hasConversations)
no_conversations_placeholder_2.beVisibleIf(!hasConversations)
val currAdapter = conversations_list.adapter
if (currAdapter == null) {
ConversationsAdapter(this, conversations, conversations_list, conversations_fastscroller) {
Intent(this, ThreadActivity::class.java).apply {
putExtra(THREAD_ID, (it as Conversation).thread_id)
putExtra(THREAD_TITLE, it.title)
startActivity(this)
}
}.apply {
conversations_list.adapter = this
}
} else {
(currAdapter as ConversationsAdapter).updateConversations(conversations)
}
}
private fun launchNewConversation() { private fun launchNewConversation() {
Intent(this, NewConversationActivity::class.java).apply { Intent(this, NewConversationActivity::class.java).apply {
startActivity(this) startActivity(this)

View File

@ -7,6 +7,7 @@ import android.view.Menu
import android.view.WindowManager import android.view.WindowManager
import com.reddit.indicatorfastscroll.FastScrollItemIndicator import com.reddit.indicatorfastscroll.FastScrollItemIndicator
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.MyContactsContentProvider
import com.simplemobiletools.commons.helpers.PERMISSION_READ_CONTACTS import com.simplemobiletools.commons.helpers.PERMISSION_READ_CONTACTS
import com.simplemobiletools.commons.helpers.SimpleContactsHelper import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.commons.helpers.ensureBackgroundThread
@ -17,7 +18,7 @@ import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.extensions.getSuggestedContacts import com.simplemobiletools.smsmessenger.extensions.getSuggestedContacts
import com.simplemobiletools.smsmessenger.extensions.getThreadId import com.simplemobiletools.smsmessenger.extensions.getThreadId
import com.simplemobiletools.smsmessenger.helpers.* import com.simplemobiletools.smsmessenger.helpers.*
import kotlinx.android.synthetic.main.activity_conversation.* import kotlinx.android.synthetic.main.activity_new_conversation.*
import kotlinx.android.synthetic.main.item_suggested_contact.view.* import kotlinx.android.synthetic.main.item_suggested_contact.view.*
import java.net.URLDecoder import java.net.URLDecoder
import java.util.* import java.util.*
@ -25,10 +26,11 @@ import kotlin.collections.ArrayList
class NewConversationActivity : SimpleActivity() { class NewConversationActivity : SimpleActivity() {
private var allContacts = ArrayList<SimpleContact>() private var allContacts = ArrayList<SimpleContact>()
private var privateContacts = ArrayList<SimpleContact>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_conversation) setContentView(R.layout.activity_new_conversation)
title = getString(R.string.new_conversation) title = getString(R.string.new_conversation)
updateTextColors(new_conversation_holder) updateTextColors(new_conversation_holder)
@ -93,9 +95,10 @@ class NewConversationActivity : SimpleActivity() {
} }
private fun isThirdPartyIntent(): Boolean { private fun isThirdPartyIntent(): Boolean {
if (intent.action == Intent.ACTION_SENDTO && intent.dataString != null) { if ((intent.action == Intent.ACTION_SENDTO || intent.action == Intent.ACTION_SEND || intent.action == Intent.ACTION_VIEW) && intent.dataString != null) {
val number = intent.dataString!!.removePrefix("sms:").removePrefix("smsto:").removePrefix("mms").removePrefix("mmsto:").trim() val number = intent.dataString!!.removePrefix("sms:").removePrefix("smsto:").removePrefix("mms").removePrefix("mmsto:").trim()
launchThreadActivity(URLDecoder.decode(number), "") launchThreadActivity(URLDecoder.decode(number), "")
finish()
return true return true
} }
return false return false
@ -103,9 +106,14 @@ class NewConversationActivity : SimpleActivity() {
private fun fetchContacts() { private fun fetchContacts() {
fillSuggestedContacts { fillSuggestedContacts {
SimpleContactsHelper(this).getAvailableContacts { SimpleContactsHelper(this).getAvailableContacts(false) {
allContacts = it allContacts = it
if (privateContacts.isNotEmpty()) {
allContacts.addAll(privateContacts)
allContacts.sort()
}
runOnUiThread { runOnUiThread {
setupAdapter(allContacts) setupAdapter(allContacts)
} }
@ -135,8 +143,10 @@ class NewConversationActivity : SimpleActivity() {
} }
private fun fillSuggestedContacts(callback: () -> Unit) { private fun fillSuggestedContacts(callback: () -> Unit) {
val privateCursor = getMyContactsContentProviderCursorLoader().loadInBackground()
ensureBackgroundThread { ensureBackgroundThread {
val suggestions = getSuggestedContacts() privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor)
val suggestions = getSuggestedContacts(privateContacts)
runOnUiThread { runOnUiThread {
suggestions_holder.removeAllViews() suggestions_holder.removeAllViews()
if (suggestions.isEmpty()) { if (suggestions.isEmpty()) {

View File

@ -30,10 +30,7 @@ import com.klinker.android.send_message.Settings
import com.klinker.android.send_message.Transaction import com.klinker.android.send_message.Transaction
import com.simplemobiletools.commons.dialogs.ConfirmationDialog import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.PERMISSION_READ_PHONE_STATE import com.simplemobiletools.commons.helpers.*
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.commons.models.SimpleContact
import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.adapters.AutoCompleteTextViewAdapter import com.simplemobiletools.smsmessenger.adapters.AutoCompleteTextViewAdapter
@ -57,6 +54,7 @@ class ThreadActivity : SimpleActivity() {
private var threadItems = ArrayList<ThreadItem>() private var threadItems = ArrayList<ThreadItem>()
private var bus: EventBus? = null private var bus: EventBus? = null
private var participants = ArrayList<SimpleContact>() private var participants = ArrayList<SimpleContact>()
private var privateContacts = ArrayList<SimpleContact>()
private var messages = ArrayList<Message>() private var messages = ArrayList<Message>()
private val availableSIMCards = ArrayList<SIMCard>() private val availableSIMCards = ArrayList<SIMCard>()
private var attachmentUris = LinkedHashSet<Uri>() private var attachmentUris = LinkedHashSet<Uri>()
@ -89,6 +87,7 @@ class ThreadActivity : SimpleActivity() {
} }
private fun setupThread() { private fun setupThread() {
val privateCursor = getMyContactsContentProviderCursorLoader().loadInBackground()
ensureBackgroundThread { ensureBackgroundThread {
messages = getMessages(threadId) messages = getMessages(threadId)
participants = if (messages.isEmpty()) { participants = if (messages.isEmpty()) {
@ -97,6 +96,25 @@ class ThreadActivity : SimpleActivity() {
messages.first().participants messages.first().participants
} }
// check if no participant came from a privately stored contact in Simple Contacts
privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor)
if (privateContacts.isNotEmpty()) {
val senderNumbersToReplace = HashMap<String, String>()
participants.filter { it.name == it.phoneNumber }.forEach { participant ->
privateContacts.firstOrNull { it.phoneNumber == participant.phoneNumber }?.apply {
senderNumbersToReplace[participant.phoneNumber] = name
participant.name = name
participant.photoUri = photoUri
}
}
messages.forEach { message ->
if (senderNumbersToReplace.keys.contains(message.senderName)) {
message.senderName = senderNumbersToReplace[message.senderName]!!
}
}
}
if (participants.isEmpty()) { if (participants.isEmpty()) {
val name = intent.getStringExtra(THREAD_TITLE) ?: "" val name = intent.getStringExtra(THREAD_TITLE) ?: ""
val number = intent.getStringExtra(THREAD_NUMBER) val number = intent.getStringExtra(THREAD_NUMBER)
@ -202,9 +220,10 @@ class ThreadActivity : SimpleActivity() {
thread_messages_list.adapter = adapter thread_messages_list.adapter = adapter
} }
SimpleContactsHelper(this).getAvailableContacts { SimpleContactsHelper(this).getAvailableContacts(false) { contacts ->
contacts.addAll(privateContacts)
runOnUiThread { runOnUiThread {
val adapter = AutoCompleteTextViewAdapter(this, it) val adapter = AutoCompleteTextViewAdapter(this, contacts)
add_contact_or_number.setAdapter(adapter) add_contact_or_number.setAdapter(adapter)
add_contact_or_number.imeOptions = EditorInfo.IME_ACTION_NEXT add_contact_or_number.imeOptions = EditorInfo.IME_ACTION_NEXT
add_contact_or_number.setOnItemClickListener { _, _, position, _ -> add_contact_or_number.setOnItemClickListener { _, _, position, _ ->
@ -219,7 +238,7 @@ class ThreadActivity : SimpleActivity() {
} }
} }
confirm_inserted_number.setOnClickListener { confirm_inserted_number?.setOnClickListener {
val number = add_contact_or_number.value val number = add_contact_or_number.value
val contact = SimpleContact(number.hashCode(), number.hashCode(), number, "", number) val contact = SimpleContact(number.hashCode(), number.hashCode(), number, "", number)
addSelectedContact(contact) addSelectedContact(contact)
@ -307,10 +326,9 @@ class ThreadActivity : SimpleActivity() {
} }
private fun blockNumber() { private fun blockNumber() {
val baseString = R.string.block_confirmation val numbers = participants.map { it.phoneNumber }
val numbers = participants.map { it.phoneNumber }.toTypedArray()
val numbersString = TextUtils.join(", ", numbers) val numbersString = TextUtils.join(", ", numbers)
val question = String.format(resources.getString(baseString), numbersString) val question = String.format(resources.getString(R.string.block_confirmation), numbersString)
ConfirmationDialog(this, question) { ConfirmationDialog(this, question) {
ensureBackgroundThread { ensureBackgroundThread {
@ -325,11 +343,15 @@ class ThreadActivity : SimpleActivity() {
private fun askConfirmDelete() { private fun askConfirmDelete() {
ConfirmationDialog(this, getString(R.string.delete_whole_conversation_confirmation)) { ConfirmationDialog(this, getString(R.string.delete_whole_conversation_confirmation)) {
ensureBackgroundThread {
deleteConversation(threadId) deleteConversation(threadId)
runOnUiThread {
refreshMessages() refreshMessages()
finish() finish()
} }
} }
}
}
private fun managePeople() { private fun managePeople() {
if (thread_add_contacts.isVisible()) { if (thread_add_contacts.isVisible()) {
@ -399,6 +421,7 @@ class ThreadActivity : SimpleActivity() {
if (!it.read) { if (!it.read) {
hadUnreadItems = true hadUnreadItems = true
markMessageRead(it.id, it.isMMS) markMessageRead(it.id, it.isMMS)
conversationsDB.markRead(threadId.toLong())
} }
} }
@ -450,7 +473,6 @@ class ThreadActivity : SimpleActivity() {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean { override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
attachmentView.thread_attachment_preview.beGone() attachmentView.thread_attachment_preview.beGone()
attachmentView.thread_remove_attachment.beGone() attachmentView.thread_remove_attachment.beGone()
showErrorToast(e?.localizedMessage ?: "")
return false return false
} }
@ -497,9 +519,13 @@ class ThreadActivity : SimpleActivity() {
if (attachmentUris.isNotEmpty()) { if (attachmentUris.isNotEmpty()) {
for (uri in attachmentUris) { for (uri in attachmentUris) {
try {
val byteArray = contentResolver.openInputStream(uri)?.readBytes() ?: continue val byteArray = contentResolver.openInputStream(uri)?.readBytes() ?: continue
val mimeType = contentResolver.getType(uri) ?: continue val mimeType = contentResolver.getType(uri) ?: continue
message.addMedia(byteArray, mimeType) message.addMedia(byteArray, mimeType)
} catch (e: Exception) {
showErrorToast(e)
}
} }
} }

View File

@ -13,7 +13,6 @@ import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.SimpleActivity import com.simplemobiletools.smsmessenger.activities.SimpleActivity
class AutoCompleteTextViewAdapter(val activity: SimpleActivity, val contacts: ArrayList<SimpleContact>) : ArrayAdapter<SimpleContact>(activity, 0, contacts) { class AutoCompleteTextViewAdapter(val activity: SimpleActivity, val contacts: ArrayList<SimpleContact>) : ArrayAdapter<SimpleContact>(activity, 0, contacts) {
var resultList = ArrayList<SimpleContact>() var resultList = ArrayList<SimpleContact>()
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
@ -25,6 +24,12 @@ class AutoCompleteTextViewAdapter(val activity: SimpleActivity, val contacts: Ar
listItem!!.apply { listItem!!.apply {
tag = contact.name.isNotEmpty() tag = contact.name.isNotEmpty()
// clickable and focusable properties seem to break Autocomplete clicking, so remove them
findViewById<View>(R.id.item_contact_frame).apply {
isClickable = false
isFocusable = false
}
findViewById<TextView>(R.id.item_contact_name).text = contact.name findViewById<TextView>(R.id.item_contact_name).text = contact.name
findViewById<TextView>(R.id.item_contact_number).text = contact.phoneNumber findViewById<TextView>(R.id.item_contact_number).text = contact.phoneNumber

View File

@ -2,6 +2,7 @@ package com.simplemobiletools.smsmessenger.adapters
import android.content.Intent import android.content.Intent
import android.graphics.Typeface import android.graphics.Typeface
import android.text.TextUtils
import android.view.Menu import android.view.Menu
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -9,11 +10,13 @@ import android.widget.TextView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
import com.simplemobiletools.commons.dialogs.ConfirmationDialog import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.extensions.addBlockedNumber
import com.simplemobiletools.commons.extensions.formatDateOrTime import com.simplemobiletools.commons.extensions.formatDateOrTime
import com.simplemobiletools.commons.extensions.toast import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.helpers.KEY_PHONE import com.simplemobiletools.commons.helpers.KEY_PHONE
import com.simplemobiletools.commons.helpers.SimpleContactsHelper import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.helpers.isNougatPlus
import com.simplemobiletools.commons.views.FastScroller import com.simplemobiletools.commons.views.FastScroller
import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.R
@ -35,6 +38,7 @@ class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayLis
override fun prepareActionMode(menu: Menu) { override fun prepareActionMode(menu: Menu) {
menu.apply { menu.apply {
findItem(R.id.cab_add_number_to_contact).isVisible = isOneItemSelected() && getSelectedItems().firstOrNull()?.isGroupConversation == false findItem(R.id.cab_add_number_to_contact).isVisible = isOneItemSelected() && getSelectedItems().firstOrNull()?.isGroupConversation == false
findItem(R.id.cab_block_number).isVisible = isNougatPlus()
} }
} }
@ -45,6 +49,7 @@ class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayLis
when (id) { when (id) {
R.id.cab_add_number_to_contact -> addNumberToContact() R.id.cab_add_number_to_contact -> addNumberToContact()
R.id.cab_block_number -> askConfirmBlock()
R.id.cab_select_all -> selectAll() R.id.cab_select_all -> selectAll()
R.id.cab_delete -> askConfirmDelete() R.id.cab_delete -> askConfirmDelete()
} }
@ -54,9 +59,9 @@ class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayLis
override fun getIsItemSelectable(position: Int) = true override fun getIsItemSelectable(position: Int) = true
override fun getItemSelectionKey(position: Int) = conversations.getOrNull(position)?.id override fun getItemSelectionKey(position: Int) = conversations.getOrNull(position)?.thread_id
override fun getItemKeyPosition(key: Int) = conversations.indexOfFirst { it.id == key } override fun getItemKeyPosition(key: Int) = conversations.indexOfFirst { it.thread_id == key }
override fun onActionModeCreated() {} override fun onActionModeCreated() {}
@ -74,6 +79,37 @@ class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayLis
override fun getItemCount() = conversations.size override fun getItemCount() = conversations.size
private fun askConfirmBlock() {
val numbers = getSelectedItems().distinctBy { it.phoneNumber }.map { it.phoneNumber }
val numbersString = TextUtils.join(", ", numbers)
val question = String.format(resources.getString(R.string.block_confirmation), numbersString)
ConfirmationDialog(activity, question) {
blockNumbers()
}
}
private fun blockNumbers() {
if (selectedKeys.isEmpty()) {
return
}
val numbersToBlock = getSelectedItems()
val positions = getSelectedItemPositions()
conversations.removeAll(numbersToBlock)
ensureBackgroundThread {
numbersToBlock.map { it.phoneNumber }.forEach { number ->
activity.addBlockedNumber(number)
}
activity.runOnUiThread {
removeSelectedItems(positions)
finishActMode()
}
}
}
private fun askConfirmDelete() { private fun askConfirmDelete() {
val itemsCnt = selectedKeys.size val itemsCnt = selectedKeys.size
val items = resources.getQuantityString(R.plurals.delete_conversations, itemsCnt, itemsCnt) val items = resources.getQuantityString(R.plurals.delete_conversations, itemsCnt, itemsCnt)
@ -93,10 +129,10 @@ class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayLis
return return
} }
val conversationsToRemove = conversations.filter { selectedKeys.contains(it.id) } as ArrayList<Conversation> val conversationsToRemove = conversations.filter { selectedKeys.contains(it.thread_id) } as ArrayList<Conversation>
val positions = getSelectedItemPositions() val positions = getSelectedItemPositions()
conversationsToRemove.forEach { conversationsToRemove.forEach {
activity.deleteConversation(it.id) activity.deleteConversation(it.thread_id)
} }
conversations.removeAll(conversationsToRemove) conversations.removeAll(conversationsToRemove)
@ -128,7 +164,7 @@ class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayLis
} }
} }
private fun getSelectedItems() = conversations.filter { selectedKeys.contains(it.id) } as ArrayList<Conversation> private fun getSelectedItems() = conversations.filter { selectedKeys.contains(it.thread_id) } as ArrayList<Conversation>
override fun onViewRecycled(holder: ViewHolder) { override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder) super.onViewRecycled(holder)
@ -137,9 +173,18 @@ class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayLis
} }
} }
fun updateConversations(newConversations: ArrayList<Conversation>) {
val oldHashCode = conversations.hashCode()
val newHashCode = newConversations.hashCode()
if (newHashCode != oldHashCode) {
conversations = newConversations
notifyDataSetChanged()
}
}
private fun setupView(view: View, conversation: Conversation) { private fun setupView(view: View, conversation: Conversation) {
view.apply { view.apply {
conversation_frame.isSelected = selectedKeys.contains(conversation.id) conversation_frame.isSelected = selectedKeys.contains(conversation.thread_id)
conversation_address.text = conversation.title conversation_address.text = conversation.title
conversation_body_short.text = conversation.snippet conversation_body_short.text = conversation.snippet

View File

@ -44,6 +44,7 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) {
private val roundedCornersRadius = resources.getDimension(R.dimen.normal_margin).toInt() private val roundedCornersRadius = resources.getDimension(R.dimen.normal_margin).toInt()
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
private val hasMultipleSIMCards = SubscriptionManager.from(activity).activeSubscriptionInfoList?.size ?: 0 > 1 private val hasMultipleSIMCards = SubscriptionManager.from(activity).activeSubscriptionInfoList?.size ?: 0 > 1

View File

@ -0,0 +1,30 @@
package com.simplemobiletools.smsmessenger.databases
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.simplemobiletools.smsmessenger.interfaces.ConversationsDao
import com.simplemobiletools.smsmessenger.models.Conversation
@Database(entities = [(Conversation::class)], version = 1)
abstract class MessagesDatabase : RoomDatabase() {
abstract fun ConversationsDao(): ConversationsDao
companion object {
private var db: MessagesDatabase? = null
fun getInstance(context: Context): MessagesDatabase {
if (db == null) {
synchronized(MessagesDatabase::class) {
if (db == null) {
db = Room.databaseBuilder(context.applicationContext, MessagesDatabase::class.java, "conversations.db")
.build()
}
}
}
return db!!
}
}
}

View File

@ -2,5 +2,8 @@ package com.simplemobiletools.smsmessenger.extensions
import android.text.TextUtils import android.text.TextUtils
import com.simplemobiletools.commons.models.SimpleContact import com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.smsmessenger.models.Conversation
fun ArrayList<SimpleContact>.getThreadTitle() = TextUtils.join(", ", map { it.name }.toTypedArray()) fun ArrayList<SimpleContact>.getThreadTitle() = TextUtils.join(", ", map { it.name }.toTypedArray())
fun ArrayList<Conversation>.getHashToCompare() = map { it.getStringToCompare() }.hashCode()

View File

@ -22,7 +22,9 @@ import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.SimpleContact import com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.ThreadActivity import com.simplemobiletools.smsmessenger.activities.ThreadActivity
import com.simplemobiletools.smsmessenger.databases.MessagesDatabase
import com.simplemobiletools.smsmessenger.helpers.* import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.interfaces.ConversationsDao
import com.simplemobiletools.smsmessenger.models.* import com.simplemobiletools.smsmessenger.models.*
import com.simplemobiletools.smsmessenger.receivers.MarkAsReadReceiver import com.simplemobiletools.smsmessenger.receivers.MarkAsReadReceiver
import java.util.* import java.util.*
@ -30,6 +32,10 @@ import kotlin.collections.ArrayList
val Context.config: Config get() = Config.newInstance(applicationContext) val Context.config: Config get() = Config.newInstance(applicationContext)
fun Context.getMessagessDB() = MessagesDatabase.getInstance(this)
val Context.conversationsDB: ConversationsDao get() = getMessagessDB().ConversationsDao()
fun Context.getMessages(threadId: Int): ArrayList<Message> { fun Context.getMessages(threadId: Int): ArrayList<Message> {
val uri = Sms.CONTENT_URI val uri = Sms.CONTENT_URI
val projection = arrayOf( val projection = arrayOf(
@ -48,6 +54,7 @@ fun Context.getMessages(threadId: Int): ArrayList<Message> {
val sortOrder = "${Sms._ID} DESC LIMIT 100" val sortOrder = "${Sms._ID} DESC LIMIT 100"
val blockStatus = HashMap<String, Boolean>() val blockStatus = HashMap<String, Boolean>()
val blockedNumbers = getBlockedNumbers()
var messages = ArrayList<Message>() var messages = ArrayList<Message>()
queryCursor(uri, projection, selection, selectionArgs, sortOrder, showErrors = true) { cursor -> queryCursor(uri, projection, selection, selectionArgs, sortOrder, showErrors = true) { cursor ->
val senderNumber = cursor.getStringValue(Sms.ADDRESS) ?: return@queryCursor val senderNumber = cursor.getStringValue(Sms.ADDRESS) ?: return@queryCursor
@ -55,7 +62,7 @@ fun Context.getMessages(threadId: Int): ArrayList<Message> {
val isNumberBlocked = if (blockStatus.containsKey(senderNumber)) { val isNumberBlocked = if (blockStatus.containsKey(senderNumber)) {
blockStatus[senderNumber]!! blockStatus[senderNumber]!!
} else { } else {
val isBlocked = isNumberBlocked(senderNumber) val isBlocked = isNumberBlocked(senderNumber, blockedNumbers)
blockStatus[senderNumber] = isBlocked blockStatus[senderNumber] = isBlocked
isBlocked isBlocked
} }
@ -148,7 +155,7 @@ fun Context.getMMS(threadId: Int? = null, sortOrder: String? = null): ArrayList<
messages.add(message) messages.add(message)
participants.forEach { participants.forEach {
contactsMap.put(it.rawId, it) contactsMap[it.rawId] = it
} }
} }
@ -173,7 +180,7 @@ fun Context.getMMSSender(msgId: Int): String {
return "" return ""
} }
fun Context.getConversations(): ArrayList<Conversation> { fun Context.getConversations(threadId: Long? = null): ArrayList<Conversation> {
val uri = Uri.parse("${Threads.CONTENT_URI}?simple=true") val uri = Uri.parse("${Threads.CONTENT_URI}?simple=true")
val projection = arrayOf( val projection = arrayOf(
Threads._ID, Threads._ID,
@ -183,10 +190,19 @@ fun Context.getConversations(): ArrayList<Conversation> {
Threads.RECIPIENT_IDS Threads.RECIPIENT_IDS
) )
val selection = "${Threads.MESSAGE_COUNT} > ?" var selection = "${Threads.MESSAGE_COUNT} > ?"
val selectionArgs = arrayOf("0") var selectionArgs = arrayOf("0")
if (threadId != null) {
selection += " AND ${Threads._ID} = ?"
selectionArgs = arrayOf("0", threadId.toString())
}
val sortOrder = "${Threads.DATE} DESC"
val conversations = ArrayList<Conversation>() val conversations = ArrayList<Conversation>()
queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor -> val simpleContactHelper = SimpleContactsHelper(this)
val blockedNumbers = getBlockedNumbers()
queryCursor(uri, projection, selection, selectionArgs, sortOrder, true) { cursor ->
val id = cursor.getIntValue(Threads._ID) val id = cursor.getIntValue(Threads._ID)
var snippet = cursor.getStringValue(Threads.SNIPPET) ?: "" var snippet = cursor.getStringValue(Threads.SNIPPET) ?: ""
if (snippet.isEmpty()) { if (snippet.isEmpty()) {
@ -198,22 +214,24 @@ fun Context.getConversations(): ArrayList<Conversation> {
date /= 1000 date /= 1000
} }
val read = cursor.getIntValue(Threads.READ) == 1
val rawIds = cursor.getStringValue(Threads.RECIPIENT_IDS) val rawIds = cursor.getStringValue(Threads.RECIPIENT_IDS)
val recipientIds = rawIds.split(" ").filter { it.areDigitsOnly() }.map { it.toInt() }.toMutableList() val recipientIds = rawIds.split(" ").filter { it.areDigitsOnly() }.map { it.toInt() }.toMutableList()
val phoneNumbers = getThreadPhoneNumbers(recipientIds) val phoneNumbers = getThreadPhoneNumbers(recipientIds)
if (phoneNumbers.any { isNumberBlocked(it) }) { if (phoneNumbers.any { isNumberBlocked(it, blockedNumbers) }) {
return@queryCursor return@queryCursor
} }
val names = getThreadContactNames(phoneNumbers) val names = getThreadContactNames(phoneNumbers)
val title = TextUtils.join(", ", names.toTypedArray()) val title = TextUtils.join(", ", names.toTypedArray())
val photoUri = if (phoneNumbers.size == 1) SimpleContactsHelper(this).getPhotoUriFromPhoneNumber(phoneNumbers.first()) else "" val photoUri = if (phoneNumbers.size == 1) simpleContactHelper.getPhotoUriFromPhoneNumber(phoneNumbers.first()) else ""
val isGroupConversation = phoneNumbers.size > 1 val isGroupConversation = phoneNumbers.size > 1
val conversation = Conversation(id, snippet, date.toInt(), read, title, photoUri, isGroupConversation, phoneNumbers.first()) val read = cursor.getIntValue(Threads.READ) == 1
val conversation = Conversation(null, id, snippet, date.toInt(), read, title, photoUri, isGroupConversation, phoneNumbers.first())
conversations.add(conversation) conversations.add(conversation)
} }
conversations.sortByDescending { it.date }
return conversations return conversations
} }
@ -363,7 +381,7 @@ fun Context.getPhoneNumberFromAddressId(canonicalAddressId: Int): String {
return "" return ""
} }
fun Context.getSuggestedContacts(): ArrayList<SimpleContact> { fun Context.getSuggestedContacts(privateContacts: ArrayList<SimpleContact>): ArrayList<SimpleContact> {
val contacts = ArrayList<SimpleContact>() val contacts = ArrayList<SimpleContact>()
val uri = Sms.CONTENT_URI val uri = Sms.CONTENT_URI
val projection = arrayOf( val projection = arrayOf(
@ -373,16 +391,29 @@ fun Context.getSuggestedContacts(): ArrayList<SimpleContact> {
val selection = "1 == 1) GROUP BY (${Sms.ADDRESS}" val selection = "1 == 1) GROUP BY (${Sms.ADDRESS}"
val selectionArgs = null val selectionArgs = null
val sortOrder = "${Sms.DATE} DESC LIMIT 20" val sortOrder = "${Sms.DATE} DESC LIMIT 20"
val blockedNumbers = getBlockedNumbers()
queryCursor(uri, projection, selection, selectionArgs, sortOrder, showErrors = true) { cursor -> queryCursor(uri, projection, selection, selectionArgs, sortOrder, showErrors = true) { cursor ->
val senderNumber = cursor.getStringValue(Sms.ADDRESS) ?: return@queryCursor val senderNumber = cursor.getStringValue(Sms.ADDRESS) ?: return@queryCursor
val namePhoto = getNameAndPhotoFromPhoneNumber(senderNumber) val namePhoto = getNameAndPhotoFromPhoneNumber(senderNumber)
if (namePhoto == null || namePhoto.name == senderNumber || isNumberBlocked(senderNumber)) { var senderName = namePhoto?.name ?: ""
var photoUri = namePhoto?.photoUri ?: ""
if (namePhoto == null || isNumberBlocked(senderNumber, blockedNumbers)) {
return@queryCursor
} else if (namePhoto.name == senderNumber) {
if (privateContacts.isNotEmpty()) {
val privateContact = privateContacts.firstOrNull { it.phoneNumber == senderNumber }
if (privateContact != null) {
senderName = privateContact.name
photoUri = privateContact.photoUri
} else {
return@queryCursor return@queryCursor
} }
} else {
return@queryCursor
}
}
val senderName = namePhoto.name
val photoUri = namePhoto.photoUri ?: ""
val contact = SimpleContact(0, 0, senderName, photoUri, senderNumber) val contact = SimpleContact(0, 0, senderName, photoUri, senderNumber)
if (!contacts.map { it.phoneNumber.trimToComparableNumber() }.contains(senderNumber.trimToComparableNumber())) { if (!contacts.map { it.phoneNumber.trimToComparableNumber() }.contains(senderNumber.trimToComparableNumber())) {
contacts.add(contact) contacts.add(contact)
@ -436,14 +467,16 @@ fun Context.insertNewSMS(address: String, subject: String, body: String, date: L
return newUri?.lastPathSegment?.toInt() ?: 0 return newUri?.lastPathSegment?.toInt() ?: 0
} }
fun Context.deleteConversation(id: Int) { fun Context.deleteConversation(threadId: Int) {
var uri = Sms.CONTENT_URI var uri = Sms.CONTENT_URI
val selection = "${Sms.THREAD_ID} = ?" val selection = "${Sms.THREAD_ID} = ?"
val selectionArgs = arrayOf(id.toString()) val selectionArgs = arrayOf(threadId.toString())
contentResolver.delete(uri, selection, selectionArgs) contentResolver.delete(uri, selection, selectionArgs)
uri = Mms.CONTENT_URI uri = Mms.CONTENT_URI
contentResolver.delete(uri, selection, selectionArgs) contentResolver.delete(uri, selection, selectionArgs)
conversationsDB.deleteThreadId(threadId.toLong())
} }
fun Context.deleteMessage(id: Int, isMMS: Boolean) { fun Context.deleteMessage(id: Int, isMMS: Boolean) {
@ -482,16 +515,6 @@ 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)
}
@SuppressLint("NewApi") @SuppressLint("NewApi")
fun Context.showReceivedMessageNotification(address: String, body: String, threadID: Int, bitmap: Bitmap?, messageId: Int, isMMS: Boolean) { fun Context.showReceivedMessageNotification(address: String, body: String, threadID: Int, bitmap: Bitmap?, messageId: Int, isMMS: Boolean) {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@ -527,6 +550,7 @@ fun Context.showReceivedMessageNotification(address: String, body: String, threa
action = MARK_AS_READ action = MARK_AS_READ
putExtra(MESSAGE_ID, messageId) putExtra(MESSAGE_ID, messageId)
putExtra(MESSAGE_IS_MMS, isMMS) putExtra(MESSAGE_IS_MMS, isMMS)
putExtra(THREAD_ID, threadID)
} }
val markAsReadPendingIntent = PendingIntent.getBroadcast(this, 0, markAsReadIntent, PendingIntent.FLAG_CANCEL_CURRENT) val markAsReadPendingIntent = PendingIntent.getBroadcast(this, 0, markAsReadIntent, PendingIntent.FLAG_CANCEL_CURRENT)

View File

@ -0,0 +1,25 @@
package com.simplemobiletools.smsmessenger.interfaces
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.simplemobiletools.smsmessenger.models.Conversation
@Dao
interface ConversationsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrUpdate(conversation: Conversation): Long
@Query("SELECT * FROM conversations")
fun getAll(): List<Conversation>
@Query("UPDATE conversations SET read = 1 WHERE thread_id = :threadId")
fun markRead(threadId: Long)
@Query("DELETE FROM conversations WHERE id = :id")
fun delete(id: Long)
@Query("DELETE FROM conversations WHERE thread_id = :threadId")
fun deleteThreadId(threadId: Long)
}

View File

@ -1,5 +1,23 @@
package com.simplemobiletools.smsmessenger.models package com.simplemobiletools.smsmessenger.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(tableName = "conversations", indices = [(Index(value = ["thread_id"], unique = true))])
data class Conversation( data class Conversation(
val id: Int, val snippet: String, val date: Int, val read: Boolean, val title: String, val photoUri: String, @PrimaryKey(autoGenerate = true) var id: Long?,
val isGroupConversation: Boolean, val phoneNumber: String) @ColumnInfo(name = "thread_id") var thread_id: Int,
@ColumnInfo(name = "snippet") var snippet: String,
@ColumnInfo(name = "date") var date: Int,
@ColumnInfo(name = "read") var read: Boolean,
@ColumnInfo(name = "title") var title: String,
@ColumnInfo(name = "photo_uri") var photoUri: String,
@ColumnInfo(name = "is_group_conversation") var isGroupConversation: Boolean,
@ColumnInfo(name = "phone_number") var phoneNumber: String
) {
fun getStringToCompare(): String {
return copy(id = 0).toString()
}
}

View File

@ -5,6 +5,6 @@ import com.simplemobiletools.commons.models.SimpleContact
data class Message( data class Message(
val id: Int, val body: String, val type: Int, val participants: ArrayList<SimpleContact>, val date: Int, val read: Boolean, val thread: Int, 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, val subscriptionId: Int) : ThreadItem() { val isMMS: Boolean, val attachment: MessageAttachment?, var senderName: String, val senderPhotoUri: String, val subscriptionId: Int) : ThreadItem() {
fun isReceivedMessage() = type == Telephony.Sms.MESSAGE_TYPE_INBOX fun isReceivedMessage() = type == Telephony.Sms.MESSAGE_TYPE_INBOX
} }

View File

@ -4,19 +4,27 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import com.simplemobiletools.commons.extensions.notificationManager import com.simplemobiletools.commons.extensions.notificationManager
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.smsmessenger.extensions.conversationsDB
import com.simplemobiletools.smsmessenger.extensions.markMessageRead import com.simplemobiletools.smsmessenger.extensions.markMessageRead
import com.simplemobiletools.smsmessenger.helpers.MARK_AS_READ import com.simplemobiletools.smsmessenger.helpers.MARK_AS_READ
import com.simplemobiletools.smsmessenger.helpers.MESSAGE_ID import com.simplemobiletools.smsmessenger.helpers.MESSAGE_ID
import com.simplemobiletools.smsmessenger.helpers.MESSAGE_IS_MMS import com.simplemobiletools.smsmessenger.helpers.MESSAGE_IS_MMS
import com.simplemobiletools.smsmessenger.helpers.THREAD_ID
class MarkAsReadReceiver : BroadcastReceiver() { class MarkAsReadReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
when (intent.action) { when (intent.action) {
MARK_AS_READ -> { MARK_AS_READ -> {
val messageId = intent.getIntExtra(MESSAGE_ID, 0) val messageId = intent.getIntExtra(MESSAGE_ID, 0)
context.notificationManager.cancel(messageId)
ensureBackgroundThread {
val isMMS = intent.getBooleanExtra(MESSAGE_IS_MMS, false) val isMMS = intent.getBooleanExtra(MESSAGE_IS_MMS, false)
context.markMessageRead(messageId, isMMS) context.markMessageRead(messageId, isMMS)
context.notificationManager.cancel(messageId)
val threadId = intent.getIntExtra(THREAD_ID, 0)
context.conversationsDB.markRead(threadId.toLong())
}
} }
} }
} }

View File

@ -3,10 +3,12 @@ package com.simplemobiletools.smsmessenger.receivers
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.simplemobiletools.commons.extensions.isNumberBlocked
import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.extensions.conversationsDB
import com.simplemobiletools.smsmessenger.extensions.getConversations
import com.simplemobiletools.smsmessenger.extensions.getLatestMMS import com.simplemobiletools.smsmessenger.extensions.getLatestMMS
import com.simplemobiletools.smsmessenger.extensions.isNumberBlocked
import com.simplemobiletools.smsmessenger.extensions.showReceivedMessageNotification import com.simplemobiletools.smsmessenger.extensions.showReceivedMessageNotification
// more info at https://github.com/klinker41/android-smsmms // more info at https://github.com/klinker41/android-smsmms
@ -32,6 +34,8 @@ class MmsReceiver : com.klinker.android.send_message.MmsReceivedReceiver() {
} }
context.showReceivedMessageNotification(address, mms.body, mms.thread, glideBitmap, mms.id, true) context.showReceivedMessageNotification(address, mms.body, mms.thread, glideBitmap, mms.id, true)
val conversation = context.getConversations(mms.thread.toLong()).firstOrNull() ?: return@ensureBackgroundThread
context.conversationsDB.insertOrUpdate(conversation)
} }
} }

View File

@ -4,10 +4,9 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.provider.Telephony import android.provider.Telephony
import com.simplemobiletools.smsmessenger.extensions.getThreadId import com.simplemobiletools.commons.extensions.isNumberBlocked
import com.simplemobiletools.smsmessenger.extensions.insertNewSMS import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.smsmessenger.extensions.isNumberBlocked import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.extensions.showReceivedMessageNotification
import com.simplemobiletools.smsmessenger.helpers.refreshMessages import com.simplemobiletools.smsmessenger.helpers.refreshMessages
class SmsReceiver : BroadcastReceiver() { class SmsReceiver : BroadcastReceiver() {
@ -34,6 +33,11 @@ class SmsReceiver : BroadcastReceiver() {
val messageId = context.insertNewSMS(address, subject, body, date, read, threadId, type, subscriptionId) val messageId = context.insertNewSMS(address, subject, body, date, read, threadId, type, subscriptionId)
context.showReceivedMessageNotification(address, body, threadId.toInt(), null, messageId, false) context.showReceivedMessageNotification(address, body, threadId.toInt(), null, messageId, false)
refreshMessages() refreshMessages()
ensureBackgroundThread {
val conversation = context.getConversations(threadId).firstOrNull() ?: return@ensureBackgroundThread
context.conversationsDB.insertOrUpdate(conversation)
}
} }
} }
} }

View File

@ -2,6 +2,7 @@ package com.simplemobiletools.smsmessenger.services
import android.app.Service import android.app.Service
import android.content.Intent import android.content.Intent
import android.net.Uri
import com.klinker.android.send_message.Settings import com.klinker.android.send_message.Settings
import com.klinker.android.send_message.Transaction import com.klinker.android.send_message.Transaction
import com.simplemobiletools.smsmessenger.extensions.getThreadId import com.simplemobiletools.smsmessenger.extensions.getThreadId
@ -15,7 +16,7 @@ class HeadlessSmsSendService : Service() {
return START_NOT_STICKY return START_NOT_STICKY
} }
val number = intent.dataString!!.removePrefix("sms:").removePrefix("smsto:").removePrefix("mms").removePrefix("mmsto:").trim() val number = Uri.decode(intent.dataString!!.removePrefix("sms:").removePrefix("smsto:").removePrefix("mms").removePrefix("mmsto:").trim())
val text = intent.getStringExtra(Intent.EXTRA_TEXT) val text = intent.getStringExtra(Intent.EXTRA_TEXT)
val settings = Settings() val settings = Settings()
settings.useSystemSending = true settings.useSystemSending = true

View File

@ -42,12 +42,12 @@
android:id="@+id/no_contacts_placeholder" android:id="@+id/no_contacts_placeholder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/message_divider_two"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/bigger_margin" android:layout_marginTop="@dimen/bigger_margin"
android:alpha="0.8" android:alpha="0.8"
android:gravity="center" android:gravity="center"
android:paddingLeft="@dimen/activity_margin" android:paddingLeft="@dimen/activity_margin"
android:layout_below="@+id/message_divider_two"
android:paddingRight="@dimen/activity_margin" android:paddingRight="@dimen/activity_margin"
android:text="@string/no_access_to_contacts" android:text="@string/no_access_to_contacts"
android:textSize="@dimen/bigger_text_size" android:textSize="@dimen/bigger_text_size"

View File

@ -161,6 +161,7 @@
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:gravity="center_vertical" android:gravity="center_vertical"
android:hint="@string/type_a_message" android:hint="@string/type_a_message"
android:inputType="textCapSentences|textMultiLine"
android:minHeight="@dimen/normal_icon_size" /> android:minHeight="@dimen/normal_icon_size" />
<ImageView <ImageView

View File

@ -6,6 +6,11 @@
android:icon="@drawable/ic_add_person_vector" android:icon="@drawable/ic_add_person_vector"
android:title="@string/add_number_to_contact" android:title="@string/add_number_to_contact"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item
android:id="@+id/cab_block_number"
android:icon="@drawable/ic_minus_circle_vector"
android:title="@string/block_number"
app:showAsAction="ifRoom" />
<item <item
android:id="@+id/cab_select_all" android:id="@+id/cab_select_all"
android:icon="@drawable/ic_select_all_vector" android:icon="@drawable/ic_select_all_vector"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 977 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,72 @@
<resources>
<string name="app_name">Schlichter SMS Messenger</string>
<string name="app_launcher_name">SMS Messenger</string>
<string name="type_a_message">schreibe eine Nachricht…</string>
<string name="message_not_sent">Nachricht wurde noch nicht gesendet</string>
<string name="add_person">füge eine Person hinzu</string>
<string name="attachment">Anhang</string>
<string name="no_conversations_found">keine gespeicherten Chats gefunden</string>
<string name="start_conversation">einen neuen Chat beginnen</string>
<!-- New conversation -->
<string name="new_conversation">neuer Chat</string>
<string name="add_contact_or_number">Füge einen Kontakt oder eine Nummer hinzu…</string>
<string name="suggestions">Vorschläge</string>
<!-- Notifications -->
<string name="channel_received_sms">Empfangene SMS</string>
<string name="new_message">neue Nachricht</string>
<string name="mark_as_read">markiere als gelesen</string>
<string name="mark_as_unread">Mark as Unread</string>
<!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Möchtest du wirklich alle Nachrichten dieses Chat löschen?</string>
<!-- Are you sure you want to delete 5 conversations? -->
<plurals name="delete_conversations">
<item quantity="one">%d Chat</item>
<item quantity="other">%d Chats</item>
</plurals>
<!-- Are you sure you want to delete 5 messages? -->
<plurals name="delete_messages">
<item quantity="one">%d Nachricht</item>
<item quantity="other">%d Nachrichten</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.
Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors.
<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

@ -0,0 +1,72 @@
<resources>
<string name="app_name">Απλός SMS Messenger</string>
<string name="app_launcher_name">SMS Messenger</string>
<string name="type_a_message">Πληκτρολογήστε ένα μήνυμα…</string>
<string name="message_not_sent">Το μήνυμα δεν έχει σταλεί.</string>
<string name="add_person">Προσθήκη ατόμου</string>
<string name="attachment">Συνημμένο</string>
<string name="no_conversations_found">Δεν βρέθηκαν αποθηκευμένες συνομιλίες</string>
<string name="start_conversation">Έναρξη συνομιλίας</string>
<!-- New conversation -->
<string name="new_conversation">Νέα συνομιλία</string>
<string name="add_contact_or_number">Προσθήκη επαφής ή αριθμού…</string>
<string name="suggestions">Προτάσεις</string>
<!-- Notifications -->
<string name="channel_received_sms">Ελήφθη SMS</string>
<string name="new_message">Νέο μήνυμα</string>
<string name="mark_as_read">Σήμανση ως αναγνωσμένου</string>
<string name="mark_as_unread">Mark as Unread</string>
<!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Είστε βέβαιοι ότι θέλετε να διαγράψετε όλα τα μηνύματα αυτής της συνομιλίας;</string>
<!-- Are you sure you want to delete 5 conversations? -->
<plurals name="delete_conversations">
<item quantity="one">%d συνομιλία</item>
<item quantity="other">%d συνομιλίες</item>
</plurals>
<!-- Are you sure you want to delete 5 messages? -->
<plurals name="delete_messages">
<item quantity="one">%d μήνυμα</item>
<item quantity="other">%d μηνύματα</item>
</plurals>
<!-- FAQ -->
<string name="faq_1_title">Γιατί η εφαρμογή απαιτεί πρόσβαση στο Internet;</string>
<string name="faq_1_text">Δυστυχώς, απαιτείται για την αποστολή συνημμένων MMS. Το να μην είμαστε σε θέση να στείλουμε MMS θα αποτελούσε πραγματικά τεράστιο μειονέκτημα σε σύγκριση με άλλες εφαρμογές, επομένως αποφασίσαμε να ακολουθήσουμε αυτόν τον δρόμο.
Ωστόσο, όπως συνήθως, δεν υπάρχουν καθόλου διαφημίσεις, παρακολούθηση ή αναλύσεις, το διαδίκτυο χρησιμοποιείται μόνο για την αποστολή 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">Απλός SMS Messenger - Εύκολη διαχείριση μηνυμάτων</string>
<!-- Short description has to have max 80 characters -->
<string name="app_short_description">Ένας εύκολος και γρήγορος τρόπος διαχείρισης SMS και MMS χωρίς διαφημίσεις.</string>
<string name="app_long_description">
Ένας εξαιρετικός τρόπος για να μείνετε σε επαφή με τους συγγενείς σας, στέλνοντας μηνύματα SMS και MMS. Η εφαρμογή χειρίζεται σωστά και την ομαδική ανταλλαγή μηνυμάτων, όπως και τον αποκλεισμό αριθμών από Android 7+.
Προσφέρει πολλές μορφές ημερομηνίας για να διαλέξετε, ώστε να αισθάνεστε άνετα να τις χρησιμοποιήσετε. Μπορείτε επίσης να κάνετε εναλλαγή μεταξύ 12 και 24 ωρών.
Έχει πραγματικά πολύ μικρό μέγεθος εφαρμογής σε σύγκριση με τον ανταγωνισμό, καθιστώντας την πραγματικά γρήγορη στη λήψη της.
Διατίθεται με σχεδίαση υλικού και σκούρο θέμα από προεπιλογή, παρέχει εξαιρετική εμπειρία χρήστη για εύκολη χρήση. Η έλλειψη πρόσβασης στο διαδίκτυο σάς παρέχει περισσότερο απόρρητο, ασφάλεια και σταθερότητα από ό,τι άλλες εφαρμογές.
Δεν περιέχει διαφημίσεις ή περιττά δικαιώματα. Είναι πλήρως ανοιχτού κώδικα, παρέχει προσαρμόσιμα χρώματα.
<b>Δείτε την πλήρη σειρά 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

@ -0,0 +1,72 @@
<resources>
<string name="app_name">Mensajería SMS Simple</string>
<string name="app_launcher_name">Mensajería SMS</string>
<string name="type_a_message">Escribe un mensaje…</string>
<string name="message_not_sent">El mensaje no se ha enviado.</string>
<string name="add_person">Añadir persona</string>
<string name="attachment">Archivo adjunto</string>
<string name="no_conversations_found">No se han encontrado conversaciones</string>
<string name="start_conversation">Inicia una conversación</string>
<!-- New conversation -->
<string name="new_conversation">Nueva conversación</string>
<string name="add_contact_or_number">Escribe contacto o número…</string>
<string name="suggestions">Sugerencias</string>
<!-- Notifications -->
<string name="channel_received_sms">Mensaje recibico</string>
<string name="new_message">Nuevo mensaje</string>
<string name="mark_as_read">Marcar como leído</string>
<string name="mark_as_unread">Mark as Unread</string>
<!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">¿Estás seguro que quieres eliminar todos los mensajes en esta conversación?</string>
<!-- Are you sure you want to delete 5 conversations? -->
<plurals name="delete_conversations">
<item quantity="one">%d conversación</item>
<item quantity="other">%d conversaciones</item>
</plurals>
<!-- Are you sure you want to delete 5 messages? -->
<plurals name="delete_messages">
<item quantity="one">%d mensaje</item>
<item quantity="other">%d mensajes</item>
</plurals>
<!-- FAQ -->
<string name="faq_1_title">¿Por qué la aplicación requiere acceso a internet?</string>
<string name="faq_1_text">Tristemente es necesitado apra enviar archivos adjuntos MMS. El no poder enviar MMS sería una desventaja realmente enorme comparada con otras aplicaciones, así que decidimos tomar este camino.
Sin embargo, como siempre, no hay anuncios, rastreo o análisis, por lo que el internet solo es usado para enviar 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">Mensajería SMS Simple - Mensajea fácilmente</string>
<!-- Short description has to have max 80 characters -->
<string name="app_short_description">Una forma fácil y rápida de enviar mensajes SMS y MMS sin anuncios.</string>
<string name="app_long_description">
Una excelente forma de estar en contacto con tus parientes, enviando mensajes SMS y MMS. La aplicación maneja adecuadamente mensajes grupales también, así como números bloqueados desde Android 7+.
Ofrece varios formatos de fecha para escoger, para hacerte sentir cómodo mientras la usas. También puedes escoger entre formato de 12 y 24 horas.
La aplicaicón pesa muy poco comparada con la competencia, haciendo que se descargue realmente rápido.
Viene con diseño material y tema obscuro por defecto, proveyendo una excelente experiencia de usuario y un uso fácil. La falta de acceso a internet te da más privacidad, seguridad y estabilidad que otras aplicaciones.
No contiene anuncios o permisos innecesarios. Es completamente de código abierto, provee colores personalizables.
<b>Mira la suite completa de herramientas Simples aquí:</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

@ -17,6 +17,7 @@
<string name="channel_received_sms">Gautos žinutės</string> <string name="channel_received_sms">Gautos žinutės</string>
<string name="new_message">Nauja žinutė</string> <string name="new_message">Nauja žinutė</string>
<string name="mark_as_read">Mark as Read</string> <string name="mark_as_read">Mark as Read</string>
<string name="mark_as_unread">Mark as Unread</string>
<!-- Confirmation dialog --> <!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Ar tikrai norite ištrinti visas šio pokalbio žinutes?</string> <string name="delete_whole_conversation_confirmation">Ar tikrai norite ištrinti visas šio pokalbio žinutes?</string>

View File

@ -0,0 +1,72 @@
<resources>
<string name="app_name">Eenvoudig Berichtenbeheer (SMS)</string>
<string name="app_launcher_name">Berichten</string>
<string name="type_a_message">Typ een bericht…</string>
<string name="message_not_sent">Bericht niet verzonden.</string>
<string name="add_person">Persoon toevoegen</string>
<string name="attachment">Bijlage</string>
<string name="no_conversations_found">Geen opgeslagen berichten gevonden</string>
<string name="start_conversation">Een gesprek starten</string>
<!-- New conversation -->
<string name="new_conversation">Nieuw gesprek</string>
<string name="add_contact_or_number">Contact of nummer toevoegen…</string>
<string name="suggestions">Suggesties</string>
<!-- Notifications -->
<string name="channel_received_sms">Ontvangen berichten</string>
<string name="new_message">Nieuw bericht</string>
<string name="mark_as_read">Als gelezen markeren</string>
<string name="mark_as_unread">Als ongelezen markeren</string>
<!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Alle berichten in dit gesprek verwijderen?</string>
<!-- Are you sure you want to delete 5 conversations? -->
<plurals name="delete_conversations">
<item quantity="one">%d gesperk</item>
<item quantity="other">%d gesprekken</item>
</plurals>
<!-- Are you sure you want to delete 5 messages? -->
<plurals name="delete_messages">
<item quantity="one">%d bericht</item>
<item quantity="other">%d berichten</item>
</plurals>
<!-- FAQ -->
<string name="faq_1_title">Waarom heeft deze app toegang nodig tot het internet?</string>
<string name="faq_1_text">Dit is helaas nodig voor het verzenden van MMS-bijlagen. Het versturen van MMS-berichten onmogelijk maken zou een te groot nadeel t.o.v. andere apps betekenen en daarom hebben we besloten om het toch toe te voegen.
Zoals gewoonlijk bevat de app echter geen advertenties, tracking of analytics; de verbinding wordt alleen maar gebruikt voor het versturen van MMS-berichten.</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">Eenvoudig Berichtenbeheer - Verstuur snel SMS/MMS</string>
<!-- Short description has to have max 80 characters -->
<string name="app_short_description">Beheer eenvoudig en snel SMS- en MMS-berichten, zonder advertenties.</string>
<string name="app_long_description">
Blijf gemakkelijk in contact met familieleden en vrienden door het versuren van SMS- en MMS-berichten. De app ondersteunt groepsberichten, evenals het blokkeren van nummers (vanaf Android 7).
Het datumformaat kan worden ingesteld naar voorkeur. Ook kan geschakeld worden tussen 12- of 24-uursnotatie.
Vergeleken met de competitie is deze app zeer compact en dus erg snel gedownload.
De app is ontworpen volgens material design en heeft standaard een donker thema. De app heeft geen toegang tot het internet nodig en voorziet van meer privacy, veiligheid en stabiliteit dan andere apps.
Bevat geen advertenties of onnodige machtigingen. Volledig open-source. Kleuren van de app kunnen worden aangepast.
<b>Probeer ook eens de andere apps van Simple Tools:</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

@ -16,7 +16,8 @@
<!-- Notifications --> <!-- Notifications -->
<string name="channel_received_sms">SMS recebida</string> <string name="channel_received_sms">SMS recebida</string>
<string name="new_message">Nova mensagem</string> <string name="new_message">Nova mensagem</string>
<string name="mark_as_read">Mark as Read</string> <string name="mark_as_read">Marcar como lida</string>
<string name="mark_as_unread">Mark as Unread</string>
<!-- Confirmation dialog --> <!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Tem a certeza de que deseja eliminar todas as mensagens desta conversa?</string> <string name="delete_whole_conversation_confirmation">Tem a certeza de que deseja eliminar todas as mensagens desta conversa?</string>
@ -54,7 +55,7 @@
Não contém anúncios nem permissões desnecessárias. É open source e permite a personalização de cores. 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> <b>Consulte o conjunto completo de aplicações Simple Tools aqui:</b>
https://www.simplemobiletools.com https://www.simplemobiletools.com
<b>Facebook:</b> <b>Facebook:</b>

View File

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

View File

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

View File

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

View File

@ -0,0 +1,18 @@
Ένας εξαιρετικός τρόπος για να μείνετε σε επαφή με τους συγγενείς σας, στέλνοντας μηνύματα SMS και MMS. Η εφαρμογή χειρίζεται σωστά και την ομαδική ανταλλαγή μηνυμάτων, όπως και τον αποκλεισμό αριθμών από Android 7+.
Προσφέρει πολλές μορφές ημερομηνίας για να διαλέξετε, ώστε να αισθάνεστε άνετα να τις χρησιμοποιήσετε. Μπορείτε επίσης να κάνετε εναλλαγή μεταξύ 12 και 24 ωρών.
Έχει πραγματικά πολύ μικρό μέγεθος εφαρμογής σε σύγκριση με τον ανταγωνισμό, καθιστώντας την πραγματικά γρήγορη στη λήψη της.
Διατίθεται με σχεδίαση υλικού και σκούρο θέμα από προεπιλογή, παρέχει εξαιρετική εμπειρία χρήστη για εύκολη χρήση. Η έλλειψη πρόσβασης στο διαδίκτυο σάς παρέχει περισσότερο απόρρητο, ασφάλεια και σταθερότητα από ό,τι άλλες εφαρμογές.
Δεν περιέχει διαφημίσεις ή περιττά δικαιώματα. Είναι πλήρως ανοιχτού κώδικα, παρέχει προσαρμόσιμα χρώματα.
<b>Δείτε την πλήρη σειρά 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

View File

@ -0,0 +1 @@
Ένας εύκολος και γρήγορος τρόπος διαχείρισης SMS και MMS χωρίς διαφημίσεις.

View File

@ -0,0 +1 @@
Απλός SMS Messenger - Εύκολη διαχείριση μηνυμάτων

View File

@ -0,0 +1,3 @@
* Adding support for accessing private contacts stored in Simple Contacts Pro (to be added soon)
* Make sure the conversations are sorted properly
* Added some other UI and translation improvements

View File

@ -0,0 +1,4 @@
* Improved the performance at checking if a number is blocked
* Use capital letters at sentences at writing a message
* Allow blocking numbers from the main screen
* Fixed a glitch with adding new people to a conversation

View File

@ -0,0 +1,4 @@
* Cache the conversations shown on the main screen to drastically improve their loading speed
* Allow adding newlines with the Enter button
* Properly handle a new View third party intent
* Added some other translation, UI and stability improvements

View File

@ -0,0 +1,18 @@
Una excelente forma de estar en contacto con tus parientes, enviando mensajes SMS y MMS. La aplicación maneja adecuadamente mensajes grupales también, así como números bloqueados desde Android 7+.
Ofrece varios formatos de fecha para escoger, para hacerte sentir cómodo mientras la usas. También puedes escoger entre formato de 12 y 24 horas.
La aplicaicón pesa muy poco comparada con la competencia, haciendo que se descargue realmente rápido.
Viene con diseño material y tema obscuro por defecto, proveyendo una excelente experiencia de usuario y un uso fácil. La falta de acceso a internet te da más privacidad, seguridad y estabilidad que otras aplicaciones.
No contiene anuncios o permisos innecesarios. Es completamente de código abierto, provee colores personalizables.
<b>Mira la suite completa de herramientas Simples aquí:</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 @@
Una forma fácil y rápida de enviar mensajes SMS y MMS sin anuncios.

View File

@ -0,0 +1 @@
Mensajería SMS Simple - Mensajea fácilmente

View File

@ -8,7 +8,7 @@ Disponibiliza um design atrativo e um tema escuro por omissão. A não utilizaç
Não contém anúncios nem permissões desnecessárias. É open source e permite a personalização de cores. 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> <b>Consulte o conjunto completo de aplicações Simple Tools aqui:</b>
https://www.simplemobiletools.com https://www.simplemobiletools.com
<b>Facebook:</b> <b>Facebook:</b>