# Conflicts:
#	gradle/wrapper/gradle-wrapper.properties
This commit is contained in:
Jonathan Buhacoff
2021-03-06 06:44:45 -08:00
99 changed files with 2499 additions and 367 deletions

View File

@@ -17,7 +17,7 @@ insert_final_newline = true
charset = utf-8 charset = utf-8
indent_style = space indent_style = space
indent_size = 4 indent_size = 4
continuation_indent_size = 8 continuation_indent_size = 4
[*.xml] [*.xml]
continuation_indent_size = 4 continuation_indent_size = 4

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
github: [tibbi]
patreon: tiborkaputa
custom: ["https://www.paypal.com/paypalme/simplemobiletools", "https://www.simplemobiletools.com/donate"]

View File

@@ -1,6 +1,84 @@
Changelog Changelog
========== ==========
Version 5.9.0 *(2021-02-16)*
----------------------------
* Added Search
* Added a White theme with special handling
* Some stability and translation improvements
Version 5.8.3 *(2021-01-27)*
----------------------------
* Adding some stability and translation improvements
Version 5.8.2 *(2021-01-18)*
----------------------------
* Fixed a glitch with inability to send messages in empty conversations
* Adding a settings item for quickly getting into notification settings
* Some stability and translation improvements
Version 5.8.1 *(2021-01-11)*
----------------------------
* Fixed a glitch with "Sending..." stuck at messages
* Allow selecting a phone numbers at contacts with multiple ones
* Some translation and stability improvements
Version 5.8.0 *(2021-01-02)*
----------------------------
* Improved delivery reports, show a notification at failed messages
* Allow resending failed messages with tapping them
* Added multiple dual-SIM related improvements
* Many other UX, stability, performance and translation improvements
Version 5.7.0 *(2020-12-30)*
----------------------------
* Prefetch all messages at first launch for better performance
* Require Simple Thank You for color customization
* Some stability, translation and UX improvements
Version 5.6.2 *(2020-12-25)*
----------------------------
* Fixed messages not being sent in some cases
Version 5.6.1 *(2020-12-24)*
----------------------------
* Fixing a crash at devices with multiple SIM cards
Version 5.6.0 *(2020-12-23)*
----------------------------
* Cache messages for better performance
* Add a scrollbar on the main screen
* Adding some stability and translation improvements
Version 5.5.1 *(2020-12-06)*
----------------------------
* Properly show private contact names at group conversations
* Fixed private contacts not being visible on Android 11+
* Some stability and translation improvements
Version 5.5.0 *(2020-11-04)*
----------------------------
* Allow dialing or copying selected conversation phone numbers
* Allow copying specific parts of messages into clipboard
* Adding an option to show character counter at outgoing messages
* Couple other UI, translation and stability improvements
Version 5.4.5 *(2020-10-27)*
----------------------------
* Fixed some smaller glitches + translation improvements
Version 5.4.4 *(2020-09-23)* Version 5.4.4 *(2020-09-23)*
---------------------------- ----------------------------

View File

@@ -11,20 +11,21 @@ It comes with material design and dark theme by default, provides great user exp
Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors. Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors.
Check out the full suite of Simple Tools here: Check out the full suite of Simple Tools here:
https://www.simplemobiletools.com https://www.simplemobiletools.com
Facebook: Facebook:
https://www.facebook.com/simplemobiletools https://www.facebook.com/simplemobiletools
Reddit: 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> <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/english/1.jpg" width="30%">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app_2.jpg" width="30%"> <img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/english/2.jpg" width="30%">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/english/3.jpg" width="30%">
</div> </div>

View File

@@ -16,8 +16,8 @@ android {
applicationId "com.simplemobiletools.smsmessenger" applicationId "com.simplemobiletools.smsmessenger"
minSdkVersion 22 minSdkVersion 22
targetSdkVersion 30 targetSdkVersion 30
versionCode 16 versionCode 29
versionName "5.4.4" versionName "5.9.0"
setProperty("archivesBaseName", "sms-messenger") setProperty("archivesBaseName", "sms-messenger")
} }
@@ -56,13 +56,14 @@ android {
} }
dependencies { dependencies {
implementation 'com.simplemobiletools:commons:5.30.12' implementation 'com.simplemobiletools:commons:5.33.32'
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:c3de1d040a'
implementation "me.leolin:ShortcutBadger:1.1.22" implementation "me.leolin:ShortcutBadger:1.1.22"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
kapt "androidx.room:room-compiler:2.2.5" kapt "androidx.room:room-compiler:2.2.6"
implementation "androidx.room:room-runtime:2.2.5" implementation "androidx.room:room-runtime:2.2.6"
annotationProcessor "androidx.room:room-compiler:2.2.5" annotationProcessor "androidx.room:room-compiler:2.2.6"
} }

View File

@@ -38,6 +38,21 @@
android:name=".activities.ThreadActivity" android:name=".activities.ThreadActivity"
android:parentActivityName=".activities.MainActivity" /> android:parentActivityName=".activities.MainActivity" />
<activity
android:name=".activities.SearchActivity"
android:label=""
android:parentActivityName=".activities.MainActivity"
android:resizeableActivity="true">
<meta-data
android:name="android.app.default_searchable"
android:resource="@xml/searchable"/>
<intent-filter>
<action android:name="android.intent.action.SEARCH"/>
</intent-filter>
</activity>
<activity <activity
android:name=".activities.NewConversationActivity" android:name=".activities.NewConversationActivity"
android:parentActivityName=".activities.MainActivity"> android:parentActivityName=".activities.MainActivity">
@@ -121,6 +136,10 @@
<service android:name="com.android.mms.transaction.TransactionService" /> <service android:name="com.android.mms.transaction.TransactionService" />
<receiver android:name=".receivers.SmsStatusSentReceiver" />
<receiver android:name=".receivers.SmsStatusDeliveredReceiver" />
<receiver <receiver
android:name=".receivers.SmsReceiver" android:name=".receivers.SmsReceiver"
android:permission="android.permission.BROADCAST_SMS"> android:permission="android.permission.BROADCAST_SMS">
@@ -149,11 +168,6 @@
android:exported="true" android:exported="true"
android:taskAffinity="com.klinker.android.messaging.MMS_RECEIVED" /> android:taskAffinity="com.klinker.android.messaging.MMS_RECEIVED" />
<receiver
android:name=".receivers.SmsSentReceiver"
android:exported="true"
android:taskAffinity="${applicationId}.SMS_SENT" />
<receiver <receiver
android:name=".receivers.MarkAsReadReceiver" android:name=".receivers.MarkAsReadReceiver"
android:enabled="true" android:enabled="true"

View File

@@ -18,10 +18,7 @@ import com.simplemobiletools.commons.models.FAQItem
import com.simplemobiletools.smsmessenger.BuildConfig 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.*
import com.simplemobiletools.smsmessenger.extensions.conversationsDB
import com.simplemobiletools.smsmessenger.extensions.getConversations
import com.simplemobiletools.smsmessenger.extensions.updateUnreadCountBadge
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
import com.simplemobiletools.smsmessenger.models.Conversation import com.simplemobiletools.smsmessenger.models.Conversation
@@ -86,6 +83,8 @@ class MainActivity : SimpleActivity() {
updateTextColors(main_coordinator) updateTextColors(main_coordinator)
no_conversations_placeholder_2.setTextColor(getAdjustedPrimaryColor()) no_conversations_placeholder_2.setTextColor(getAdjustedPrimaryColor())
no_conversations_placeholder_2.underlineText() no_conversations_placeholder_2.underlineText()
conversations_fastscroller.updatePrimaryColor()
conversations_fastscroller.updateBubbleColors()
checkShortcut() checkShortcut()
} }
@@ -101,11 +100,13 @@ class MainActivity : SimpleActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu) menuInflater.inflate(R.menu.menu_main, menu)
updateMenuItemColors(menu)
return true return true
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.search -> launchSearch()
R.id.settings -> launchSettings() R.id.settings -> launchSettings()
R.id.about -> launchAbout() R.id.about -> launchAbout()
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)
@@ -138,7 +139,10 @@ class MainActivity : SimpleActivity() {
handlePermission(PERMISSION_READ_CONTACTS) { handlePermission(PERMISSION_READ_CONTACTS) {
initMessenger() initMessenger()
bus = EventBus.getDefault() bus = EventBus.getDefault()
bus!!.register(this) try {
bus!!.register(this)
} catch (e: Exception) {
}
} }
} else { } else {
finish() finish()
@@ -165,7 +169,12 @@ class MainActivity : SimpleActivity() {
private fun getCachedConversations() { private fun getCachedConversations() {
ensureBackgroundThread { ensureBackgroundThread {
val conversations = conversationsDB.getAll().sortedByDescending { it.date }.toMutableList() as ArrayList<Conversation> val conversations = try {
conversationsDB.getAll().sortedByDescending { it.date }.toMutableList() as ArrayList<Conversation>
} catch (e: Exception) {
ArrayList()
}
updateUnreadCountBadge(conversations) updateUnreadCountBadge(conversations)
runOnUiThread { runOnUiThread {
setupConversations(conversations) setupConversations(conversations)
@@ -175,46 +184,43 @@ class MainActivity : SimpleActivity() {
} }
private fun getNewConversations(cachedConversations: ArrayList<Conversation>) { private fun getNewConversations(cachedConversations: ArrayList<Conversation>) {
val privateCursor = getMyContactsCursor().loadInBackground() val privateCursor = getMyContactsCursor()?.loadInBackground()
ensureBackgroundThread { ensureBackgroundThread {
val conversations = getConversations()
// check if no message came from a privately stored contact in Simple Contacts
val privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor) val privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor)
if (privateContacts.isNotEmpty()) { val conversations = getConversations(privateContacts = privateContacts)
conversations.filter { it.title == it.phoneNumber }.forEach { conversation ->
privateContacts.forEach { contact ->
if (contact.doesContainPhoneNumber(conversation.phoneNumber)) {
conversation.title = contact.name
conversation.photoUri = contact.photoUri
}
}
}
}
runOnUiThread { runOnUiThread {
setupConversations(conversations) setupConversations(conversations)
} }
conversations.forEach { clonedConversation -> conversations.forEach { clonedConversation ->
if (!cachedConversations.map { it.thread_id }.contains(clonedConversation.thread_id)) { if (!cachedConversations.map { it.threadId }.contains(clonedConversation.threadId)) {
conversationsDB.insertOrUpdate(clonedConversation) conversationsDB.insertOrUpdate(clonedConversation)
cachedConversations.add(clonedConversation) cachedConversations.add(clonedConversation)
} }
} }
cachedConversations.forEach { cachedConversation -> cachedConversations.forEach { cachedConversation ->
if (!conversations.map { it.thread_id }.contains(cachedConversation.thread_id)) { if (!conversations.map { it.threadId }.contains(cachedConversation.threadId)) {
conversationsDB.delete(cachedConversation.id!!) conversationsDB.deleteThreadId(cachedConversation.threadId)
} }
} }
cachedConversations.forEach { cachedConversation -> cachedConversations.forEach { cachedConversation ->
val conv = conversations.firstOrNull { it.thread_id == cachedConversation.thread_id && it.getStringToCompare() != cachedConversation.getStringToCompare() } val conv = conversations.firstOrNull { it.threadId == cachedConversation.threadId && it.toString() != cachedConversation.toString() }
if (conv != null) { if (conv != null) {
conversationsDB.insertOrUpdate(conv) conversationsDB.insertOrUpdate(conv)
} }
} }
if (config.appRunCount == 1) {
conversations.map { it.threadId }.forEach { threadId ->
val messages = getMessages(threadId)
messages.chunked(30).forEach { currentMessages ->
messagesDB.insertMessages(*currentMessages.toTypedArray())
}
}
}
} }
} }
@@ -224,17 +230,27 @@ class MainActivity : SimpleActivity() {
no_conversations_placeholder.beVisibleIf(!hasConversations) no_conversations_placeholder.beVisibleIf(!hasConversations)
no_conversations_placeholder_2.beVisibleIf(!hasConversations) no_conversations_placeholder_2.beVisibleIf(!hasConversations)
if (!hasConversations && config.appRunCount == 1) {
no_conversations_placeholder.text = getString(R.string.loading_messages)
no_conversations_placeholder_2.beGone()
}
val currAdapter = conversations_list.adapter val currAdapter = conversations_list.adapter
if (currAdapter == null) { if (currAdapter == null) {
ConversationsAdapter(this, conversations, conversations_list, conversations_fastscroller) { ConversationsAdapter(this, conversations, conversations_list, conversations_fastscroller) {
Intent(this, ThreadActivity::class.java).apply { Intent(this, ThreadActivity::class.java).apply {
putExtra(THREAD_ID, (it as Conversation).thread_id) putExtra(THREAD_ID, (it as Conversation).threadId)
putExtra(THREAD_TITLE, it.title) putExtra(THREAD_TITLE, it.title)
startActivity(this) startActivity(this)
} }
}.apply { }.apply {
conversations_list.adapter = this conversations_list.adapter = this
} }
conversations_fastscroller.setViews(conversations_list) {
val listItem = (conversations_list.adapter as? ConversationsAdapter)?.conversations?.getOrNull(it)
conversations_fastscroller.updateBubbleText(listItem?.title ?: "")
}
} else { } else {
try { try {
(currAdapter as ConversationsAdapter).updateConversations(conversations) (currAdapter as ConversationsAdapter).updateConversations(conversations)
@@ -281,6 +297,10 @@ class MainActivity : SimpleActivity() {
.build() .build()
} }
private fun launchSearch() {
startActivity(Intent(applicationContext, SearchActivity::class.java))
}
private fun launchSettings() { private fun launchSettings() {
startActivity(Intent(applicationContext, SettingsActivity::class.java)) startActivity(Intent(applicationContext, SettingsActivity::class.java))
} }
@@ -290,7 +310,8 @@ class MainActivity : SimpleActivity() {
val faqItems = arrayListOf( val faqItems = arrayListOf(
FAQItem(R.string.faq_2_title_commons, R.string.faq_2_text_commons), FAQItem(R.string.faq_2_title_commons, R.string.faq_2_text_commons),
FAQItem(R.string.faq_6_title_commons, R.string.faq_6_text_commons) FAQItem(R.string.faq_6_title_commons, R.string.faq_6_text_commons),
FAQItem(R.string.faq_9_title_commons, R.string.faq_9_text_commons)
) )
startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true) startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true)

View File

@@ -5,12 +5,12 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.WindowManager import android.view.WindowManager
import com.google.gson.Gson
import com.reddit.indicatorfastscroll.FastScrollItemIndicator import com.reddit.indicatorfastscroll.FastScrollItemIndicator
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.MyContactsContentProvider import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.helpers.PERMISSION_READ_CONTACTS import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
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.ContactsAdapter import com.simplemobiletools.smsmessenger.adapters.ContactsAdapter
@@ -89,9 +89,12 @@ class NewConversationActivity : SimpleActivity() {
} }
} }
val adjustedPrimaryColor = getAdjustedPrimaryColor()
contacts_letter_fastscroller.textColor = config.textColor.getColorStateList() contacts_letter_fastscroller.textColor = config.textColor.getColorStateList()
contacts_letter_fastscroller.pressedTextColor = adjustedPrimaryColor
contacts_letter_fastscroller_thumb.setupWithFastScroller(contacts_letter_fastscroller) contacts_letter_fastscroller_thumb.setupWithFastScroller(contacts_letter_fastscroller)
contacts_letter_fastscroller_thumb.textColor = config.primaryColor.getContrastColor() contacts_letter_fastscroller_thumb?.textColor = adjustedPrimaryColor.getContrastColor()
contacts_letter_fastscroller_thumb?.thumbColor = adjustedPrimaryColor.getColorStateList()
} }
private fun isThirdPartyIntent(): Boolean { private fun isThirdPartyIntent(): Boolean {
@@ -134,7 +137,20 @@ class NewConversationActivity : SimpleActivity() {
ContactsAdapter(this, contacts, contacts_list, null) { ContactsAdapter(this, contacts, contacts_list, null) {
hideKeyboard() hideKeyboard()
launchThreadActivity((it as SimpleContact).phoneNumbers.first(), it.name) val contact = it as SimpleContact
val phoneNumbers = contact.phoneNumbers
if (phoneNumbers.size > 1) {
val items = ArrayList<RadioItem>()
phoneNumbers.forEachIndexed { index, phoneNumber ->
items.add(RadioItem(index, phoneNumber, phoneNumber))
}
RadioGroupDialog(this, items) {
launchThreadActivity(it as String, contact.name)
}
} else {
launchThreadActivity(phoneNumbers.first(), contact.name)
}
}.apply { }.apply {
contacts_list.adapter = this contacts_list.adapter = this
} }
@@ -143,7 +159,7 @@ class NewConversationActivity : SimpleActivity() {
} }
private fun fillSuggestedContacts(callback: () -> Unit) { private fun fillSuggestedContacts(callback: () -> Unit) {
val privateCursor = getMyContactsCursor().loadInBackground() val privateCursor = getMyContactsCursor()?.loadInBackground()
ensureBackgroundThread { ensureBackgroundThread {
privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor) privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor)
val suggestions = getSuggestedContacts(privateContacts) val suggestions = getSuggestedContacts(privateContacts)
@@ -160,10 +176,13 @@ class NewConversationActivity : SimpleActivity() {
layoutInflater.inflate(R.layout.item_suggested_contact, null).apply { layoutInflater.inflate(R.layout.item_suggested_contact, null).apply {
suggested_contact_name.text = contact.name suggested_contact_name.text = contact.name
suggested_contact_name.setTextColor(baseConfig.textColor) suggested_contact_name.setTextColor(baseConfig.textColor)
SimpleContactsHelper(this@NewConversationActivity).loadContactImage(contact.photoUri, suggested_contact_image, contact.name)
suggestions_holder.addView(this) if (!isDestroyed) {
setOnClickListener { SimpleContactsHelper(this@NewConversationActivity).loadContactImage(contact.photoUri, suggested_contact_image, contact.name)
launchThreadActivity(contact.phoneNumbers.first(), contact.name) suggestions_holder.addView(this)
setOnClickListener {
launchThreadActivity(contact.phoneNumbers.first(), contact.name)
}
} }
} }
} }
@@ -187,11 +206,13 @@ class NewConversationActivity : SimpleActivity() {
private fun launchThreadActivity(phoneNumber: String, name: String) { private fun launchThreadActivity(phoneNumber: String, name: String) {
val text = intent.getStringExtra(Intent.EXTRA_TEXT) ?: "" val text = intent.getStringExtra(Intent.EXTRA_TEXT) ?: ""
val numbers = phoneNumber.split(";").toSet()
val number = if (numbers.size == 1) phoneNumber else Gson().toJson(numbers)
Intent(this, ThreadActivity::class.java).apply { Intent(this, ThreadActivity::class.java).apply {
putExtra(THREAD_ID, getThreadId(phoneNumber).toInt()) putExtra(THREAD_ID, getThreadId(numbers))
putExtra(THREAD_TITLE, name) putExtra(THREAD_TITLE, name)
putExtra(THREAD_TEXT, text) putExtra(THREAD_TEXT, text)
putExtra(THREAD_NUMBER, phoneNumber) putExtra(THREAD_NUMBER, number)
if (intent.action == Intent.ACTION_SEND && intent.extras?.containsKey(Intent.EXTRA_STREAM) == true) { if (intent.action == Intent.ACTION_SEND && intent.extras?.containsKey(Intent.EXTRA_STREAM) == true) {
val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM) val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)

View File

@@ -0,0 +1,146 @@
package com.simplemobiletools.smsmessenger.activities
import android.annotation.SuppressLint
import android.app.SearchManager
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.text.TextUtils
import android.util.TypedValue
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuItemCompat
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.adapters.SearchResultsAdapter
import com.simplemobiletools.smsmessenger.extensions.conversationsDB
import com.simplemobiletools.smsmessenger.extensions.messagesDB
import com.simplemobiletools.smsmessenger.helpers.SEARCHED_MESSAGE_ID
import com.simplemobiletools.smsmessenger.helpers.THREAD_ID
import com.simplemobiletools.smsmessenger.helpers.THREAD_TITLE
import com.simplemobiletools.smsmessenger.models.Conversation
import com.simplemobiletools.smsmessenger.models.Message
import com.simplemobiletools.smsmessenger.models.SearchResult
import kotlinx.android.synthetic.main.activity_search.*
class SearchActivity : SimpleActivity() {
private var mIsSearchOpen = false
private var mLastSearchedText = ""
private var mSearchMenuItem: MenuItem? = null
@SuppressLint("InlinedApi")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_search)
updateTextColors(search_holder)
search_placeholder.setTextSize(TypedValue.COMPLEX_UNIT_PX, getTextSize())
search_placeholder_2.setTextSize(TypedValue.COMPLEX_UNIT_PX, getTextSize())
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_search, menu)
setupSearch(menu)
return true
}
private fun setupSearch(menu: Menu) {
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
mSearchMenuItem = menu.findItem(R.id.search)
MenuItemCompat.setOnActionExpandListener(mSearchMenuItem, object : MenuItemCompat.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
mIsSearchOpen = true
return true
}
// this triggers on device rotation too, avoid doing anything
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
if (mIsSearchOpen) {
mIsSearchOpen = false
mLastSearchedText = ""
finish()
}
return true
}
})
mSearchMenuItem?.expandActionView()
(mSearchMenuItem?.actionView as? SearchView)?.apply {
setSearchableInfo(searchManager.getSearchableInfo(componentName))
isSubmitButtonEnabled = false
setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String) = false
override fun onQueryTextChange(newText: String): Boolean {
if (mIsSearchOpen) {
mLastSearchedText = newText
textChanged(newText)
}
return true
}
})
}
}
private fun textChanged(text: String) {
search_placeholder_2.beGoneIf(text.length >= 2)
if (text.length >= 2) {
ensureBackgroundThread {
val searchQuery = "%$text%"
val messages = messagesDB.getMessagesWithText(searchQuery)
val conversations = conversationsDB.getConversationsWithText(searchQuery)
if (text == mLastSearchedText) {
showSearchResults(messages, conversations, text)
}
}
} else {
search_placeholder.beVisible()
search_results_list.beGone()
}
}
private fun showSearchResults(messages: List<Message>, conversations: List<Conversation>, searchedText: String) {
val searchResults = ArrayList<SearchResult>()
conversations.forEach { conversation ->
val date = conversation.date.formatDateOrTime(this, true, true)
val searchResult = SearchResult(-1, conversation.title, conversation.phoneNumber, date, conversation.threadId, conversation.photoUri)
searchResults.add(searchResult)
}
messages.forEach { message ->
var recipient = message.senderName
if (recipient.isEmpty() && message.participants.isNotEmpty()) {
val participantNames = message.participants.map { it.name }
recipient = TextUtils.join(", ", participantNames)
}
val date = message.date.formatDateOrTime(this, true, true)
val searchResult = SearchResult(message.id, recipient, message.body, date, message.threadId, message.senderPhotoUri)
searchResults.add(searchResult)
}
runOnUiThread {
search_results_list.beVisibleIf(searchResults.isNotEmpty())
search_placeholder.beVisibleIf(searchResults.isEmpty())
val currAdapter = search_results_list.adapter
if (currAdapter == null) {
SearchResultsAdapter(this, searchResults, search_results_list, searchedText) {
Intent(this, ThreadActivity::class.java).apply {
putExtra(THREAD_ID, (it as SearchResult).threadId)
putExtra(THREAD_TITLE, it.title)
putExtra(SEARCHED_MESSAGE_ID, it.messageId)
startActivity(this)
}
}.apply {
search_results_list.adapter = this
}
} else {
(currAdapter as SearchResultsAdapter).updateItems(searchResults, searchedText)
}
}
}
}

View File

@@ -30,10 +30,12 @@ class SettingsActivity : SimpleActivity() {
setupPurchaseThankYou() setupPurchaseThankYou()
setupCustomizeColors() setupCustomizeColors()
setupCustomizeNotifications()
setupUseEnglish() setupUseEnglish()
setupManageBlockedNumbers() setupManageBlockedNumbers()
setupChangeDateTimeFormat() setupChangeDateTimeFormat()
setupFontSize() setupFontSize()
setupShowCharacterCounter()
updateTextColors(settings_scrollview) updateTextColors(settings_scrollview)
if (blockedNumbersAtPause != -1 && blockedNumbersAtPause != getBlockedNumbers().hashCode()) { if (blockedNumbersAtPause != -1 && blockedNumbersAtPause != getBlockedNumbers().hashCode()) {
@@ -52,15 +54,23 @@ class SettingsActivity : SimpleActivity() {
} }
private fun setupPurchaseThankYou() { private fun setupPurchaseThankYou() {
settings_purchase_thank_you_holder.beVisibleIf(!isThankYouInstalled()) settings_purchase_thank_you_holder.beGoneIf(isOrWasThankYouInstalled())
settings_purchase_thank_you_holder.setOnClickListener { settings_purchase_thank_you_holder.setOnClickListener {
launchPurchaseThankYouIntent() launchPurchaseThankYouIntent()
} }
} }
private fun setupCustomizeColors() { private fun setupCustomizeColors() {
settings_customize_colors_label.text = getCustomizeColorsString()
settings_customize_colors_holder.setOnClickListener { settings_customize_colors_holder.setOnClickListener {
startCustomizationActivity() handleCustomizeColorsClick()
}
}
private fun setupCustomizeNotifications() {
settings_customize_notifications_holder.beVisibleIf(isOreoPlus())
settings_customize_notifications_holder.setOnClickListener {
launchCustomizeNotificationsIntent()
} }
} }
@@ -106,4 +116,12 @@ class SettingsActivity : SimpleActivity() {
} }
} }
} }
private fun setupShowCharacterCounter() {
settings_show_character_counter.isChecked = config.showCharacterCounter
settings_show_character_counter_holder.setOnClickListener {
settings_show_character_counter.toggle()
config.showCharacterCounter = settings_show_character_counter.isChecked
}
}
} }

View File

@@ -5,12 +5,15 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.media.MediaMetadataRetriever import android.media.MediaMetadataRetriever
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.provider.Telephony import android.provider.Telephony
import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager
import android.text.TextUtils import android.text.TextUtils
import android.util.TypedValue
import android.view.* import android.view.*
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.LinearLayout import android.widget.LinearLayout
@@ -38,6 +41,9 @@ import com.simplemobiletools.smsmessenger.adapters.ThreadAdapter
import com.simplemobiletools.smsmessenger.extensions.* import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.* import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.models.* import com.simplemobiletools.smsmessenger.models.*
import com.simplemobiletools.smsmessenger.receivers.SmsStatusDeliveredReceiver
import com.simplemobiletools.smsmessenger.receivers.SmsStatusSentReceiver
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_thread.* import kotlinx.android.synthetic.main.activity_thread.*
import kotlinx.android.synthetic.main.item_attachment.view.* import kotlinx.android.synthetic.main.item_attachment.view.*
import kotlinx.android.synthetic.main.item_selected_contact.view.* import kotlinx.android.synthetic.main.item_selected_contact.view.*
@@ -49,9 +55,10 @@ class ThreadActivity : SimpleActivity() {
private val MIN_DATE_TIME_DIFF_SECS = 300 private val MIN_DATE_TIME_DIFF_SECS = 300
private val PICK_ATTACHMENT_INTENT = 1 private val PICK_ATTACHMENT_INTENT = 1
private var threadId = 0 private var threadId = 0L
private var currentSIMCardIndex = 0 private var currentSIMCardIndex = 0
private var isActivityVisible = false private var isActivityVisible = false
private var refreshedSinceSent = false
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>()
@@ -71,7 +78,7 @@ class ThreadActivity : SimpleActivity() {
return return
} }
threadId = intent.getIntExtra(THREAD_ID, 0) threadId = intent.getLongExtra(THREAD_ID, 0L)
intent.getStringExtra(THREAD_TITLE)?.let { intent.getStringExtra(THREAD_TITLE)?.let {
supportActionBar?.title = it supportActionBar?.title = it
} }
@@ -80,7 +87,19 @@ class ThreadActivity : SimpleActivity() {
bus!!.register(this) bus!!.register(this)
handlePermission(PERMISSION_READ_PHONE_STATE) { handlePermission(PERMISSION_READ_PHONE_STATE) {
if (it) { if (it) {
setupThread() setupButtons()
setupCachedMessages {
val searchedMessageId = intent.getLongExtra(SEARCHED_MESSAGE_ID, -1L)
intent.removeExtra(SEARCHED_MESSAGE_ID)
if (searchedMessageId != -1L) {
val index = threadItems.indexOfFirst { (it as? Message)?.id == searchedMessageId }
if (index != -1) {
thread_messages_list.smoothScrollToPosition(index)
}
}
setupThread()
}
} else { } else {
finish() finish()
} }
@@ -97,94 +116,6 @@ class ThreadActivity : SimpleActivity() {
isActivityVisible = false isActivityVisible = false
} }
private fun setupThread() {
val privateCursor = getMyContactsCursor().loadInBackground()
ensureBackgroundThread {
messages = getMessages(threadId)
participants = if (messages.isEmpty()) {
getThreadParticipants(threadId, null)
} else {
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.doesContainPhoneNumber(it.name) }.forEach { participant ->
privateContacts.firstOrNull { it.doesContainPhoneNumber(participant.phoneNumbers.first()) }?.apply {
senderNumbersToReplace[participant.phoneNumbers.first()] = name
participant.name = name
participant.photoUri = photoUri
}
}
messages.forEach { message ->
if (senderNumbersToReplace.keys.contains(message.senderName)) {
message.senderName = senderNumbersToReplace[message.senderName]!!
}
}
}
if (participants.isEmpty()) {
val name = intent.getStringExtra(THREAD_TITLE) ?: ""
val number = intent.getStringExtra(THREAD_NUMBER)
if (number == null) {
toast(R.string.unknown_error_occurred)
finish()
return@ensureBackgroundThread
}
val contact = SimpleContact(0, 0, name, "", arrayListOf(number))
participants.add(contact)
}
messages.filter { it.attachment != null }.forEach {
it.attachment!!.attachments.forEach {
try {
if (it.mimetype.startsWith("image/")) {
val fileOptions = BitmapFactory.Options()
fileOptions.inJustDecodeBounds = true
BitmapFactory.decodeStream(contentResolver.openInputStream(it.uri), null, fileOptions)
it.width = fileOptions.outWidth
it.height = fileOptions.outHeight
} else if (it.mimetype.startsWith("video/")) {
val metaRetriever = MediaMetadataRetriever()
metaRetriever.setDataSource(this, it.uri)
it.width = metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)!!.toInt()
it.height = metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)!!.toInt()
}
if (it.width < 0) {
it.width = 0
}
if (it.height < 0) {
it.height = 0
}
} catch (ignored: Exception) {
}
}
}
setupAdapter()
runOnUiThread {
val threadTitle = participants.getThreadTitle()
if (threadTitle.isNotEmpty()) {
supportActionBar?.title = participants.getThreadTitle()
}
if (messages.isEmpty()) {
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
thread_type_message.requestFocus()
}
setupSIMSelector()
}
}
setupButtons()
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
bus?.unregister(this) bus?.unregister(this)
@@ -223,13 +154,98 @@ class ThreadActivity : SimpleActivity() {
} }
} }
private fun setupCachedMessages(callback: () -> Unit) {
ensureBackgroundThread {
messages = messagesDB.getThreadMessages(threadId).toMutableList() as ArrayList<Message>
setupParticipants()
setupAdapter()
runOnUiThread {
if (messages.isEmpty()) {
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
thread_type_message.requestFocus()
}
setupThreadTitle()
setupSIMSelector()
callback()
}
}
}
private fun setupThread() {
val privateCursor = getMyContactsCursor()?.loadInBackground()
ensureBackgroundThread {
val cachedMessagesCode = messages.hashCode()
messages = getMessages(threadId)
if (messages.hashCode() == cachedMessagesCode && participants.isNotEmpty()) {
return@ensureBackgroundThread
}
setupParticipants()
// 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.doesContainPhoneNumber(it.name) }.forEach { participant ->
privateContacts.firstOrNull { it.doesContainPhoneNumber(participant.phoneNumbers.first()) }?.apply {
senderNumbersToReplace[participant.phoneNumbers.first()] = name
participant.name = name
participant.photoUri = photoUri
}
}
messages.forEach { message ->
if (senderNumbersToReplace.keys.contains(message.senderName)) {
message.senderName = senderNumbersToReplace[message.senderName]!!
}
}
}
if (participants.isEmpty()) {
val name = intent.getStringExtra(THREAD_TITLE) ?: ""
val number = intent.getStringExtra(THREAD_NUMBER)
if (number == null) {
toast(R.string.unknown_error_occurred)
finish()
return@ensureBackgroundThread
}
val contact = SimpleContact(0, 0, name, "", arrayListOf(number), ArrayList(), ArrayList())
participants.add(contact)
}
messages.chunked(30).forEach { currentMessages ->
messagesDB.insertMessages(*currentMessages.toTypedArray())
}
setupAttachmentSizes()
setupAdapter()
runOnUiThread {
setupThreadTitle()
setupSIMSelector()
}
}
}
private fun setupAdapter() { private fun setupAdapter() {
threadItems = getThreadItems() threadItems = getThreadItems()
invalidateOptionsMenu() invalidateOptionsMenu()
runOnUiThread { runOnUiThread {
val adapter = ThreadAdapter(this, threadItems, thread_messages_list, thread_messages_fastscroller) {} val currAdapter = thread_messages_list.adapter
thread_messages_list.adapter = adapter if (currAdapter == null) {
ThreadAdapter(this, threadItems, thread_messages_list, thread_messages_fastscroller) {
(it as? ThreadError)?.apply {
thread_type_message.setText(it.messageText)
}
}.apply {
thread_messages_list.adapter = this
}
} else {
(currAdapter as ThreadAdapter).updateMessages(threadItems)
}
} }
SimpleContactsHelper(this).getAvailableContacts(false) { contacts -> SimpleContactsHelper(this).getAvailableContacts(false) { contacts ->
@@ -252,7 +268,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, "", arrayListOf(number)) val contact = SimpleContact(number.hashCode(), number.hashCode(), number, "", arrayListOf(number), ArrayList(), ArrayList())
addSelectedContact(contact) addSelectedContact(contact)
} }
} }
@@ -264,6 +280,10 @@ class ThreadActivity : SimpleActivity() {
confirm_manage_contacts.applyColorFilter(textColor) confirm_manage_contacts.applyColorFilter(textColor)
thread_add_attachment.applyColorFilter(textColor) thread_add_attachment.applyColorFilter(textColor)
thread_character_counter.beVisibleIf(config.showCharacterCounter)
thread_character_counter.setTextSize(TypedValue.COMPLEX_UNIT_PX, getTextSize())
thread_type_message.setTextSize(TypedValue.COMPLEX_UNIT_PX, getTextSize())
thread_send_message.setOnClickListener { thread_send_message.setOnClickListener {
sendMessage() sendMessage()
} }
@@ -271,6 +291,7 @@ class ThreadActivity : SimpleActivity() {
thread_send_message.isClickable = false thread_send_message.isClickable = false
thread_type_message.onTextChangeListener { thread_type_message.onTextChangeListener {
checkSendMessageAvailability() checkSendMessageAvailability()
thread_character_counter.text = it.length.toString()
} }
confirm_manage_contacts.setOnClickListener { confirm_manage_contacts.setOnClickListener {
@@ -284,7 +305,7 @@ class ThreadActivity : SimpleActivity() {
} }
} }
val newThreadId = getThreadId(numbers).toInt() val newThreadId = getThreadId(numbers)
if (threadId != newThreadId) { if (threadId != newThreadId) {
Intent(this, ThreadActivity::class.java).apply { Intent(this, ThreadActivity::class.java).apply {
putExtra(THREAD_ID, newThreadId) putExtra(THREAD_ID, newThreadId)
@@ -309,12 +330,59 @@ class ThreadActivity : SimpleActivity() {
} }
} }
private fun setupAttachmentSizes() {
messages.filter { it.attachment != null }.forEach {
it.attachment!!.attachments.forEach {
try {
if (it.mimetype.startsWith("image/")) {
val fileOptions = BitmapFactory.Options()
fileOptions.inJustDecodeBounds = true
BitmapFactory.decodeStream(contentResolver.openInputStream(it.getUri()), null, fileOptions)
it.width = fileOptions.outWidth
it.height = fileOptions.outHeight
} else if (it.mimetype.startsWith("video/")) {
val metaRetriever = MediaMetadataRetriever()
metaRetriever.setDataSource(this, it.getUri())
it.width = metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)!!.toInt()
it.height = metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)!!.toInt()
}
if (it.width < 0) {
it.width = 0
}
if (it.height < 0) {
it.height = 0
}
} catch (ignored: Exception) {
}
}
}
}
private fun setupParticipants() {
if (participants.isEmpty()) {
participants = if (messages.isEmpty()) {
getThreadParticipants(threadId, null)
} else {
messages.first().participants
}
}
}
private fun setupThreadTitle() {
val threadTitle = participants.getThreadTitle()
if (threadTitle.isNotEmpty()) {
supportActionBar?.title = participants.getThreadTitle()
}
}
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
private fun setupSIMSelector() { private fun setupSIMSelector() {
val availableSIMs = SubscriptionManager.from(this).activeSubscriptionInfoList ?: return val availableSIMs = SubscriptionManager.from(this).activeSubscriptionInfoList ?: return
if (availableSIMs.size > 1) { if (availableSIMs.size > 1) {
availableSIMs.forEachIndexed { index, subscriptionInfo -> availableSIMs.forEachIndexed { index, subscriptionInfo ->
var label = subscriptionInfo.displayName.toString() var label = subscriptionInfo.displayName?.toString() ?: ""
if (subscriptionInfo.number?.isNotEmpty() == true) { if (subscriptionInfo.number?.isNotEmpty() == true) {
label += " (${subscriptionInfo.number})" label += " (${subscriptionInfo.number})"
} }
@@ -329,6 +397,10 @@ class ThreadActivity : SimpleActivity() {
} }
} }
if (numbers.isEmpty()) {
return
}
currentSIMCardIndex = availableSIMs.indexOfFirstOrNull { it.subscriptionId == config.getUseSIMIdAtNumber(numbers.first()) } ?: 0 currentSIMCardIndex = availableSIMs.indexOfFirstOrNull { it.subscriptionId == config.getUseSIMIdAtNumber(numbers.first()) } ?: 0
thread_select_sim_icon.applyColorFilter(config.textColor) thread_select_sim_icon.applyColorFilter(config.textColor)
@@ -396,11 +468,20 @@ class ThreadActivity : SimpleActivity() {
} }
private fun showSelectedContacts() { private fun showSelectedContacts() {
val adjustedColor = getAdjustedPrimaryColor()
val views = ArrayList<View>() val views = ArrayList<View>()
participants.forEach { participants.forEach {
val contact = it val contact = it
layoutInflater.inflate(R.layout.item_selected_contact, null).apply { layoutInflater.inflate(R.layout.item_selected_contact, null).apply {
val selectedContactBg = resources.getDrawable(R.drawable.item_selected_contact_background)
(selectedContactBg as LayerDrawable).findDrawableByLayerId(R.id.selected_contact_bg).applyColorFilter(adjustedColor)
selected_contact_holder.background = selectedContactBg
selected_contact_name.text = contact.name selected_contact_name.text = contact.name
selected_contact_name.setTextColor(adjustedColor.getContrastColor())
selected_contact_remove.applyColorFilter(adjustedColor.getContrastColor())
selected_contact_remove.setOnClickListener { selected_contact_remove.setOnClickListener {
if (contact.rawId != participants.first().rawId) { if (contact.rawId != participants.first().rawId) {
removeSelectedContact(contact.rawId) removeSelectedContact(contact.rawId)
@@ -424,7 +505,7 @@ class ThreadActivity : SimpleActivity() {
private fun markAsUnread() { private fun markAsUnread() {
ensureBackgroundThread { ensureBackgroundThread {
conversationsDB.markUnread(threadId.toLong()) conversationsDB.markUnread(threadId)
markThreadMessagesUnread(threadId) markThreadMessagesUnread(threadId)
runOnUiThread { runOnUiThread {
finish() finish()
@@ -462,13 +543,17 @@ class ThreadActivity : SimpleActivity() {
items.add(message) items.add(message)
if (message.type == Telephony.Sms.MESSAGE_TYPE_FAILED) { if (message.type == Telephony.Sms.MESSAGE_TYPE_FAILED) {
items.add(ThreadError(message.id)) items.add(ThreadError(message.id, message.body))
}
if (message.type == Telephony.Sms.MESSAGE_TYPE_OUTBOX) {
items.add(ThreadSending(message.id))
} }
if (!message.read) { if (!message.read) {
hadUnreadItems = true hadUnreadItems = true
markMessageRead(message.id, message.isMMS) markMessageRead(message.id, message.isMMS)
conversationsDB.markRead(threadId.toLong()) conversationsDB.markRead(threadId)
} }
if (i == cnt - 1 && message.type == Telephony.Sms.MESSAGE_TYPE_SENT) { if (i == cnt - 1 && message.type == Telephony.Sms.MESSAGE_TYPE_SENT) {
@@ -562,6 +647,7 @@ class ThreadActivity : SimpleActivity() {
val settings = Settings() val settings = Settings()
settings.useSystemSending = true settings.useSystemSending = true
settings.deliveryReports = true
val SIMId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId val SIMId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId
if (SIMId != null) { if (SIMId != null) {
@@ -582,19 +668,35 @@ class ThreadActivity : SimpleActivity() {
message.addMedia(byteArray, mimeType) message.addMedia(byteArray, mimeType)
} catch (e: Exception) { } catch (e: Exception) {
showErrorToast(e) showErrorToast(e)
} catch (e: Error) {
toast(e.localizedMessage ?: getString(R.string.unknown_error_occurred))
} }
} }
} }
try { try {
transaction.sendNewMessage(message, threadId.toLong()) val smsSentIntent = Intent(this, SmsStatusSentReceiver::class.java)
val deliveredIntent = Intent(this, SmsStatusDeliveredReceiver::class.java)
transaction.setExplicitBroadcastForSentSms(smsSentIntent)
transaction.setExplicitBroadcastForDeliveredSms(deliveredIntent)
refreshedSinceSent = false
transaction.sendNewMessage(message, threadId)
thread_type_message.setText("") thread_type_message.setText("")
attachmentUris.clear() attachmentUris.clear()
thread_attachments_holder.beGone() thread_attachments_holder.beGone()
thread_attachments_wrapper.removeAllViews() thread_attachments_wrapper.removeAllViews()
Handler().postDelayed({
if (!refreshedSinceSent) {
refreshMessages()
}
}, 2000)
} catch (e: Exception) { } catch (e: Exception) {
showErrorToast(e) showErrorToast(e)
} catch (e: Error) {
toast(e.localizedMessage ?: getString(R.string.unknown_error_occurred))
} }
} }
@@ -652,13 +754,30 @@ class ThreadActivity : SimpleActivity() {
showSelectedContacts() showSelectedContacts()
} }
@SuppressLint("MissingPermission")
@Subscribe(threadMode = ThreadMode.ASYNC) @Subscribe(threadMode = ThreadMode.ASYNC)
fun refreshMessages(event: Events.RefreshMessages) { fun refreshMessages(event: Events.RefreshMessages) {
refreshedSinceSent = true
if (isActivityVisible) { if (isActivityVisible) {
notificationManager.cancel(threadId) notificationManager.cancel(threadId.hashCode())
} }
val lastMaxId = messages.maxByOrNull { it.id }?.id ?: 0L
messages = getMessages(threadId) messages = getMessages(threadId)
messages.filter { !it.isReceivedMessage() && it.id > lastMaxId }.forEach { latestMessage ->
// subscriptionIds seem to be not filled out at sending with multiple SIM cards, so fill it manually
if (SubscriptionManager.from(this).activeSubscriptionInfoList?.size ?: 0 > 1) {
val SIMId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId
if (SIMId != null) {
updateMessageSubscriptionId(latestMessage.id, SIMId)
latestMessage.subscriptionId = SIMId
}
}
messagesDB.insertOrIgnore(latestMessage)
}
setupAdapter() setupAdapter()
} }
} }

View File

@@ -5,12 +5,16 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Filter import android.widget.Filter
import android.widget.RelativeLayout
import android.widget.TextView import android.widget.TextView
import com.simplemobiletools.commons.extensions.darkenColor
import com.simplemobiletools.commons.extensions.getContrastColor
import com.simplemobiletools.commons.extensions.normalizeString import com.simplemobiletools.commons.extensions.normalizeString
import com.simplemobiletools.commons.helpers.SimpleContactsHelper import com.simplemobiletools.commons.helpers.SimpleContactsHelper
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.SimpleActivity import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.extensions.config
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>()
@@ -30,8 +34,13 @@ class AutoCompleteTextViewAdapter(val activity: SimpleActivity, val contacts: Ar
isFocusable = false isFocusable = false
} }
val backgroundColor = activity.config.backgroundColor
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.phoneNumbers.first() findViewById<TextView>(R.id.item_contact_number).text = contact.phoneNumbers.first()
findViewById<RelativeLayout>(R.id.item_contact_holder).setBackgroundColor(backgroundColor.darkenColor())
findViewById<TextView>(R.id.item_contact_name).setTextColor(backgroundColor.getContrastColor())
findViewById<TextView>(R.id.item_contact_number).setTextColor(backgroundColor.getContrastColor())
SimpleContactsHelper(context).loadContactImage(contact.photoUri, findViewById(R.id.item_contact_image), contact.name) SimpleContactsHelper(context).loadContactImage(contact.photoUri, findViewById(R.id.item_contact_image), contact.name)
} }

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.net.Uri
import android.text.TextUtils import android.text.TextUtils
import android.util.TypedValue import android.util.TypedValue
import android.view.Menu import android.view.Menu
@@ -37,8 +38,10 @@ 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_block_number).isVisible = isNougatPlus() findItem(R.id.cab_block_number).isVisible = isNougatPlus()
findItem(R.id.cab_add_number_to_contact).isVisible = isOneItemSelected() && getSelectedItems().firstOrNull()?.isGroupConversation == false
findItem(R.id.cab_dial_number).isVisible = isOneItemSelected() && getSelectedItems().firstOrNull()?.isGroupConversation == false
findItem(R.id.cab_copy_number).isVisible = isOneItemSelected() && getSelectedItems().firstOrNull()?.isGroupConversation == false
} }
} }
@@ -50,8 +53,10 @@ 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_block_number -> askConfirmBlock()
R.id.cab_select_all -> selectAll() R.id.cab_dial_number -> dialNumber()
R.id.cab_copy_number -> copyNumberToClipboard()
R.id.cab_delete -> askConfirmDelete() R.id.cab_delete -> askConfirmDelete()
R.id.cab_select_all -> selectAll()
} }
} }
@@ -59,9 +64,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)?.thread_id override fun getItemSelectionKey(position: Int) = conversations.getOrNull(position)?.hashCode()
override fun getItemKeyPosition(key: Int) = conversations.indexOfFirst { it.thread_id == key } override fun getItemKeyPosition(key: Int) = conversations.indexOfFirst { it.hashCode() == key }
override fun onActionModeCreated() {} override fun onActionModeCreated() {}
@@ -110,6 +115,26 @@ class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayLis
} }
} }
private fun dialNumber() {
val conversation = getSelectedItems().firstOrNull() ?: return
Intent(Intent.ACTION_DIAL).apply {
data = Uri.fromParts("tel", conversation.phoneNumber, null)
if (resolveActivity(activity.packageManager) != null) {
activity.startActivity(this)
finishActMode()
} else {
activity.toast(R.string.no_app_found)
}
}
}
private fun copyNumberToClipboard() {
val conversation = getSelectedItems().firstOrNull() ?: return
activity.copyToClipboard(conversation.phoneNumber)
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)
@@ -129,13 +154,17 @@ class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayLis
return return
} }
val conversationsToRemove = conversations.filter { selectedKeys.contains(it.thread_id) } as ArrayList<Conversation> val conversationsToRemove = conversations.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
val positions = getSelectedItemPositions() val positions = getSelectedItemPositions()
conversationsToRemove.forEach { conversationsToRemove.forEach {
activity.deleteConversation(it.thread_id) activity.deleteConversation(it.threadId)
activity.notificationManager.cancel(it.thread_id) activity.notificationManager.cancel(it.hashCode())
}
try {
conversations.removeAll(conversationsToRemove)
} catch (ignored: Exception) {
} }
conversations.removeAll(conversationsToRemove)
activity.runOnUiThread { activity.runOnUiThread {
if (conversationsToRemove.isEmpty()) { if (conversationsToRemove.isEmpty()) {
@@ -165,7 +194,7 @@ class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayLis
} }
} }
private fun getSelectedItems() = conversations.filter { selectedKeys.contains(it.thread_id) } as ArrayList<Conversation> private fun getSelectedItems() = conversations.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
override fun onViewRecycled(holder: ViewHolder) { override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder) super.onViewRecycled(holder)
@@ -190,7 +219,7 @@ class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayLis
private fun setupView(view: View, conversation: Conversation) { private fun setupView(view: View, conversation: Conversation) {
view.apply { view.apply {
conversation_frame.isSelected = selectedKeys.contains(conversation.thread_id) conversation_frame.isSelected = selectedKeys.contains(conversation.hashCode())
conversation_address.apply { conversation_address.apply {
text = conversation.title text = conversation.title
@@ -203,7 +232,7 @@ class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayLis
} }
conversation_date.apply { conversation_date.apply {
text = conversation.date.formatDateOrTime(context, true) text = conversation.date.formatDateOrTime(context, true, false)
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 0.8f) setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 0.8f)
} }

View File

@@ -0,0 +1,96 @@
package com.simplemobiletools.smsmessenger.adapters
import android.util.TypedValue
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import com.bumptech.glide.Glide
import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
import com.simplemobiletools.commons.extensions.getTextSize
import com.simplemobiletools.commons.extensions.highlightTextPart
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.models.SearchResult
import kotlinx.android.synthetic.main.item_search_result.view.*
import java.util.*
class SearchResultsAdapter(
activity: SimpleActivity, var searchResults: ArrayList<SearchResult>, recyclerView: MyRecyclerView, highlightText: String, itemClick: (Any) -> Unit
) : MyRecyclerViewAdapter(activity, recyclerView, null, itemClick) {
private var fontSize = activity.getTextSize()
private var textToHighlight = highlightText
override fun getActionMenuId() = 0
override fun prepareActionMode(menu: Menu) {}
override fun actionItemPressed(id: Int) {}
override fun getSelectableItemCount() = searchResults.size
override fun getIsItemSelectable(position: Int) = false
override fun getItemSelectionKey(position: Int) = searchResults.getOrNull(position)?.hashCode()
override fun getItemKeyPosition(key: Int) = searchResults.indexOfFirst { it.hashCode() == key }
override fun onActionModeCreated() {}
override fun onActionModeDestroyed() {}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_search_result, parent)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val searchResult = searchResults[position]
holder.bindView(searchResult, true, false) { itemView, layoutPosition ->
setupView(itemView, searchResult)
}
bindViewHolder(holder)
}
override fun getItemCount() = searchResults.size
fun updateItems(newItems: ArrayList<SearchResult>, highlightText: String = "") {
if (newItems.hashCode() != searchResults.hashCode()) {
searchResults = newItems.clone() as ArrayList<SearchResult>
textToHighlight = highlightText
notifyDataSetChanged()
} else if (textToHighlight != highlightText) {
textToHighlight = highlightText
notifyDataSetChanged()
}
}
private fun setupView(view: View, searchResult: SearchResult) {
view.apply {
search_result_title.apply {
text = searchResult.title.highlightTextPart(textToHighlight, adjustedPrimaryColor)
setTextColor(textColor)
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 1.2f)
}
search_result_snippet.apply {
text = searchResult.snippet.highlightTextPart(textToHighlight, adjustedPrimaryColor)
setTextColor(textColor)
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 0.9f)
}
search_result_date.apply {
text = searchResult.date
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 0.8f)
}
SimpleContactsHelper(context).loadContactImage(searchResult.photoUri, search_result_image, searchResult.title)
}
}
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
if (!activity.isDestroyed && !activity.isFinishing && holder.itemView.search_result_image != null) {
Glide.with(activity).clear(holder.itemView.search_result_image)
}
}
}

View File

@@ -29,6 +29,7 @@ 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
import com.simplemobiletools.smsmessenger.activities.SimpleActivity import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.dialogs.SelectTextDialog
import com.simplemobiletools.smsmessenger.extensions.deleteMessage import com.simplemobiletools.smsmessenger.extensions.deleteMessage
import com.simplemobiletools.smsmessenger.helpers.* import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.models.* import com.simplemobiletools.smsmessenger.models.*
@@ -37,10 +38,14 @@ import kotlinx.android.synthetic.main.item_received_message.view.*
import kotlinx.android.synthetic.main.item_received_unknown_attachment.view.* 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_sent_unknown_attachment.view.*
import kotlinx.android.synthetic.main.item_thread_date_time.view.* import kotlinx.android.synthetic.main.item_thread_date_time.view.*
import kotlinx.android.synthetic.main.item_thread_error.view.*
import kotlinx.android.synthetic.main.item_thread_sending.view.*
import kotlinx.android.synthetic.main.item_thread_success.view.* import kotlinx.android.synthetic.main.item_thread_success.view.*
class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem>, recyclerView: MyRecyclerView, fastScroller: FastScroller, class ThreadAdapter(
itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { 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() private val roundedCornersRadius = resources.getDimension(R.dimen.normal_margin).toInt()
private var fontSize = activity.getTextSize() private var fontSize = activity.getTextSize()
@@ -58,6 +63,7 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
menu.apply { menu.apply {
findItem(R.id.cab_copy_to_clipboard).isVisible = isOneItemSelected findItem(R.id.cab_copy_to_clipboard).isVisible = isOneItemSelected
findItem(R.id.cab_share).isVisible = isOneItemSelected findItem(R.id.cab_share).isVisible = isOneItemSelected
findItem(R.id.cab_select_text).isVisible = isOneItemSelected
} }
} }
@@ -69,8 +75,9 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
when (id) { when (id) {
R.id.cab_copy_to_clipboard -> copyToClipboard() R.id.cab_copy_to_clipboard -> copyToClipboard()
R.id.cab_share -> shareText() R.id.cab_share -> shareText()
R.id.cab_select_all -> selectAll() R.id.cab_select_text -> selectText()
R.id.cab_delete -> askConfirmDelete() R.id.cab_delete -> askConfirmDelete()
R.id.cab_select_all -> selectAll()
} }
} }
@@ -78,9 +85,9 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
override fun getIsItemSelectable(position: Int) = !isThreadDateTime(position) override fun getIsItemSelectable(position: Int) = !isThreadDateTime(position)
override fun getItemSelectionKey(position: Int) = (messages.getOrNull(position) as? Message)?.id override fun getItemSelectionKey(position: Int) = (messages.getOrNull(position) as? Message)?.hashCode()
override fun getItemKeyPosition(key: Int) = messages.indexOfFirst { (it as? Message)?.id == key } override fun getItemKeyPosition(key: Int) = messages.indexOfFirst { (it as? Message)?.hashCode() == key }
override fun onActionModeCreated() {} override fun onActionModeCreated() {}
@@ -92,6 +99,7 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
THREAD_RECEIVED_MESSAGE -> R.layout.item_received_message THREAD_RECEIVED_MESSAGE -> R.layout.item_received_message
THREAD_SENT_MESSAGE_ERROR -> R.layout.item_thread_error THREAD_SENT_MESSAGE_ERROR -> R.layout.item_thread_error
THREAD_SENT_MESSAGE_SUCCESS -> R.layout.item_thread_success THREAD_SENT_MESSAGE_SUCCESS -> R.layout.item_thread_success
THREAD_SENT_MESSAGE_SENDING -> R.layout.item_thread_sending
else -> R.layout.item_sent_message else -> R.layout.item_sent_message
} }
return createViewHolder(layout, parent) return createViewHolder(layout, parent)
@@ -99,11 +107,15 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = messages[position] val item = messages[position]
holder.bindView(item, true, item is Message) { itemView, layoutPosition -> val isClickable = item is ThreadError || item is Message
val isLongClickable = item is Message
holder.bindView(item, isClickable, isLongClickable) { itemView, layoutPosition ->
when (item) { when (item) {
is ThreadDateTime -> setupDateTime(itemView, item) is ThreadDateTime -> setupDateTime(itemView, item)
is ThreadSuccess -> setupThreadSuccess(itemView) is ThreadSuccess -> setupThreadSuccess(itemView)
!is ThreadError -> setupView(itemView, item as Message) is ThreadError -> setupThreadError(itemView)
is ThreadSending -> setupThreadSending(itemView)
else -> setupView(itemView, item as Message)
} }
} }
bindViewHolder(holder) bindViewHolder(holder)
@@ -118,23 +130,38 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
(messages[position] as? Message)?.isReceivedMessage() == true -> THREAD_RECEIVED_MESSAGE (messages[position] as? Message)?.isReceivedMessage() == true -> THREAD_RECEIVED_MESSAGE
item is ThreadError -> THREAD_SENT_MESSAGE_ERROR item is ThreadError -> THREAD_SENT_MESSAGE_ERROR
item is ThreadSuccess -> THREAD_SENT_MESSAGE_SUCCESS item is ThreadSuccess -> THREAD_SENT_MESSAGE_SUCCESS
item is ThreadSending -> THREAD_SENT_MESSAGE_SENDING
else -> THREAD_SENT_MESSAGE else -> THREAD_SENT_MESSAGE
} }
} }
private fun copyToClipboard() { private fun copyToClipboard() {
val firstItem = getSelectedItems().first() as? Message ?: return val firstItem = getSelectedItems().firstOrNull() as? Message ?: return
activity.copyToClipboard(firstItem.body) activity.copyToClipboard(firstItem.body)
} }
private fun shareText() { private fun shareText() {
val firstItem = getSelectedItems().first() as? Message ?: return val firstItem = getSelectedItems().firstOrNull() as? Message ?: return
activity.shareTextIntent(firstItem.body) activity.shareTextIntent(firstItem.body)
} }
private fun selectText() {
val firstItem = getSelectedItems().firstOrNull() as? Message ?: return
if (firstItem.body.trim().isNotEmpty()) {
SelectTextDialog(activity, firstItem.body)
}
}
private fun askConfirmDelete() { private fun askConfirmDelete() {
val itemsCnt = selectedKeys.size val itemsCnt = selectedKeys.size
val items = resources.getQuantityString(R.plurals.delete_messages, itemsCnt, itemsCnt)
// not sure how we can get UnknownFormatConversionException here, so show the error and hope that someone reports it
val items = try {
resources.getQuantityString(R.plurals.delete_messages, itemsCnt, itemsCnt)
} catch (e: Exception) {
activity.showErrorToast(e)
return
}
val baseString = R.string.deletion_confirmation val baseString = R.string.deletion_confirmation
val question = String.format(resources.getString(baseString), items) val question = String.format(resources.getString(baseString), items)
@@ -151,7 +178,7 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
return return
} }
val messagesToRemove = messages.filter { selectedKeys.contains((it as? Message)?.id ?: 0) } as ArrayList<ThreadItem> val messagesToRemove = getSelectedItems()
val positions = getSelectedItemPositions() val positions = getSelectedItemPositions()
messagesToRemove.forEach { messagesToRemove.forEach {
activity.deleteMessage((it as Message).id, it.isMMS) activity.deleteMessage((it as Message).id, it.isMMS)
@@ -168,20 +195,23 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
} }
} }
private fun getSelectedItems() = messages.filter { selectedKeys.contains((it as? Message)?.id ?: 0) } as ArrayList<ThreadItem> private fun getSelectedItems() = messages.filter { selectedKeys.contains((it as? Message)?.hashCode() ?: 0) } as ArrayList<ThreadItem>
private fun isThreadDateTime(position: Int) = messages.getOrNull(position) is ThreadDateTime private fun isThreadDateTime(position: Int) = messages.getOrNull(position) is ThreadDateTime
override fun onViewRecycled(holder: ViewHolder) { fun updateMessages(newMessages: ArrayList<ThreadItem>) {
super.onViewRecycled(holder) val oldHashCode = messages.hashCode()
if (!activity.isDestroyed && !activity.isFinishing && holder.itemView.thread_message_sender_photo != null) { val newHashCode = newMessages.hashCode()
Glide.with(activity).clear(holder.itemView.thread_message_sender_photo) if (newHashCode != oldHashCode) {
messages = newMessages
notifyDataSetChanged()
recyclerView.scrollToPosition(messages.size - 1)
} }
} }
private fun setupView(view: View, message: Message) { private fun setupView(view: View, message: Message) {
view.apply { view.apply {
thread_message_holder.isSelected = selectedKeys.contains(message.id) thread_message_holder.isSelected = selectedKeys.contains(message.hashCode())
thread_message_body.apply { thread_message_body.apply {
text = message.body text = message.body
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize) setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize)
@@ -196,7 +226,7 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
} else { } else {
thread_message_sender_photo?.beGone() thread_message_sender_photo?.beGone()
val background = context.getAdjustedPrimaryColor() val background = context.getAdjustedPrimaryColor()
thread_message_body.background.applyColorFilter(background.adjustAlpha(0.8f)) thread_message_body.background.applyColorFilter(background)
val contrastColor = background.getContrastColor() val contrastColor = background.getContrastColor()
thread_message_body.setTextColor(contrastColor) thread_message_body.setTextColor(contrastColor)
@@ -207,7 +237,7 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
if (message.attachment?.attachments?.isNotEmpty() == true) { if (message.attachment?.attachments?.isNotEmpty() == true) {
for (attachment in message.attachment.attachments) { for (attachment in message.attachment.attachments) {
val mimetype = attachment.mimetype val mimetype = attachment.mimetype
val uri = attachment.uri val uri = attachment.getUri()
if (mimetype.startsWith("image/") || mimetype.startsWith("video/")) { if (mimetype.startsWith("image/") || mimetype.startsWith("video/")) {
val imageView = layoutInflater.inflate(R.layout.item_attachment_image, null) val imageView = layoutInflater.inflate(R.layout.item_attachment_image, null)
thread_mesage_attachments_holder.addView(imageView) thread_mesage_attachments_holder.addView(imageView)
@@ -238,7 +268,9 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
} }
builder.into(imageView.attachment_image) builder.into(imageView.attachment_image)
imageView.attachment_image.setOnClickListener { launchViewIntent(uri, mimetype, attachment.filename) } imageView.attachment_image.setOnClickListener {
launchViewIntent(uri, mimetype, attachment.filename)
}
} else { } else {
if (message.isReceivedMessage()) { if (message.isReceivedMessage()) {
val attachmentView = layoutInflater.inflate(R.layout.item_received_unknown_attachment, null).apply { val attachmentView = layoutInflater.inflate(R.layout.item_received_unknown_attachment, null).apply {
@@ -247,7 +279,9 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
thread_received_attachment_label.text = attachment.filename thread_received_attachment_label.text = attachment.filename
} }
setTextColor(textColor) setTextColor(textColor)
setOnClickListener { launchViewIntent(uri, mimetype, attachment.filename) } setOnClickListener {
launchViewIntent(uri, mimetype, attachment.filename)
}
} }
} }
thread_mesage_attachments_holder.addView(attachmentView) thread_mesage_attachments_holder.addView(attachmentView)
@@ -255,12 +289,14 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
val background = context.getAdjustedPrimaryColor() val background = context.getAdjustedPrimaryColor()
val attachmentView = layoutInflater.inflate(R.layout.item_sent_unknown_attachment, null).apply { val attachmentView = layoutInflater.inflate(R.layout.item_sent_unknown_attachment, null).apply {
thread_sent_attachment_label.apply { thread_sent_attachment_label.apply {
this.background.applyColorFilter(background.adjustAlpha(0.8f)) this.background.applyColorFilter(background)
setTextColor(background.getContrastColor()) setTextColor(background.getContrastColor())
if (attachment.filename.isNotEmpty()) { if (attachment.filename.isNotEmpty()) {
thread_sent_attachment_label.text = attachment.filename thread_sent_attachment_label.text = attachment.filename
} }
setOnClickListener { launchViewIntent(uri, mimetype, attachment.filename) } setOnClickListener {
launchViewIntent(uri, mimetype, attachment.filename)
}
} }
} }
thread_mesage_attachments_holder.addView(attachmentView) thread_mesage_attachments_holder.addView(attachmentView)
@@ -295,7 +331,7 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
private fun setupDateTime(view: View, dateTime: ThreadDateTime) { private fun setupDateTime(view: View, dateTime: ThreadDateTime) {
view.apply { view.apply {
thread_date_time.apply { thread_date_time.apply {
text = dateTime.date.formatDateOrTime(context, false) text = dateTime.date.formatDateOrTime(context, false, false)
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize) setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize)
} }
thread_date_time.setTextColor(textColor) thread_date_time.setTextColor(textColor)
@@ -313,4 +349,22 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
private fun setupThreadSuccess(view: View) { private fun setupThreadSuccess(view: View) {
view.thread_success.applyColorFilter(textColor) view.thread_success.applyColorFilter(textColor)
} }
private fun setupThreadError(view: View) {
view.thread_error.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize - 4)
}
private fun setupThreadSending(view: View) {
view.thread_sending.apply {
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize)
setTextColor(textColor)
}
}
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
if (!activity.isDestroyed && !activity.isFinishing && holder.itemView.thread_message_sender_photo != null) {
Glide.with(activity).clear(holder.itemView.thread_message_sender_photo)
}
}
} }

View File

@@ -4,14 +4,31 @@ import android.content.Context
import androidx.room.Database import androidx.room.Database
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.simplemobiletools.smsmessenger.helpers.Converters
import com.simplemobiletools.smsmessenger.interfaces.AttachmentsDao
import com.simplemobiletools.smsmessenger.interfaces.ConversationsDao import com.simplemobiletools.smsmessenger.interfaces.ConversationsDao
import com.simplemobiletools.smsmessenger.interfaces.MessageAttachmentsDao
import com.simplemobiletools.smsmessenger.interfaces.MessagesDao
import com.simplemobiletools.smsmessenger.models.Attachment
import com.simplemobiletools.smsmessenger.models.Conversation import com.simplemobiletools.smsmessenger.models.Conversation
import com.simplemobiletools.smsmessenger.models.Message
import com.simplemobiletools.smsmessenger.models.MessageAttachment
@Database(entities = [(Conversation::class)], version = 1) @Database(entities = [Conversation::class, Attachment::class, MessageAttachment::class, Message::class], version = 3)
@TypeConverters(Converters::class)
abstract class MessagesDatabase : RoomDatabase() { abstract class MessagesDatabase : RoomDatabase() {
abstract fun ConversationsDao(): ConversationsDao abstract fun ConversationsDao(): ConversationsDao
abstract fun AttachmentsDao(): AttachmentsDao
abstract fun MessageAttachmentsDao(): MessageAttachmentsDao
abstract fun MessagesDao(): MessagesDao
companion object { companion object {
private var db: MessagesDatabase? = null private var db: MessagesDatabase? = null
@@ -20,11 +37,44 @@ abstract class MessagesDatabase : RoomDatabase() {
synchronized(MessagesDatabase::class) { synchronized(MessagesDatabase::class) {
if (db == null) { if (db == null) {
db = Room.databaseBuilder(context.applicationContext, MessagesDatabase::class.java, "conversations.db") db = Room.databaseBuilder(context.applicationContext, MessagesDatabase::class.java, "conversations.db")
.fallbackToDestructiveMigration()
.addMigrations(MIGRATION_1_2)
.addMigrations(MIGRATION_2_3)
.build() .build()
} }
} }
} }
return db!! return db!!
} }
private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.apply {
execSQL("CREATE TABLE IF NOT EXISTS `messages` (`id` INTEGER PRIMARY KEY NOT NULL, `body` TEXT NOT NULL, `type` INTEGER NOT NULL, `participants` TEXT NOT NULL, `date` INTEGER NOT NULL, `read` INTEGER NOT NULL, `thread_id` INTEGER NOT NULL, `is_mms` INTEGER NOT NULL, `attachment` TEXT, `sender_name` TEXT NOT NULL, `sender_photo_uri` TEXT NOT NULL, `subscription_id` INTEGER NOT NULL)")
execSQL("CREATE TABLE IF NOT EXISTS `message_attachments` (`id` INTEGER PRIMARY KEY NOT NULL, `text` TEXT NOT NULL, `attachments` TEXT NOT NULL)")
execSQL("CREATE TABLE IF NOT EXISTS `attachments` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message_id` INTEGER NOT NULL, `uri_string` TEXT NOT NULL, `mimetype` TEXT NOT NULL, `width` INTEGER NOT NULL, `height` INTEGER NOT NULL, `filename` TEXT NOT NULL)")
execSQL("CREATE UNIQUE INDEX `index_attachments_message_id` ON `attachments` (`message_id`)")
}
}
}
private val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.apply {
execSQL("CREATE TABLE conversations_new (`thread_id` INTEGER NOT NULL PRIMARY KEY, `snippet` TEXT NOT NULL, `date` INTEGER NOT NULL, `read` INTEGER NOT NULL, `title` TEXT NOT NULL, `photo_uri` TEXT NOT NULL, `is_group_conversation` INTEGER NOT NULL, `phone_number` TEXT NOT NULL)")
execSQL("INSERT OR IGNORE INTO conversations_new (thread_id, snippet, date, read, title, photo_uri, is_group_conversation, phone_number) " +
"SELECT thread_id, snippet, date, read, title, photo_uri, is_group_conversation, phone_number FROM conversations")
execSQL("DROP TABLE conversations")
execSQL("ALTER TABLE conversations_new RENAME TO conversations")
execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_conversations_id` ON `conversations` (`thread_id`)")
}
}
}
} }
} }

View File

@@ -0,0 +1,22 @@
package com.simplemobiletools.smsmessenger.dialogs
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.smsmessenger.R
import kotlinx.android.synthetic.main.dialog_select_text.view.*
// helper dialog for selecting just a part of a message, not copying the whole into clipboard
class SelectTextDialog(val activity: BaseSimpleActivity, val text: String) {
init {
val view = activity.layoutInflater.inflate(R.layout.dialog_select_text, null).apply {
dialog_select_text_value.text = text
}
AlertDialog.Builder(activity)
.setPositiveButton(R.string.ok) { dialog, which -> { } }
.create().apply {
activity.setupDialogStuff(view, this)
}
}
}

View File

@@ -2,8 +2,5 @@ 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

@@ -8,6 +8,7 @@ import android.app.PendingIntent
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.database.Cursor
import android.graphics.Bitmap import android.graphics.Bitmap
import android.media.AudioAttributes import android.media.AudioAttributes
import android.media.AudioManager import android.media.AudioManager
@@ -27,7 +28,10 @@ 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.databases.MessagesDatabase
import com.simplemobiletools.smsmessenger.helpers.* import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.interfaces.AttachmentsDao
import com.simplemobiletools.smsmessenger.interfaces.ConversationsDao import com.simplemobiletools.smsmessenger.interfaces.ConversationsDao
import com.simplemobiletools.smsmessenger.interfaces.MessageAttachmentsDao
import com.simplemobiletools.smsmessenger.interfaces.MessagesDao
import com.simplemobiletools.smsmessenger.models.* import com.simplemobiletools.smsmessenger.models.*
import com.simplemobiletools.smsmessenger.receivers.DirectReplyReceiver import com.simplemobiletools.smsmessenger.receivers.DirectReplyReceiver
import com.simplemobiletools.smsmessenger.receivers.MarkAsReadReceiver import com.simplemobiletools.smsmessenger.receivers.MarkAsReadReceiver
@@ -41,7 +45,13 @@ fun Context.getMessagessDB() = MessagesDatabase.getInstance(this)
val Context.conversationsDB: ConversationsDao get() = getMessagessDB().ConversationsDao() val Context.conversationsDB: ConversationsDao get() = getMessagessDB().ConversationsDao()
fun Context.getMessages(threadId: Int): ArrayList<Message> { val Context.attachmentsDB: AttachmentsDao get() = getMessagessDB().AttachmentsDao()
val Context.messageAttachmentsDB: MessageAttachmentsDao get() = getMessagessDB().MessageAttachmentsDao()
val Context.messagesDB: MessagesDao get() = getMessagessDB().MessagesDao()
fun Context.getMessages(threadId: Long): ArrayList<Message> {
val uri = Sms.CONTENT_URI val uri = Sms.CONTENT_URI
val projection = arrayOf( val projection = arrayOf(
Sms._ID, Sms._ID,
@@ -76,17 +86,17 @@ fun Context.getMessages(threadId: Int): ArrayList<Message> {
return@queryCursor return@queryCursor
} }
val id = cursor.getIntValue(Sms._ID) val id = cursor.getLongValue(Sms._ID)
val body = cursor.getStringValue(Sms.BODY) val body = cursor.getStringValue(Sms.BODY)
val type = cursor.getIntValue(Sms.TYPE) val type = cursor.getIntValue(Sms.TYPE)
val namePhoto = getNameAndPhotoFromPhoneNumber(senderNumber) val namePhoto = getNameAndPhotoFromPhoneNumber(senderNumber)
val senderName = namePhoto?.name ?: "" val senderName = namePhoto.name
val photoUri = namePhoto?.photoUri ?: "" val photoUri = namePhoto.photoUri ?: ""
val date = (cursor.getLongValue(Sms.DATE) / 1000).toInt() val date = (cursor.getLongValue(Sms.DATE) / 1000).toInt()
val read = cursor.getIntValue(Sms.READ) == 1 val read = cursor.getIntValue(Sms.READ) == 1
val thread = cursor.getIntValue(Sms.THREAD_ID) val thread = cursor.getLongValue(Sms.THREAD_ID)
val subscriptionId = cursor.getIntValue(Sms.SUBSCRIPTION_ID) val subscriptionId = cursor.getIntValue(Sms.SUBSCRIPTION_ID)
val participant = SimpleContact(0, 0, senderName, photoUri, arrayListOf(senderNumber)) val participant = SimpleContact(0, 0, senderName, photoUri, arrayListOf(senderNumber), ArrayList(), ArrayList())
val isMMS = false val isMMS = false
val message = Message(id, body, type, arrayListOf(participant), date, read, thread, isMMS, null, senderName, photoUri, subscriptionId) val message = Message(id, body, type, arrayListOf(participant), date, read, thread, isMMS, null, senderName, photoUri, subscriptionId)
messages.add(message) messages.add(message)
@@ -100,7 +110,7 @@ fun Context.getMessages(threadId: Int): ArrayList<Message> {
} }
// as soon as a message contains multiple recipients it counts 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> { fun Context.getMMS(threadId: Long? = null, sortOrder: String? = null): ArrayList<Message> {
val uri = Mms.CONTENT_URI val uri = Mms.CONTENT_URI
val projection = arrayOf( val projection = arrayOf(
Mms._ID, Mms._ID,
@@ -125,13 +135,13 @@ fun Context.getMMS(threadId: Int? = null, sortOrder: String? = null): ArrayList<
val messages = ArrayList<Message>() val messages = ArrayList<Message>()
val contactsMap = HashMap<Int, SimpleContact>() val contactsMap = HashMap<Int, SimpleContact>()
val threadParticipants = HashMap<Int, ArrayList<SimpleContact>>() val threadParticipants = HashMap<Long, ArrayList<SimpleContact>>()
queryCursor(uri, projection, selection, selectionArgs, sortOrder, showErrors = true) { cursor -> queryCursor(uri, projection, selection, selectionArgs, sortOrder, showErrors = true) { cursor ->
val mmsId = cursor.getIntValue(Mms._ID) val mmsId = cursor.getLongValue(Mms._ID)
val type = cursor.getIntValue(Mms.MESSAGE_BOX) val type = cursor.getIntValue(Mms.MESSAGE_BOX)
val date = cursor.getLongValue(Mms.DATE).toInt() val date = cursor.getLongValue(Mms.DATE).toInt()
val read = cursor.getIntValue(Mms.READ) == 1 val read = cursor.getIntValue(Mms.READ) == 1
val threadId = cursor.getIntValue(Mms.THREAD_ID) val threadId = cursor.getLongValue(Mms.THREAD_ID)
val subscriptionId = cursor.getIntValue(Mms.SUBSCRIPTION_ID) val subscriptionId = cursor.getIntValue(Mms.SUBSCRIPTION_ID)
val participants = if (threadParticipants.containsKey(threadId)) { val participants = if (threadParticipants.containsKey(threadId)) {
threadParticipants[threadId]!! threadParticipants[threadId]!!
@@ -143,17 +153,15 @@ fun Context.getMMS(threadId: Int? = null, sortOrder: String? = null): ArrayList<
val isMMS = true val isMMS = true
val attachment = getMmsAttachment(mmsId) val attachment = getMmsAttachment(mmsId)
val body = attachment?.text ?: "" val body = attachment.text
var senderName = "" var senderName = ""
var senderPhotoUri = "" var senderPhotoUri = ""
if (type != Mms.MESSAGE_BOX_SENT && type != Mms.MESSAGE_BOX_FAILED) { if (type != Mms.MESSAGE_BOX_SENT && type != Mms.MESSAGE_BOX_FAILED) {
val number = getMMSSender(mmsId) val number = getMMSSender(mmsId)
val namePhoto = getNameAndPhotoFromPhoneNumber(number) val namePhoto = getNameAndPhotoFromPhoneNumber(number)
if (namePhoto != null) { senderName = namePhoto.name
senderName = namePhoto.name senderPhotoUri = namePhoto.photoUri ?: ""
senderPhotoUri = namePhoto.photoUri ?: ""
}
} }
val message = Message(mmsId, body, type, participants, date, read, threadId, isMMS, attachment, senderName, senderPhotoUri, subscriptionId) val message = Message(mmsId, body, type, participants, date, read, threadId, isMMS, attachment, senderName, senderPhotoUri, subscriptionId)
@@ -167,7 +175,7 @@ fun Context.getMMS(threadId: Int? = null, sortOrder: String? = null): ArrayList<
return messages return messages
} }
fun Context.getMMSSender(msgId: Int): String { fun Context.getMMSSender(msgId: Long): String {
val uri = Uri.parse("${Mms.CONTENT_URI}/$msgId/addr") val uri = Uri.parse("${Mms.CONTENT_URI}/$msgId/addr")
val projection = arrayOf( val projection = arrayOf(
Mms.Addr.ADDRESS Mms.Addr.ADDRESS
@@ -185,7 +193,7 @@ fun Context.getMMSSender(msgId: Int): String {
return "" return ""
} }
fun Context.getConversations(threadId: Long? = null): ArrayList<Conversation> { fun Context.getConversations(threadId: Long? = null, privateContacts: ArrayList<SimpleContact> = ArrayList()): 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,
@@ -208,7 +216,7 @@ fun Context.getConversations(threadId: Long? = null): ArrayList<Conversation> {
val simpleContactHelper = SimpleContactsHelper(this) val simpleContactHelper = SimpleContactsHelper(this)
val blockedNumbers = getBlockedNumbers() val blockedNumbers = getBlockedNumbers()
queryCursor(uri, projection, selection, selectionArgs, sortOrder, true) { cursor -> queryCursor(uri, projection, selection, selectionArgs, sortOrder, true) { cursor ->
val id = cursor.getIntValue(Threads._ID) val id = cursor.getLongValue(Threads._ID)
var snippet = cursor.getStringValue(Threads.SNIPPET) ?: "" var snippet = cursor.getStringValue(Threads.SNIPPET) ?: ""
if (snippet.isEmpty()) { if (snippet.isEmpty()) {
snippet = getThreadSnippet(id) snippet = getThreadSnippet(id)
@@ -226,12 +234,12 @@ fun Context.getConversations(threadId: Long? = null): ArrayList<Conversation> {
return@queryCursor return@queryCursor
} }
val names = getThreadContactNames(phoneNumbers) val names = getThreadContactNames(phoneNumbers, privateContacts)
val title = TextUtils.join(", ", names.toTypedArray()) val title = TextUtils.join(", ", names.toTypedArray())
val photoUri = if (phoneNumbers.size == 1) simpleContactHelper.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 read = cursor.getIntValue(Threads.READ) == 1 val read = cursor.getIntValue(Threads.READ) == 1
val conversation = Conversation(null, id, snippet, date.toInt(), read, title, photoUri, isGroupConversation, phoneNumbers.first()) val conversation = Conversation(id, snippet, date.toInt(), read, title, photoUri, isGroupConversation, phoneNumbers.first())
conversations.add(conversation) conversations.add(conversation)
} }
@@ -241,7 +249,7 @@ fun Context.getConversations(threadId: Long? = null): ArrayList<Conversation> {
// based on https://stackoverflow.com/a/6446831/1967672 // based on https://stackoverflow.com/a/6446831/1967672
@SuppressLint("NewApi") @SuppressLint("NewApi")
fun Context.getMmsAttachment(id: Int): MessageAttachment? { fun Context.getMmsAttachment(id: Long): MessageAttachment {
val uri = if (isQPlus()) { val uri = if (isQPlus()) {
Mms.Part.CONTENT_URI Mms.Part.CONTENT_URI
} else { } else {
@@ -259,15 +267,15 @@ fun Context.getMmsAttachment(id: Int): MessageAttachment? {
var attachmentName = "" var attachmentName = ""
queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor -> queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val partId = cursor.getStringValue(Mms._ID) val partId = cursor.getLongValue(Mms._ID)
val mimetype = cursor.getStringValue(Mms.Part.CONTENT_TYPE) val mimetype = cursor.getStringValue(Mms.Part.CONTENT_TYPE)
if (mimetype == "text/plain") { if (mimetype == "text/plain") {
messageAttachment.text = cursor.getStringValue(Mms.Part.TEXT) ?: "" messageAttachment.text = cursor.getStringValue(Mms.Part.TEXT) ?: ""
} else if (mimetype.startsWith("image/") || mimetype.startsWith("video/")) { } else if (mimetype.startsWith("image/") || mimetype.startsWith("video/")) {
val attachment = Attachment(Uri.withAppendedPath(uri, partId), mimetype, 0, 0, "") val attachment = Attachment(partId, id, Uri.withAppendedPath(uri, partId.toString()).toString(), mimetype, 0, 0, "")
messageAttachment.attachments.add(attachment) messageAttachment.attachments.add(attachment)
} else if (mimetype != "application/smil") { } else if (mimetype != "application/smil") {
val attachment = Attachment(Uri.withAppendedPath(uri, partId), mimetype, 0, 0, attachmentName) val attachment = Attachment(partId, id, Uri.withAppendedPath(uri, partId.toString()).toString(), mimetype, 0, 0, attachmentName)
messageAttachment.attachments.add(attachment) messageAttachment.attachments.add(attachment)
} else { } else {
val text = cursor.getStringValue(Mms.Part.TEXT) val text = cursor.getStringValue(Mms.Part.TEXT)
@@ -286,7 +294,7 @@ fun Context.getLatestMMS(): Message? {
return getMMS(sortOrder = sortOrder).firstOrNull() return getMMS(sortOrder = sortOrder).firstOrNull()
} }
fun Context.getThreadSnippet(threadId: Int): String { fun Context.getThreadSnippet(threadId: Long): String {
val sortOrder = "${Mms.DATE} DESC LIMIT 1" val sortOrder = "${Mms.DATE} DESC LIMIT 1"
val latestMms = getMMS(threadId, sortOrder).firstOrNull() val latestMms = getMMS(threadId, sortOrder).firstOrNull()
var snippet = latestMms?.body ?: "" var snippet = latestMms?.body ?: ""
@@ -313,7 +321,29 @@ fun Context.getThreadSnippet(threadId: Int): String {
return snippet return snippet
} }
fun Context.getThreadParticipants(threadId: Int, contactsMap: HashMap<Int, SimpleContact>?): ArrayList<SimpleContact> { fun Context.getMessageRecipientAddress(messageId: Long): String {
val uri = Sms.CONTENT_URI
val projection = arrayOf(
Sms.ADDRESS
)
val selection = "${Sms._ID} = ?"
val selectionArgs = arrayOf(messageId.toString())
try {
val cursor = contentResolver.query(uri, projection, selection, selectionArgs, null)
cursor?.use {
if (cursor.moveToFirst()) {
return cursor.getStringValue(Sms.ADDRESS)
}
}
} catch (e: Exception) {
}
return ""
}
fun Context.getThreadParticipants(threadId: Long, contactsMap: HashMap<Int, SimpleContact>?): ArrayList<SimpleContact> {
val uri = Uri.parse("${MmsSms.CONTENT_CONVERSATIONS_URI}?simple=true") val uri = Uri.parse("${MmsSms.CONTENT_CONVERSATIONS_URI}?simple=true")
val projection = arrayOf( val projection = arrayOf(
ThreadsColumns.RECIPIENT_IDS ThreadsColumns.RECIPIENT_IDS
@@ -335,9 +365,9 @@ fun Context.getThreadParticipants(threadId: Int, contactsMap: HashMap<Int, Simpl
val phoneNumber = getPhoneNumberFromAddressId(addressId) val phoneNumber = getPhoneNumberFromAddressId(addressId)
val namePhoto = getNameAndPhotoFromPhoneNumber(phoneNumber) val namePhoto = getNameAndPhotoFromPhoneNumber(phoneNumber)
val name = namePhoto?.name ?: "" val name = namePhoto.name
val photoUri = namePhoto?.photoUri ?: "" val photoUri = namePhoto.photoUri ?: ""
val contact = SimpleContact(addressId, addressId, name, photoUri, arrayListOf(phoneNumber)) val contact = SimpleContact(addressId, addressId, name, photoUri, arrayListOf(phoneNumber), ArrayList(), ArrayList())
participants.add(contact) participants.add(contact)
} }
} }
@@ -356,10 +386,20 @@ fun Context.getThreadPhoneNumbers(recipientIds: List<Int>): ArrayList<String> {
return numbers return numbers
} }
fun Context.getThreadContactNames(phoneNumbers: List<String>): ArrayList<String> { fun Context.getThreadContactNames(phoneNumbers: List<String>, privateContacts: ArrayList<SimpleContact>): ArrayList<String> {
val names = ArrayList<String>() val names = ArrayList<String>()
phoneNumbers.forEach { phoneNumbers.forEach { number ->
names.add(SimpleContactsHelper(this).getNameFromPhoneNumber(it)) val name = SimpleContactsHelper(this).getNameFromPhoneNumber(number)
if (name != number) {
names.add(name)
} else {
val privateContact = privateContacts.firstOrNull { it.doesContainPhoneNumber(number) }
if (privateContact == null) {
names.add(name)
} else {
names.add(privateContact.name)
}
}
} }
return names return names
} }
@@ -400,9 +440,9 @@ fun Context.getSuggestedContacts(privateContacts: ArrayList<SimpleContact>): Arr
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)
var senderName = namePhoto?.name ?: "" var senderName = namePhoto.name
var photoUri = namePhoto?.photoUri ?: "" var photoUri = namePhoto.photoUri ?: ""
if (namePhoto == null || isNumberBlocked(senderNumber, blockedNumbers)) { if (isNumberBlocked(senderNumber, blockedNumbers)) {
return@queryCursor return@queryCursor
} else if (namePhoto.name == senderNumber) { } else if (namePhoto.name == senderNumber) {
if (privateContacts.isNotEmpty()) { if (privateContacts.isNotEmpty()) {
@@ -418,7 +458,7 @@ fun Context.getSuggestedContacts(privateContacts: ArrayList<SimpleContact>): Arr
} }
} }
val contact = SimpleContact(0, 0, senderName, photoUri, arrayListOf(senderNumber)) val contact = SimpleContact(0, 0, senderName, photoUri, arrayListOf(senderNumber), ArrayList(), ArrayList())
if (!contacts.map { it.phoneNumbers.first().trimToComparableNumber() }.contains(senderNumber.trimToComparableNumber())) { if (!contacts.map { it.phoneNumbers.first().trimToComparableNumber() }.contains(senderNumber.trimToComparableNumber())) {
contacts.add(contact) contacts.add(contact)
} }
@@ -427,7 +467,7 @@ fun Context.getSuggestedContacts(privateContacts: ArrayList<SimpleContact>): Arr
return contacts return contacts
} }
fun Context.getNameAndPhotoFromPhoneNumber(number: String): NamePhoto? { fun Context.getNameAndPhotoFromPhoneNumber(number: String): NamePhoto {
if (!hasPermission(PERMISSION_READ_CONTACTS)) { if (!hasPermission(PERMISSION_READ_CONTACTS)) {
return NamePhoto(number, null) return NamePhoto(number, null)
} }
@@ -454,7 +494,7 @@ fun Context.getNameAndPhotoFromPhoneNumber(number: String): NamePhoto? {
return NamePhoto(number, null) return NamePhoto(number, null)
} }
fun Context.insertNewSMS(address: String, subject: String, body: String, date: Long, read: Int, threadId: Long, type: Int, subscriptionId: Int): Int { fun Context.insertNewSMS(address: String, subject: String, body: String, date: Long, read: Int, threadId: Long, type: Int, subscriptionId: Int): Long {
val uri = Sms.CONTENT_URI val uri = Sms.CONTENT_URI
val contentValues = ContentValues().apply { val contentValues = ContentValues().apply {
put(Sms.ADDRESS, address) put(Sms.ADDRESS, address)
@@ -468,29 +508,39 @@ fun Context.insertNewSMS(address: String, subject: String, body: String, date: L
} }
val newUri = contentResolver.insert(uri, contentValues) val newUri = contentResolver.insert(uri, contentValues)
return newUri?.lastPathSegment?.toInt() ?: 0 return newUri?.lastPathSegment?.toLong() ?: 0L
} }
fun Context.deleteConversation(threadId: Int) { fun Context.deleteConversation(threadId: Long) {
var uri = Sms.CONTENT_URI var uri = Sms.CONTENT_URI
val selection = "${Sms.THREAD_ID} = ?" val selection = "${Sms.THREAD_ID} = ?"
val selectionArgs = arrayOf(threadId.toString()) val selectionArgs = arrayOf(threadId.toString())
contentResolver.delete(uri, selection, selectionArgs) try {
contentResolver.delete(uri, selection, selectionArgs)
} catch (e: Exception) {
showErrorToast(e)
}
uri = Mms.CONTENT_URI uri = Mms.CONTENT_URI
contentResolver.delete(uri, selection, selectionArgs) contentResolver.delete(uri, selection, selectionArgs)
conversationsDB.deleteThreadId(threadId.toLong()) conversationsDB.deleteThreadId(threadId)
messagesDB.deleteThreadMessages(threadId)
} }
fun Context.deleteMessage(id: Int, isMMS: Boolean) { fun Context.deleteMessage(id: Long, isMMS: Boolean) {
val uri = if (isMMS) Mms.CONTENT_URI else Sms.CONTENT_URI val uri = if (isMMS) Mms.CONTENT_URI else Sms.CONTENT_URI
val selection = "${Sms._ID} = ?" val selection = "${Sms._ID} = ?"
val selectionArgs = arrayOf(id.toString()) val selectionArgs = arrayOf(id.toString())
contentResolver.delete(uri, selection, selectionArgs) try {
contentResolver.delete(uri, selection, selectionArgs)
messagesDB.delete(id)
} catch (e: Exception) {
showErrorToast(e)
}
} }
fun Context.markMessageRead(id: Int, isMMS: Boolean) { fun Context.markMessageRead(id: Long, isMMS: Boolean) {
val uri = if (isMMS) Mms.CONTENT_URI else Sms.CONTENT_URI val uri = if (isMMS) Mms.CONTENT_URI else Sms.CONTENT_URI
val contentValues = ContentValues().apply { val contentValues = ContentValues().apply {
put(Sms.READ, 1) put(Sms.READ, 1)
@@ -499,9 +549,10 @@ fun Context.markMessageRead(id: Int, isMMS: Boolean) {
val selection = "${Sms._ID} = ?" val selection = "${Sms._ID} = ?"
val selectionArgs = arrayOf(id.toString()) val selectionArgs = arrayOf(id.toString())
contentResolver.update(uri, contentValues, selection, selectionArgs) contentResolver.update(uri, contentValues, selection, selectionArgs)
messagesDB.markRead(id)
} }
fun Context.markThreadMessagesRead(threadId: Int) { fun Context.markThreadMessagesRead(threadId: Long) {
arrayOf(Sms.CONTENT_URI, Mms.CONTENT_URI).forEach { uri -> arrayOf(Sms.CONTENT_URI, Mms.CONTENT_URI).forEach { uri ->
val contentValues = ContentValues().apply { val contentValues = ContentValues().apply {
put(Sms.READ, 1) put(Sms.READ, 1)
@@ -511,9 +562,10 @@ fun Context.markThreadMessagesRead(threadId: Int) {
val selectionArgs = arrayOf(threadId.toString()) val selectionArgs = arrayOf(threadId.toString())
contentResolver.update(uri, contentValues, selection, selectionArgs) contentResolver.update(uri, contentValues, selection, selectionArgs)
} }
messagesDB.markThreadRead(threadId)
} }
fun Context.markThreadMessagesUnread(threadId: Int) { fun Context.markThreadMessagesUnread(threadId: Long) {
arrayOf(Sms.CONTENT_URI, Mms.CONTENT_URI).forEach { uri -> arrayOf(Sms.CONTENT_URI, Mms.CONTENT_URI).forEach { uri ->
val contentValues = ContentValues().apply { val contentValues = ContentValues().apply {
put(Sms.READ, 0) put(Sms.READ, 0)
@@ -525,6 +577,26 @@ fun Context.markThreadMessagesUnread(threadId: Int) {
} }
} }
fun Context.updateMessageType(id: Long, status: Int) {
val uri = Sms.CONTENT_URI
val contentValues = ContentValues().apply {
put(Sms.TYPE, status)
}
val selection = "${Sms._ID} = ?"
val selectionArgs = arrayOf(id.toString())
contentResolver.update(uri, contentValues, selection, selectionArgs)
}
fun Context.updateMessageSubscriptionId(messageId: Long, subscriptionId: Int) {
val uri = Sms.CONTENT_URI
val contentValues = ContentValues().apply {
put(Sms.SUBSCRIPTION_ID, subscriptionId)
}
val selection = "${Sms._ID} = ?"
val selectionArgs = arrayOf(messageId.toString())
contentResolver.update(uri, contentValues, selection, selectionArgs)
}
fun Context.updateUnreadCountBadge(conversations: List<Conversation>) { fun Context.updateUnreadCountBadge(conversations: List<Conversation>) {
val unreadCount = conversations.count { !it.read } val unreadCount = conversations.count { !it.read }
if (unreadCount == 0) { if (unreadCount == 0) {
@@ -560,23 +632,28 @@ fun Context.getThreadId(addresses: Set<String>): Long {
} }
} }
fun Context.showReceivedMessageNotification(address: String, body: String, threadID: Int, bitmap: Bitmap?) { fun Context.showReceivedMessageNotification(address: String, body: String, threadId: Long, bitmap: Bitmap?) {
val privateCursor = getMyContactsCursor().loadInBackground() val privateCursor = getMyContactsCursor()?.loadInBackground()
ensureBackgroundThread { ensureBackgroundThread {
var sender = getNameAndPhotoFromPhoneNumber(address)?.name ?: "" val senderName = getNameFromAddress(address, privateCursor)
if (address == sender) {
val privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor)
sender = privateContacts.firstOrNull { it.doesContainPhoneNumber(address) }?.name ?: address
}
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
showMessageNotification(address, body, threadID, bitmap, sender) showMessageNotification(address, body, threadId, bitmap, senderName)
} }
} }
} }
fun Context.getNameFromAddress(address: String, privateCursor: Cursor?): String {
var sender = getNameAndPhotoFromPhoneNumber(address).name
if (address == sender) {
val privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor)
sender = privateContacts.firstOrNull { it.doesContainPhoneNumber(address) }?.name ?: address
}
return sender
}
@SuppressLint("NewApi") @SuppressLint("NewApi")
fun Context.showMessageNotification(address: String, body: String, threadID: Int, bitmap: Bitmap?, sender: String) { fun Context.showMessageNotification(address: String, body: String, threadId: Long, bitmap: Bitmap?, sender: String) {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) val soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
if (isOreoPlus()) { if (isOreoPlus()) {
@@ -598,14 +675,14 @@ fun Context.showMessageNotification(address: String, body: String, threadID: Int
} }
val intent = Intent(this, ThreadActivity::class.java).apply { val intent = Intent(this, ThreadActivity::class.java).apply {
putExtra(THREAD_ID, threadID) putExtra(THREAD_ID, threadId)
} }
val pendingIntent = PendingIntent.getActivity(this, threadID, intent, PendingIntent.FLAG_UPDATE_CURRENT) val pendingIntent = PendingIntent.getActivity(this, threadId.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT)
val summaryText = getString(R.string.new_message) val summaryText = getString(R.string.new_message)
val markAsReadIntent = Intent(this, MarkAsReadReceiver::class.java).apply { val markAsReadIntent = Intent(this, MarkAsReadReceiver::class.java).apply {
action = MARK_AS_READ action = MARK_AS_READ
putExtra(THREAD_ID, threadID) 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)
@@ -618,11 +695,11 @@ fun Context.showMessageNotification(address: String, body: String, threadID: Int
.build() .build()
val replyIntent = Intent(this, DirectReplyReceiver::class.java).apply { val replyIntent = Intent(this, DirectReplyReceiver::class.java).apply {
putExtra(THREAD_ID, threadID) putExtra(THREAD_ID, threadId)
putExtra(THREAD_NUMBER, address) putExtra(THREAD_NUMBER, address)
} }
val replyPendingIntent = PendingIntent.getBroadcast(applicationContext, threadID, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT) val replyPendingIntent = PendingIntent.getBroadcast(applicationContext, threadId.hashCode(), replyIntent, PendingIntent.FLAG_UPDATE_CURRENT)
replyAction = NotificationCompat.Action.Builder(R.drawable.ic_send_vector, replyLabel, replyPendingIntent) replyAction = NotificationCompat.Action.Builder(R.drawable.ic_send_vector, replyLabel, replyPendingIntent)
.addRemoteInput(remoteInput) .addRemoteInput(remoteInput)
.build() .build()
@@ -632,7 +709,7 @@ fun Context.showMessageNotification(address: String, body: String, threadID: Int
val builder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL) val builder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL)
.setContentTitle(sender) .setContentTitle(sender)
.setContentText(body) .setContentText(body)
.setColor(config.primaryColor) .setColor(getAdjustedPrimaryColor())
.setSmallIcon(R.drawable.ic_messenger) .setSmallIcon(R.drawable.ic_messenger)
.setLargeIcon(largeIcon) .setLargeIcon(largeIcon)
.setStyle(NotificationCompat.BigTextStyle().setSummaryText(summaryText).bigText(body)) .setStyle(NotificationCompat.BigTextStyle().setSummaryText(summaryText).bigText(body))
@@ -648,5 +725,5 @@ fun Context.showMessageNotification(address: String, body: String, threadID: Int
builder.addAction(R.drawable.ic_check_vector, getString(R.string.mark_as_read), markAsReadPendingIntent) builder.addAction(R.drawable.ic_check_vector, getString(R.string.mark_as_read), markAsReadPendingIntent)
.setChannelId(NOTIFICATION_CHANNEL) .setChannelId(NOTIFICATION_CHANNEL)
notificationManager.notify(threadID, builder.build()) notificationManager.notify(threadId.hashCode(), builder.build())
} }

View File

@@ -13,4 +13,8 @@ class Config(context: Context) : BaseConfig(context) {
} }
fun getUseSIMIdAtNumber(number: String) = prefs.getInt(USE_SIM_ID_PREFIX + number, 0) fun getUseSIMIdAtNumber(number: String) = prefs.getInt(USE_SIM_ID_PREFIX + number, 0)
var showCharacterCounter: Boolean
get() = prefs.getBoolean(SHOW_CHARACTER_COUNTER, false)
set(showCharacterCounter) = prefs.edit().putBoolean(SHOW_CHARACTER_COUNTER, showCharacterCounter).apply()
} }

View File

@@ -9,8 +9,10 @@ const val THREAD_TEXT = "thread_text"
const val THREAD_NUMBER = "thread_number" const val THREAD_NUMBER = "thread_number"
const val THREAD_ATTACHMENT_URI = "thread_attachment_uri" const val THREAD_ATTACHMENT_URI = "thread_attachment_uri"
const val THREAD_ATTACHMENT_URIS = "thread_attachment_uris" const val THREAD_ATTACHMENT_URIS = "thread_attachment_uris"
const val SEARCHED_MESSAGE_ID = "searched_message_id"
const val USE_SIM_ID_PREFIX = "use_sim_id_" const val USE_SIM_ID_PREFIX = "use_sim_id_"
const val NOTIFICATION_CHANNEL = "simple_sms_messenger" const val NOTIFICATION_CHANNEL = "simple_sms_messenger"
const val SHOW_CHARACTER_COUNTER = "show_character_counter"
private const val PATH = "com.simplemobiletools.smsmessenger.action." private const val PATH = "com.simplemobiletools.smsmessenger.action."
const val MARK_AS_READ = PATH + "mark_as_read" const val MARK_AS_READ = PATH + "mark_as_read"
@@ -22,6 +24,7 @@ const val THREAD_RECEIVED_MESSAGE = 2
const val THREAD_SENT_MESSAGE = 3 const val THREAD_SENT_MESSAGE = 3
const val THREAD_SENT_MESSAGE_ERROR = 4 const val THREAD_SENT_MESSAGE_ERROR = 4
const val THREAD_SENT_MESSAGE_SUCCESS = 5 const val THREAD_SENT_MESSAGE_SUCCESS = 5
const val THREAD_SENT_MESSAGE_SENDING = 6
fun refreshMessages() { fun refreshMessages() {
EventBus.getDefault().post(Events.RefreshMessages()) EventBus.getDefault().post(Events.RefreshMessages())

View File

@@ -0,0 +1,33 @@
package com.simplemobiletools.smsmessenger.helpers
import androidx.room.TypeConverter
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.smsmessenger.models.Attachment
import com.simplemobiletools.smsmessenger.models.MessageAttachment
class Converters {
private val gson = Gson()
private val attachmentType = object : TypeToken<List<Attachment>>() {}.type
private val simpleContactType = object : TypeToken<List<SimpleContact>>() {}.type
private val messageAttachmentType = object : TypeToken<MessageAttachment?>() {}.type
@TypeConverter
fun jsonToAttachmentList(value: String) = gson.fromJson<ArrayList<Attachment>>(value, attachmentType)
@TypeConverter
fun attachmentListToJson(list: ArrayList<Attachment>) = gson.toJson(list)
@TypeConverter
fun jsonToSimpleContactList(value: String) = gson.fromJson<ArrayList<SimpleContact>>(value, simpleContactType)
@TypeConverter
fun simpleContactListToJson(list: ArrayList<SimpleContact>) = gson.toJson(list)
@TypeConverter
fun jsonToMessageAttachment(value: String) = gson.fromJson<MessageAttachment>(value, messageAttachmentType)
@TypeConverter
fun messageAttachmentToJson(messageAttachment: MessageAttachment?) = gson.toJson(messageAttachment)
}

View File

@@ -0,0 +1,11 @@
package com.simplemobiletools.smsmessenger.interfaces
import androidx.room.Dao
import androidx.room.Query
import com.simplemobiletools.smsmessenger.models.Attachment
@Dao
interface AttachmentsDao {
@Query("SELECT * FROM attachments")
fun getAll(): List<Attachment>
}

View File

@@ -17,15 +17,15 @@ interface ConversationsDao {
@Query("SELECT * FROM conversations WHERE read = 0") @Query("SELECT * FROM conversations WHERE read = 0")
fun getUnreadConversations(): List<Conversation> fun getUnreadConversations(): List<Conversation>
@Query("SELECT * FROM conversations WHERE title LIKE :text")
fun getConversationsWithText(text: String): List<Conversation>
@Query("UPDATE conversations SET read = 1 WHERE thread_id = :threadId") @Query("UPDATE conversations SET read = 1 WHERE thread_id = :threadId")
fun markRead(threadId: Long) fun markRead(threadId: Long)
@Query("UPDATE conversations SET read = 0 WHERE thread_id = :threadId") @Query("UPDATE conversations SET read = 0 WHERE thread_id = :threadId")
fun markUnread(threadId: Long) fun markUnread(threadId: Long)
@Query("DELETE FROM conversations WHERE id = :id")
fun delete(id: Long)
@Query("DELETE FROM conversations WHERE thread_id = :threadId") @Query("DELETE FROM conversations WHERE thread_id = :threadId")
fun deleteThreadId(threadId: Long) fun deleteThreadId(threadId: Long)
} }

View File

@@ -0,0 +1,11 @@
package com.simplemobiletools.smsmessenger.interfaces
import androidx.room.Dao
import androidx.room.Query
import com.simplemobiletools.smsmessenger.models.MessageAttachment
@Dao
interface MessageAttachmentsDao {
@Query("SELECT * FROM message_attachments")
fun getAll(): List<MessageAttachment>
}

View File

@@ -0,0 +1,43 @@
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.Message
@Dao
interface MessagesDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrUpdate(message: Message)
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insertOrIgnore(message: Message): Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertMessages(vararg message: Message)
@Query("SELECT * FROM messages")
fun getAll(): List<Message>
@Query("SELECT * FROM messages WHERE thread_id = :threadId")
fun getThreadMessages(threadId: Long): List<Message>
@Query("SELECT * FROM messages WHERE body LIKE :text")
fun getMessagesWithText(text: String): List<Message>
@Query("UPDATE messages SET read = 1 WHERE id = :id")
fun markRead(id: Long)
@Query("UPDATE messages SET read = 1 WHERE thread_id = :threadId")
fun markThreadRead(threadId: Long)
@Query("UPDATE messages SET type = :type WHERE id = :id")
fun updateType(id: Long, type: Int): Int
@Query("DELETE FROM messages WHERE id = :id")
fun delete(id: Long)
@Query("DELETE FROM messages WHERE thread_id = :threadId")
fun deleteThreadMessages(threadId: Long)
}

View File

@@ -1,5 +1,20 @@
package com.simplemobiletools.smsmessenger.models package com.simplemobiletools.smsmessenger.models
import android.net.Uri import android.net.Uri
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
data class Attachment(var uri: Uri, var mimetype: String, var width: Int, var height: Int, var filename: String) @Entity(tableName = "attachments", indices = [(Index(value = ["message_id"], unique = true))])
data class Attachment(
@PrimaryKey(autoGenerate = true) var id: Long?,
@ColumnInfo(name = "message_id") var messageId: Long,
@ColumnInfo(name = "uri_string") var uriString: String,
@ColumnInfo(name = "mimetype") var mimetype: String,
@ColumnInfo(name = "width") var width: Int,
@ColumnInfo(name = "height") var height: Int,
@ColumnInfo(name = "filename") var filename: String) {
fun getUri() = Uri.parse(uriString)
}

View File

@@ -7,8 +7,7 @@ import androidx.room.PrimaryKey
@Entity(tableName = "conversations", indices = [(Index(value = ["thread_id"], unique = true))]) @Entity(tableName = "conversations", indices = [(Index(value = ["thread_id"], unique = true))])
data class Conversation( data class Conversation(
@PrimaryKey(autoGenerate = true) var id: Long?, @PrimaryKey @ColumnInfo(name = "thread_id") var threadId: Long,
@ColumnInfo(name = "thread_id") var thread_id: Int,
@ColumnInfo(name = "snippet") var snippet: String, @ColumnInfo(name = "snippet") var snippet: String,
@ColumnInfo(name = "date") var date: Int, @ColumnInfo(name = "date") var date: Int,
@ColumnInfo(name = "read") var read: Boolean, @ColumnInfo(name = "read") var read: Boolean,
@@ -16,8 +15,4 @@ data class Conversation(
@ColumnInfo(name = "photo_uri") var photoUri: String, @ColumnInfo(name = "photo_uri") var photoUri: String,
@ColumnInfo(name = "is_group_conversation") var isGroupConversation: Boolean, @ColumnInfo(name = "is_group_conversation") var isGroupConversation: Boolean,
@ColumnInfo(name = "phone_number") var phoneNumber: String @ColumnInfo(name = "phone_number") var phoneNumber: String
) { )
fun getStringToCompare(): String {
return copy(id = 0).toString()
}
}

View File

@@ -1,10 +1,25 @@
package com.simplemobiletools.smsmessenger.models package com.simplemobiletools.smsmessenger.models
import android.provider.Telephony import android.provider.Telephony
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.simplemobiletools.commons.models.SimpleContact import com.simplemobiletools.commons.models.SimpleContact
@Entity(tableName = "messages")
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, @PrimaryKey val id: Long,
val isMMS: Boolean, val attachment: MessageAttachment?, var senderName: String, val senderPhotoUri: String, val subscriptionId: Int) : ThreadItem() { @ColumnInfo(name = "body") val body: String,
@ColumnInfo(name = "type") val type: Int,
@ColumnInfo(name = "participants") val participants: ArrayList<SimpleContact>,
@ColumnInfo(name = "date") val date: Int,
@ColumnInfo(name = "read") val read: Boolean,
@ColumnInfo(name = "thread_id") val threadId: Long,
@ColumnInfo(name = "is_mms") val isMMS: Boolean,
@ColumnInfo(name = "attachment") val attachment: MessageAttachment?,
@ColumnInfo(name = "sender_name") var senderName: String,
@ColumnInfo(name = "sender_photo_uri") val senderPhotoUri: String,
@ColumnInfo(name = "subscription_id") var subscriptionId: Int) : ThreadItem() {
fun isReceivedMessage() = type == Telephony.Sms.MESSAGE_TYPE_INBOX fun isReceivedMessage() = type == Telephony.Sms.MESSAGE_TYPE_INBOX
} }

View File

@@ -1,3 +1,11 @@
package com.simplemobiletools.smsmessenger.models package com.simplemobiletools.smsmessenger.models
data class MessageAttachment(val id: Int, var text: String, var attachments: ArrayList<Attachment>) import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "message_attachments")
data class MessageAttachment(
@PrimaryKey val id: Long,
@ColumnInfo(name = "text") var text: String,
@ColumnInfo(name = "attachments") var attachments: ArrayList<Attachment>)

View File

@@ -0,0 +1,3 @@
package com.simplemobiletools.smsmessenger.models
data class SearchResult(val messageId: Long, val title: String, val snippet: String, val date: String, val threadId: Long, var photoUri: String)

View File

@@ -1,3 +1,3 @@
package com.simplemobiletools.smsmessenger.models package com.simplemobiletools.smsmessenger.models
data class ThreadError(val messageID: Int) : ThreadItem() data class ThreadError(val messageId: Long, val messageText: String) : ThreadItem()

View File

@@ -0,0 +1,3 @@
package com.simplemobiletools.smsmessenger.models
data class ThreadSending(val messageId: Long) : ThreadItem()

View File

@@ -1,4 +1,4 @@
package com.simplemobiletools.smsmessenger.models package com.simplemobiletools.smsmessenger.models
// show a check after the latest message, if it is a sent one and succeeded // show a check after the latest message, if it is a sent one and succeeded
data class ThreadSuccess(val messageID: Int) : ThreadItem() data class ThreadSuccess(val messageID: Long) : ThreadItem()

View File

@@ -21,7 +21,7 @@ import com.simplemobiletools.smsmessenger.helpers.THREAD_NUMBER
class DirectReplyReceiver : BroadcastReceiver() { class DirectReplyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
val address = intent.getStringExtra(THREAD_NUMBER) val address = intent.getStringExtra(THREAD_NUMBER)
val threadId = intent.getIntExtra(THREAD_ID, 0) val threadId = intent.getLongExtra(THREAD_ID, 0L)
val msg = RemoteInput.getResultsFromIntent(intent).getCharSequence(REPLY).toString() val msg = RemoteInput.getResultsFromIntent(intent).getCharSequence(REPLY).toString()
val settings = Settings() val settings = Settings()
@@ -31,7 +31,7 @@ class DirectReplyReceiver : BroadcastReceiver() {
val message = com.klinker.android.send_message.Message(msg, address) val message = com.klinker.android.send_message.Message(msg, address)
try { try {
transaction.sendNewMessage(message, threadId.toLong()) transaction.sendNewMessage(message, threadId)
} catch (e: Exception) { } catch (e: Exception) {
context.showErrorToast(e) context.showErrorToast(e)
} }
@@ -41,11 +41,11 @@ class DirectReplyReceiver : BroadcastReceiver() {
.setContentText(msg) .setContentText(msg)
.build() .build()
context.notificationManager.notify(threadId, repliedNotification) context.notificationManager.notify(threadId.hashCode(), repliedNotification)
ensureBackgroundThread { ensureBackgroundThread {
context.markThreadMessagesRead(threadId) context.markThreadMessagesRead(threadId)
context.conversationsDB.markRead(threadId.toLong()) context.conversationsDB.markRead(threadId)
} }
} }
} }

View File

@@ -15,11 +15,11 @@ 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 threadId = intent.getIntExtra(THREAD_ID, 0) val threadId = intent.getLongExtra(THREAD_ID, 0L)
context.notificationManager.cancel(threadId) context.notificationManager.cancel(threadId.hashCode())
ensureBackgroundThread { ensureBackgroundThread {
context.markThreadMessagesRead(threadId) context.markThreadMessagesRead(threadId)
context.conversationsDB.markRead(threadId.toLong()) context.conversationsDB.markRead(threadId)
context.updateUnreadCountBadge(context.conversationsDB.getUnreadConversations()) context.updateUnreadCountBadge(context.conversationsDB.getUnreadConversations())
} }
} }

View File

@@ -24,7 +24,7 @@ class MmsReceiver : com.klinker.android.send_message.MmsReceivedReceiver() {
val glideBitmap = try { val glideBitmap = try {
Glide.with(context) Glide.with(context)
.asBitmap() .asBitmap()
.load(mms.attachment!!.attachments.first().uri) .load(mms.attachment!!.attachments.first().getUri())
.centerCrop() .centerCrop()
.into(size, size) .into(size, size)
.get() .get()
@@ -33,8 +33,8 @@ class MmsReceiver : com.klinker.android.send_message.MmsReceivedReceiver() {
} }
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
context.showReceivedMessageNotification(address, mms.body, mms.thread, glideBitmap) context.showReceivedMessageNotification(address, mms.body, mms.threadId, glideBitmap)
val conversation = context.getConversations(mms.thread.toLong()).firstOrNull() ?: return@post val conversation = context.getConversations(mms.threadId).firstOrNull() ?: return@post
ensureBackgroundThread { ensureBackgroundThread {
context.conversationsDB.insertOrUpdate(conversation) context.conversationsDB.insertOrUpdate(conversation)
context.updateUnreadCountBadge(context.conversationsDB.getUnreadConversations()) context.updateUnreadCountBadge(context.conversationsDB.getUnreadConversations())

View File

@@ -3,11 +3,15 @@ package com.simplemobiletools.smsmessenger.receivers
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.provider.Telephony import android.provider.Telephony
import com.simplemobiletools.commons.extensions.isNumberBlocked import com.simplemobiletools.commons.extensions.isNumberBlocked
import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.smsmessenger.extensions.* import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.refreshMessages import com.simplemobiletools.smsmessenger.helpers.refreshMessages
import com.simplemobiletools.smsmessenger.models.Message
class SmsReceiver : BroadcastReceiver() { class SmsReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
@@ -21,23 +25,33 @@ class SmsReceiver : BroadcastReceiver() {
val read = 0 val read = 0
val subscriptionId = intent.getIntExtra("subscription", -1) val subscriptionId = intent.getIntExtra("subscription", -1)
messages.forEach { ensureBackgroundThread {
address = it.originatingAddress ?: "" messages.forEach {
subject = it.pseudoSubject address = it.originatingAddress ?: ""
body += it.messageBody subject = it.pseudoSubject
date = Math.min(it.timestampMillis, System.currentTimeMillis()) body += it.messageBody
threadId = context.getThreadId(address) date = Math.min(it.timestampMillis, System.currentTimeMillis())
} threadId = context.getThreadId(address)
}
if (!context.isNumberBlocked(address)) { Handler(Looper.getMainLooper()).post {
context.insertNewSMS(address, subject, body, date, read, threadId, type, subscriptionId) if (!context.isNumberBlocked(address)) {
context.showReceivedMessageNotification(address, body, threadId.toInt(), null) ensureBackgroundThread {
refreshMessages() val newMessageId = context.insertNewSMS(address, subject, body, date, read, threadId, type, subscriptionId)
ensureBackgroundThread { val conversation = context.getConversations(threadId).firstOrNull() ?: return@ensureBackgroundThread
val conversation = context.getConversations(threadId).firstOrNull() ?: return@ensureBackgroundThread context.conversationsDB.insertOrUpdate(conversation)
context.conversationsDB.insertOrUpdate(conversation) context.updateUnreadCountBadge(context.conversationsDB.getUnreadConversations())
context.updateUnreadCountBadge(context.conversationsDB.getUnreadConversations())
val participant = SimpleContact(0, 0, address, "", arrayListOf(address), ArrayList(), ArrayList())
val message = Message(newMessageId, body, type, arrayListOf(participant), (date / 1000).toInt(), false, threadId,
false, null, address, "", subscriptionId)
context.messagesDB.insertOrUpdate(message)
}
context.showReceivedMessageNotification(address, body, threadId, null)
refreshMessages()
}
} }
} }
} }

View File

@@ -1,12 +0,0 @@
package com.simplemobiletools.smsmessenger.receivers
import android.content.Context
import android.content.Intent
import com.klinker.android.send_message.SentReceiver
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
class SmsSentReceiver : SentReceiver() {
override fun onMessageStatusUpdated(context: Context, intent: Intent, receiverResultCode: Int) {
refreshMessages()
}
}

View File

@@ -0,0 +1,37 @@
package com.simplemobiletools.smsmessenger.receivers
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.provider.Telephony
import com.klinker.android.send_message.DeliveredReceiver
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.smsmessenger.extensions.messagesDB
import com.simplemobiletools.smsmessenger.extensions.updateMessageType
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
class SmsStatusDeliveredReceiver : DeliveredReceiver() {
override fun onMessageStatusUpdated(context: Context, intent: Intent, receiverResultCode: Int) {
if (intent.extras?.containsKey("message_uri") == true) {
val uri = Uri.parse(intent.getStringExtra("message_uri"))
val messageId = uri?.lastPathSegment?.toLong() ?: 0L
ensureBackgroundThread {
val type = Telephony.Sms.MESSAGE_TYPE_SENT
context.updateMessageType(messageId, type)
val updated = context.messagesDB.updateType(messageId, type)
if (updated == 0) {
Handler(Looper.getMainLooper()).postDelayed({
ensureBackgroundThread {
context.messagesDB.updateType(messageId, type)
}
}, 2000)
}
refreshMessages()
}
}
}
}

View File

@@ -0,0 +1,110 @@
package com.simplemobiletools.smsmessenger.receivers
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.RingtoneManager
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.provider.Telephony
import androidx.core.app.NotificationCompat
import com.klinker.android.send_message.SentReceiver
import com.simplemobiletools.commons.extensions.getAdjustedPrimaryColor
import com.simplemobiletools.commons.extensions.getMyContactsCursor
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.helpers.isOreoPlus
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.ThreadActivity
import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.NOTIFICATION_CHANNEL
import com.simplemobiletools.smsmessenger.helpers.THREAD_ID
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
class SmsStatusSentReceiver : SentReceiver() {
override fun onMessageStatusUpdated(context: Context, intent: Intent, receiverResultCode: Int) {
if (intent.extras?.containsKey("message_uri") == true) {
val uri = Uri.parse(intent.getStringExtra("message_uri"))
val messageId = uri?.lastPathSegment?.toLong() ?: 0L
ensureBackgroundThread {
val type = if (intent.extras!!.containsKey("errorCode")) {
showSendingFailedNotification(context, messageId)
Telephony.Sms.MESSAGE_TYPE_FAILED
} else {
Telephony.Sms.MESSAGE_TYPE_OUTBOX
}
context.updateMessageType(messageId, type)
context.messagesDB.updateType(messageId, type)
refreshMessages()
}
}
}
private fun showSendingFailedNotification(context: Context, messageId: Long) {
Handler(Looper.getMainLooper()).post {
val privateCursor = context.getMyContactsCursor()?.loadInBackground()
ensureBackgroundThread {
val address = context.getMessageRecipientAddress(messageId)
val threadId = context.getThreadId(address)
val senderName = context.getNameFromAddress(address, privateCursor)
showNotification(context, senderName, threadId)
}
}
}
@SuppressLint("NewApi")
private fun showNotification(context: Context, recipientName: String, threadId: Long) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
if (isOreoPlus()) {
val audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setLegacyStreamType(AudioManager.STREAM_NOTIFICATION)
.build()
val name = context.getString(R.string.message_not_sent_short)
val importance = NotificationManager.IMPORTANCE_HIGH
NotificationChannel(NOTIFICATION_CHANNEL, name, importance).apply {
setBypassDnd(false)
enableLights(true)
setSound(soundUri, audioAttributes)
enableVibration(true)
notificationManager.createNotificationChannel(this)
}
}
val intent = Intent(context, ThreadActivity::class.java).apply {
putExtra(THREAD_ID, threadId)
}
val pendingIntent = PendingIntent.getActivity(context, threadId.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT)
val summaryText = String.format(context.getString(R.string.message_sending_error), recipientName)
val largeIcon = SimpleContactsHelper(context).getContactLetterIcon(recipientName)
val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL)
.setContentTitle(context.getString(R.string.message_not_sent_short))
.setContentText(summaryText)
.setColor(context.getAdjustedPrimaryColor())
.setSmallIcon(R.drawable.ic_messenger)
.setLargeIcon(largeIcon)
.setStyle(NotificationCompat.BigTextStyle().bigText(summaryText))
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setDefaults(Notification.DEFAULT_LIGHTS)
.setCategory(Notification.CATEGORY_MESSAGE)
.setAutoCancel(true)
.setSound(soundUri, AudioManager.STREAM_NOTIFICATION)
.setChannelId(NOTIFICATION_CHANNEL)
notificationManager.notify(threadId.hashCode(), builder.build())
}
}

View File

@@ -1,9 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
android:shape="rectangle"> <item android:id="@+id/selected_contact_bg">
<shape android:shape="rectangle">
<corners android:radius="@dimen/normal_margin" /> <corners android:radius="@dimen/normal_margin" />
<solid android:color="@color/activated_item_foreground" /> <solid android:color="@color/md_grey_white" />
</shape> </shape>
</item>
</layer-list>

View File

@@ -42,7 +42,7 @@
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_below="@+id/suggestions_scrollview"
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"

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/search_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/search_placeholder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/activity_margin"
android:alpha="0.8"
android:gravity="center"
android:paddingStart="@dimen/activity_margin"
android:paddingEnd="@dimen/activity_margin"
android:text="@string/no_items_found"
android:textSize="@dimen/bigger_text_size"
android:textStyle="italic" />
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/search_placeholder_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/search_placeholder"
android:layout_centerHorizontal="true"
android:alpha="0.8"
android:gravity="center"
android:paddingStart="@dimen/activity_margin"
android:paddingTop="@dimen/medium_margin"
android:paddingEnd="@dimen/activity_margin"
android:paddingBottom="@dimen/medium_margin"
android:text="@string/type_2_characters"
android:textSize="@dimen/bigger_text_size"
android:textStyle="italic" />
<com.simplemobiletools.commons.views.MyRecyclerView
android:id="@+id/search_results_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:scrollbars="vertical"
app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager" />
</RelativeLayout>

View File

@@ -53,6 +53,27 @@
</RelativeLayout> </RelativeLayout>
<RelativeLayout
android:id="@+id/settings_customize_notifications_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/medium_margin"
android:background="?attr/selectableItemBackground"
android:paddingLeft="@dimen/normal_margin"
android:paddingTop="@dimen/activity_margin"
android:paddingRight="@dimen/normal_margin"
android:paddingBottom="@dimen/activity_margin">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/settings_customize_notifications_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:paddingStart="@dimen/medium_margin"
android:text="@string/customize_notifications" />
</RelativeLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/settings_manage_blocked_numbers_holder" android:id="@+id/settings_manage_blocked_numbers_holder"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -149,5 +170,28 @@
android:clickable="false" /> android:clickable="false" />
</RelativeLayout> </RelativeLayout>
<RelativeLayout
android:id="@+id/settings_show_character_counter_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/medium_margin"
android:background="?attr/selectableItemBackground"
android:paddingStart="@dimen/normal_margin"
android:paddingTop="@dimen/activity_margin"
android:paddingEnd="@dimen/normal_margin"
android:paddingBottom="@dimen/activity_margin">
<com.simplemobiletools.commons.views.MySwitchCompat
android:id="@+id/settings_show_character_counter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:clickable="false"
android:paddingStart="@dimen/medium_margin"
android:text="@string/show_character_counter"
app:switchPadding="@dimen/medium_margin" />
</RelativeLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@@ -66,6 +66,7 @@
android:layout_marginStart="@dimen/medium_margin" android:layout_marginStart="@dimen/medium_margin"
android:layout_marginEnd="@dimen/medium_margin" android:layout_marginEnd="@dimen/medium_margin"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/confirm_selection"
android:paddingStart="@dimen/medium_margin" android:paddingStart="@dimen/medium_margin"
android:paddingEnd="@dimen/medium_margin" android:paddingEnd="@dimen/medium_margin"
android:src="@drawable/ic_check_vector" android:src="@drawable/ic_check_vector"
@@ -125,6 +126,7 @@
android:layout_marginEnd="@dimen/small_margin" android:layout_marginEnd="@dimen/small_margin"
android:alpha="0.9" android:alpha="0.9"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/attachment"
android:padding="@dimen/normal_margin" android:padding="@dimen/normal_margin"
android:src="@drawable/ic_plus_vector" /> android:src="@drawable/ic_plus_vector" />
@@ -166,14 +168,17 @@
<ImageView <ImageView
android:id="@+id/thread_select_sim_icon" android:id="@+id/thread_select_sim_icon"
android:layout_width="@dimen/normal_icon_size" android:layout_width="wrap_content"
android:layout_height="@dimen/normal_icon_size" android:layout_height="wrap_content"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:layout_marginTop="@dimen/small_margin" android:layout_marginTop="@dimen/small_margin"
android:layout_toStartOf="@+id/thread_send_message" android:layout_toStartOf="@+id/thread_character_counter"
android:alpha="0.9" android:alpha="0.9"
android:background="?selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/normal_margin" android:paddingStart="@dimen/medium_margin"
android:paddingTop="@dimen/normal_margin"
android:paddingEnd="@dimen/medium_margin"
android:paddingBottom="@dimen/normal_margin"
android:src="@drawable/ic_sim_vector" android:src="@drawable/ic_sim_vector"
android:visibility="gone" /> android:visibility="gone" />
@@ -190,6 +195,21 @@
android:visibility="gone" android:visibility="gone"
tools:text="1" /> tools:text="1" />
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/thread_character_counter"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignTop="@+id/thread_send_message"
android:layout_alignBottom="@+id/thread_send_message"
android:layout_toStartOf="@+id/thread_send_message"
android:gravity="center"
android:paddingStart="@dimen/small_margin"
android:paddingEnd="@dimen/small_margin"
android:text="0"
android:textSize="@dimen/normal_text_size"
android:visibility="gone"
tools:ignore="HardcodedText" />
<ImageView <ImageView
android:id="@+id/thread_send_message" android:id="@+id/thread_send_message"
android:layout_width="@dimen/normal_icon_size" android:layout_width="@dimen/normal_icon_size"
@@ -201,6 +221,7 @@
android:alpha="0.4" android:alpha="0.4"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
android:clickable="false" android:clickable="false"
android:contentDescription="@string/ok"
android:padding="@dimen/medium_margin" android:padding="@dimen/medium_margin"
android:src="@drawable/ic_send_vector" /> android:src="@drawable/ic_send_vector" />

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<com.simplemobiletools.commons.views.MyTextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/dialog_select_text_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/big_margin"
android:paddingTop="@dimen/big_margin"
android:paddingEnd="@dimen/big_margin"
android:textIsSelectable="true"
android:textSize="@dimen/big_text_size"
tools:text="My sample text" />

View File

@@ -18,7 +18,7 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.7"> app:layout_constraintWidth_percent="0.8">
<ImageView <ImageView
android:id="@+id/thread_message_sender_photo" android:id="@+id/thread_message_sender_photo"

View File

@@ -0,0 +1,56 @@
<?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/search_result_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/small_margin"
android:layout_marginBottom="@dimen/small_margin"
android:background="?attr/selectableItemBackground"
android:padding="@dimen/normal_margin">
<ImageView
android:id="@+id/search_result_image"
android:layout_width="@dimen/normal_icon_size"
android:layout_height="@dimen/normal_icon_size"
android:layout_marginEnd="@dimen/normal_margin" />
<TextView
android:id="@+id/search_result_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toStartOf="@+id/search_result_date"
android:layout_toEndOf="@+id/search_result_image"
android:ellipsize="end"
android:maxLines="1"
android:paddingEnd="@dimen/activity_margin"
android:textSize="@dimen/big_text_size"
tools:text="John" />
<TextView
android:id="@+id/search_result_snippet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/search_result_title"
android:layout_toEndOf="@+id/search_result_image"
android:alpha="0.7"
android:ellipsize="end"
android:maxLines="1"
android:paddingEnd="@dimen/activity_margin"
android:textSize="@dimen/big_text_size"
tools:text="Hey buddy!" />
<TextView
android:id="@+id/search_result_date"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignTop="@+id/search_result_title"
android:layout_alignBottom="@+id/search_result_title"
android:layout_alignParentEnd="true"
android:layout_marginTop="@dimen/tiny_margin"
android:alpha="0.7"
android:gravity="center_vertical"
android:textSize="@dimen/smaller_text_size"
tools:text="08/02/2021" />
</RelativeLayout>

View File

@@ -18,7 +18,7 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.7"> app:layout_constraintWidth_percent="0.8">
<LinearLayout <LinearLayout
android:id="@+id/thread_mesage_attachments_holder" android:id="@+id/thread_mesage_attachments_holder"

View File

@@ -3,8 +3,8 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/suggested_contact_holder" android:id="@+id/suggested_contact_holder"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:background="?selectableItemBackground" android:layout_height="wrap_content"
android:layout_height="wrap_content"> android:background="?selectableItemBackground">
<ImageView <ImageView
android:id="@+id/suggested_contact_image" android:id="@+id/suggested_contact_image"

View File

@@ -6,6 +6,6 @@
android:gravity="end" android:gravity="end"
android:paddingStart="@dimen/activity_margin" android:paddingStart="@dimen/activity_margin"
android:paddingEnd="@dimen/activity_margin" android:paddingEnd="@dimen/activity_margin"
android:text="@string/message_not_sent" android:text="@string/message_not_sent_touch_retry"
android:textColor="@color/theme_dark_red_primary_color" android:textColor="@color/theme_dark_red_primary_color"
android:textSize="@dimen/normal_text_size" /> android:textSize="@dimen/normal_text_size" />

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/thread_sending"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:paddingStart="@dimen/activity_margin"
android:paddingEnd="@dimen/activity_margin"
android:text="@string/sending"
android:textSize="@dimen/normal_text_size" />

View File

@@ -1,6 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/cab_delete"
android:icon="@drawable/ic_delete_vector"
android:title="@string/delete"
app:showAsAction="ifRoom" />
<item <item
android:id="@+id/cab_add_number_to_contact" android:id="@+id/cab_add_number_to_contact"
android:icon="@drawable/ic_add_person_vector" android:icon="@drawable/ic_add_person_vector"
@@ -12,13 +17,16 @@
android:title="@string/block_number" android:title="@string/block_number"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/cab_select_all" android:id="@+id/cab_dial_number"
android:icon="@drawable/ic_select_all_vector" android:icon="@drawable/ic_phone_vector"
android:title="@string/select_all" android:title="@string/dial_number"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/cab_delete" android:id="@+id/cab_copy_number"
android:icon="@drawable/ic_delete_vector" android:title="@string/copy_number_to_clipboard"
android:title="@string/delete" app:showAsAction="never" />
app:showAsAction="ifRoom" /> <item
android:id="@+id/cab_select_all"
android:title="@string/select_all"
app:showAsAction="never" />
</menu> </menu>

View File

@@ -12,13 +12,16 @@
android:title="@string/share" android:title="@string/share"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/cab_select_all" android:id="@+id/cab_select_text"
android:icon="@drawable/ic_select_all_vector" android:title="@string/select_text"
android:title="@string/select_all" app:showAsAction="never" />
app:showAsAction="ifRoom" />
<item <item
android:id="@+id/cab_delete" android:id="@+id/cab_delete"
android:icon="@drawable/ic_delete_vector" android:icon="@drawable/ic_delete_vector"
android:title="@string/delete" android:title="@string/delete"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item
android:id="@+id/cab_select_all"
android:title="@string/select_all"
app:showAsAction="never" />
</menu> </menu>

View File

@@ -1,6 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/search"
android:icon="@drawable/ic_search_vector"
android:title="@string/search"
app:showAsAction="always" />
<item <item
android:id="@+id/settings" android:id="@+id/settings"
android:title="@string/settings" android:title="@string/settings"

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/search"
android:icon="@drawable/ic_search_vector"
android:title="@string/search"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="collapseActionView|always" />
</menu>

View File

@@ -0,0 +1,82 @@
<resources>
<string name="app_name">Enkel SMS Beskeder</string>
<string name="app_launcher_name">Beskeder</string>
<string name="type_a_message">Skriv en besked…</string>
<string name="message_not_sent_short">Beskeden blev ikke sendt</string>
<string name="message_not_sent_touch_retry">Blev ikke sendt, tryk for at gensende</string>
<string name="message_sending_error">Din besked til \'%s\' blev ikke sendt</string>
<string name="add_person">Tilføj Person</string>
<string name="attachment">Vedhæftning</string>
<string name="no_conversations_found">Ingen gemte samtaler er fundet</string>
<string name="start_conversation">Start en samtale</string>
<string name="reply">Svar</string>
<string name="show_character_counter">Vis en karaktertæller ved skrivning af beskeder</string>
<string name="loading_messages">Henter beskeder…</string>
<string name="no_reply_support">Afsender understøtter ikke svar</string>
<string name="draft">Udkast</string>
<string name="sending">Sender…</string>
<string name="export_messages">Eksporter beskeder</string>
<string name="import_messages">Importer beskeder</string>
<!-- New conversation -->
<string name="new_conversation">Ny Samtale</string>
<string name="add_contact_or_number">Tilføj kontakt eller nummer…</string>
<string name="suggestions">Forslag</string>
<!-- Notifications -->
<string name="channel_received_sms">Modtag SMS</string>
<string name="new_message">Ny Besked</string>
<string name="mark_as_read">Marker som læst</string>
<string name="mark_as_unread">Marker som ulæst</string>
<!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Er du sikker på, at du vil slette alle beskeder i denne samtale?</string>
<!-- Are you sure you want to delete 5 conversations? -->
<plurals name="delete_conversations">
<item quantity="one">%d samtale</item>
<item quantity="other">%d samtaler</item>
</plurals>
<!-- Are you sure you want to delete 5 messages? -->
<plurals name="delete_messages">
<item quantity="one">%d besked</item>
<item quantity="other">%d beskeder</item>
</plurals>
<!-- FAQ -->
<string name="faq_1_title">Hvorfor kræver appen adgang til internettet?</string>
<string name="faq_1_text">Desværre er det nødvendigt for at sende MMS-vedhæftede filer. Ikke at kunne være i stand til at sende MMS ville være en virkelig stor ulempe i forhold til andre apps, så vi besluttede at gå denne vej.
Men som normalt er der ingen annoncer, sporing eller analyse overhovedet, internettet bruges kun til at sende 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 beskeder - Administrer dine smser nemt</string>
<!-- Short description has to have max 80 characters -->
<string name="app_short_description">En nem og hurtig måde at administrere SMS og MMS-beskeder uden annoncer på.</string>
<string name="app_long_description">
En fantastisk måde at holde kontakten med dine pårørende på, ved at sende både SMS- og MMS-beskeder. Appen håndterer også gruppemeddelelser korrekt, ligesom at blokere numre fra Android 7+.
Appen tilbyder mange datoformater at vælge imellem, så du kan føle dig godt tilpas ved at bruge den. Du kan også skifte mellem 12 og 24 timers tidsformat.
Det har en virkelig lille appstørrelse sammenlignet med konkurrencen, hvilket gør det virkelig hurtigt at downloade.
Den leveres med materialedesign og mørkt tema som standard og giver god brugeroplevelse til nem brug. Manglen på internetadgang giver dig mere privatliv, sikkerhed og stabilitet end andre apps.
Indeholder ingen annoncer eller unødvendige tilladelser. Det er fuldstændig opensource, giver farver, der kan tilpasses.
<b>Se hele pakken med enkle værktøjer her:</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

@@ -1,13 +1,22 @@
<resources> <resources>
<string name="app_name">Schlichter SMS Messenger</string> <string name="app_name">Schlichter SMS Messenger</string>
<string name="app_launcher_name">SMS Messenger</string> <string name="app_launcher_name">SMS Messenger</string>
<string name="type_a_message">schreibe eine Nachricht…</string> <string name="type_a_message">Schreibe eine Nachricht…</string>
<string name="message_not_sent">Nachricht wurde nicht gesendet.</string> <string name="message_not_sent_short">Nachricht nicht versendet.</string>
<string name="message_not_sent_touch_retry">Nachricht nicht versendet. Berühre, um es erneut zu versuchen.</string>
<string name="message_sending_error">Deine Nachricht am \'%s\' wurde nicht gesendet.</string>
<string name="add_person">Person hinzufügen</string> <string name="add_person">Person hinzufügen</string>
<string name="attachment">Anhang</string> <string name="attachment">Anhang</string>
<string name="no_conversations_found">Keine gespeicherten Chats gefunden</string> <string name="no_conversations_found">Keine gespeicherten Chats gefunden</string>
<string name="start_conversation">Neuen Chat beginnen</string> <string name="start_conversation">Neuen Chat beginnen</string>
<string name="reply">Antworten</string> <string name="reply">Antworten</string>
<string name="show_character_counter">Zeige einen Zeichenzähler während dem Schreiben von Nachrichten.</string>
<string name="loading_messages">Lade Nachrichten…</string>
<string name="no_reply_support">Der Absender unterstützt keine Antworten.</string>
<string name="draft">Entwurf</string>
<string name="sending">Sende…</string>
<string name="export_messages">Export messages</string>
<string name="import_messages">Import messages</string>
<!-- New conversation --> <!-- New conversation -->
<string name="new_conversation">Neuer Chat</string> <string name="new_conversation">Neuer Chat</string>
@@ -42,21 +51,21 @@
<!-- Strings displayed only on Google Playstore. Optional, but good to have --> <!-- 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 --> <!-- 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-Nachrichten simple verwalten</string>
<!-- Short description has to have max 80 characters --> <!-- 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">Eine einfache und schnelle Art SMS &amp; MMS ohne Werbung zu verwalten.</string>
<string name="app_long_description"> <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+. In tolle Möglichkeit mit deinen Verwandten in Kontakt zu bleiben, indem sowohl SMS als auch MMS Nachrichten gesendet werden. Die App handhabt auch Gruppennachrichten gut, genauso wie das Blockieren von Nummern ab 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. Damit die Nutzung sich vertrauter und komfortabler anfühlt, kannst du das Datumsformat in verschiedenen Arten anpassen. Zudem kannst du zwischen 12 und 24 Stunden Format auswählen.
Im Vergleich zur Konkurrenz ist die Appgröße sehr klein, wodurch es sehr schnell heruntergeladen ist.
It has a really tiny app size compared to the competition, making it really fast to download. Es kommt standardmäßig mit einem material design und Dunkelmodus, was für ein großartiges Nutzererlebnis und leichte Nutzbarkeit sorgt. Da es keine Internetverbindung verwendet, bietet diese App mehr Datenschutz, Sicherheit und Stabilität als andere Apps.
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. Beinhaltet keine Werbung oder unnötige Berechtigungen. Es ist komplett opensource, und bietet anpassbare Farben.
Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors. <b>Entdecke alle Simple Tools hier:</b>
<b>Check out the full suite of Simple Tools here:</b>
https://www.simplemobiletools.com https://www.simplemobiletools.com
<b>Facebook:</b> <b>Facebook:</b>

View File

@@ -2,12 +2,21 @@
<string name="app_name">Απλός SMS Messenger</string> <string name="app_name">Απλός SMS Messenger</string>
<string name="app_launcher_name">SMS Messenger</string> <string name="app_launcher_name">SMS Messenger</string>
<string name="type_a_message">Πληκτρολογήστε ένα μήνυμα…</string> <string name="type_a_message">Πληκτρολογήστε ένα μήνυμα…</string>
<string name="message_not_sent">Το μήνυμα δεν έχει σταλεί.</string> <string name="message_not_sent_short">Το μήνυμα δεν εστάλη</string>
<string name="message_not_sent_touch_retry">Δεν εστάλη. Αγγίξτε για επανάληψη.</string>
<string name="message_sending_error">Το μήνυμά σας προς το \'%s\' δεν έχει αποσταλεί</string>
<string name="add_person">Προσθήκη ατόμου</string> <string name="add_person">Προσθήκη ατόμου</string>
<string name="attachment">Συνημμένο</string> <string name="attachment">Συνημμένο</string>
<string name="no_conversations_found">Δεν βρέθηκαν αποθηκευμένες συνομιλίες</string> <string name="no_conversations_found">Δεν βρέθηκαν αποθηκευμένες συνομιλίες</string>
<string name="start_conversation">Έναρξη συνομιλίας</string> <string name="start_conversation">Έναρξη συνομιλίας</string>
<string name="reply">Απάντηση</string> <string name="reply">Απάντηση</string>
<string name="show_character_counter">Εμφάνιση μετρητή χαρακτήρων κατά την πληκτρολόγηση μηνυμάτων</string>
<string name="loading_messages">Φόρτωση μηνυμάτων…</string>
<string name="no_reply_support">Ο αποστολέας δεν υποστηρίζει απαντήσεις</string>
<string name="draft">Πρόχειρο</string>
<string name="sending">Γίνεται αποστολή…</string>
<string name="export_messages">Εξαγωγή μηνυμάτων</string>
<string name="import_messages">Εισαγωγή μηνυμάτων</string>
<!-- New conversation --> <!-- New conversation -->
<string name="new_conversation">Νέα συνομιλία</string> <string name="new_conversation">Νέα συνομιλία</string>

View File

@@ -2,12 +2,21 @@
<string name="app_name">Mensajería SMS Simple</string> <string name="app_name">Mensajería SMS Simple</string>
<string name="app_launcher_name">Mensajería SMS</string> <string name="app_launcher_name">Mensajería SMS</string>
<string name="type_a_message">Escribe un mensaje…</string> <string name="type_a_message">Escribe un mensaje…</string>
<string name="message_not_sent">El mensaje no se ha enviado.</string> <string name="message_not_sent_short">Message not sent</string>
<string name="message_not_sent_touch_retry">Not sent. Touch to retry.</string>
<string name="message_sending_error">Your message to \'%s\' has not been sent</string>
<string name="add_person">Añadir persona</string> <string name="add_person">Añadir persona</string>
<string name="attachment">Archivo adjunto</string> <string name="attachment">Archivo adjunto</string>
<string name="no_conversations_found">No se han encontrado conversaciones</string> <string name="no_conversations_found">No se han encontrado conversaciones</string>
<string name="start_conversation">Inicia una conversación</string> <string name="start_conversation">Inicia una conversación</string>
<string name="reply">Responder</string> <string name="reply">Responder</string>
<string name="show_character_counter">Mostrar un contador de caracteres al escribir mensajes</string>
<string name="loading_messages">Loading messages…</string>
<string name="no_reply_support">Sender doesn\'t support replies</string>
<string name="draft">Draft</string>
<string name="sending">Sending…</string>
<string name="export_messages">Export messages</string>
<string name="import_messages">Import messages</string>
<!-- New conversation --> <!-- New conversation -->
<string name="new_conversation">Nueva conversación</string> <string name="new_conversation">Nueva conversación</string>

View File

@@ -0,0 +1,82 @@
<resources>
<string name="app_name">Simple SMS Messenger</string>
<string name="app_launcher_name">Viestit</string>
<string name="type_a_message">Lähetä viesti…</string>
<string name="message_not_sent_short">Viestiä ei lähetetty</string>
<string name="message_not_sent_touch_retry">Ei lähetetty. Yritä uudelleen koskemalla.</string>
<string name="message_sending_error">Viestiäsi henkilölle \'%s\' ei lähetetty.</string>
<string name="add_person">Lisää henkilö</string>
<string name="attachment">Liite</string>
<string name="no_conversations_found">Tallennettuja keskusteluja ei löytyny</string>
<string name="start_conversation">Aloita keskustelu</string>
<string name="reply">Vastaa</string>
<string name="show_character_counter">Näytä merkkimäärä kirjoittaessasi viestejä</string>
<string name="loading_messages">Ladataan viestejä…</string>
<string name="no_reply_support">Lähettäjä ei tue vastauksia</string>
<string name="draft">Luonnos</string>
<string name="sending">Lähetetään…</string>
<string name="export_messages">Export messages</string>
<string name="import_messages">Import messages</string>
<!-- New conversation -->
<string name="new_conversation">Uusi keskustelu</string>
<string name="add_contact_or_number">Lisää yhteystieto tai numero…</string>
<string name="suggestions">Ehdotuksia</string>
<!-- Notifications -->
<string name="channel_received_sms">Vastaanotettu tekstiviesti</string>
<string name="new_message">Uusi viesti</string>
<string name="mark_as_read">Merkitse luetuksi</string>
<string name="mark_as_unread">Merkitse lukemattomaksi</string>
<!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Haluatko varmasti poistaa kaikki tämän keskustelun viestit?</string>
<!-- Are you sure you want to delete 5 conversations? -->
<plurals name="delete_conversations">
<item quantity="one">yhden keskustelun</item>
<item quantity="other">%d keskustelua</item>
</plurals>
<!-- Are you sure you want to delete 5 messages? -->
<plurals name="delete_messages">
<item quantity="one">yhden viestin</item>
<item quantity="other">%d viestiä</item>
</plurals>
<!-- FAQ -->
<string name="faq_1_title">Miksi sovellus vaatii Internet-yhteyden?</string>
<string name="faq_1_text">Valitettavasti sitä tarvitaan multimediaviestin-liitteiden lähettämiseen. Multimediaviestien lähettämättä jättäminen olisi todella valtava haitta muihin sovelluksiin verrattuna, joten päätimme mennä tällä tavalla.
Kuten yleensä, mainoksia, seurantaa tai analytiikkaa ei kuitenkaan ole, iternetiä käytetään vain multimediaviestien lähettämiseen.</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,82 @@
<resources>
<string name="app_name">Simple SMS Messenger</string>
<string name="app_launcher_name">SMS Messenger</string>
<string name="type_a_message">Écrivez un message…</string>
<string name="message_not_sent_short">Message not sent</string>
<string name="message_not_sent_touch_retry">Not sent. Touch to retry.</string>
<string name="message_sending_error">Your message to \'%s\' has not been sent</string>
<string name="add_person">Ajouter une personne</string>
<string name="attachment">Pièce jointe</string>
<string name="no_conversations_found">Aucune conversation enregistrée n\'a été trouvée</string>
<string name="start_conversation">Commencer une conversation</string>
<string name="reply">Répondre</string>
<string name="show_character_counter">Show a character counter at writing messages</string>
<string name="loading_messages">Loading messages…</string>
<string name="no_reply_support">Sender doesn\'t support replies</string>
<string name="draft">Draft</string>
<string name="sending">Sending…</string>
<string name="export_messages">Export messages</string>
<string name="import_messages">Import messages</string>
<!-- New conversation -->
<string name="new_conversation">Nouvelle conversation</string>
<string name="add_contact_or_number">Ajouter un contact ou un numéro…</string>
<string name="suggestions">Suggestions</string>
<!-- Notifications -->
<string name="channel_received_sms">SMS reçu</string>
<string name="new_message">Nouveau message</string>
<string name="mark_as_read">Marquer comme lu</string>
<string name="mark_as_unread">Marquer comme non lu</string>
<!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Êtes-vous sûr de vouloir supprimer tous les messages de cette conversation ?</string>
<!-- Are you sure you want to delete 5 conversations? -->
<plurals name="delete_conversations">
<item quantity="one">%d conversation</item>
<item quantity="other">%d conversations</item>
</plurals>
<!-- Are you sure you want to delete 5 messages? -->
<plurals name="delete_messages">
<item quantity="one">%d message</item>
<item quantity="other">%d messages</item>
</plurals>
<!-- FAQ -->
<string name="faq_1_title">Pourquoi cette application a besoin d\'un accès à internet ?</string>
<string name="faq_1_text">Malheureusement, cela est nécessaire pour envoyer des pièces jointes dans les MMS. Ne pas pouvoir envoyer de MMS serait un énorme désavantage comparé à d\'autres applications, nous avons donc décidé de faire ainsi.
Cependant, comme toujours, il n\'y a aucune publicité, traqueur ou analyseur de quelque sorte, internet n\'est utilisé que pour envoyer des 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 - Gérez vos messages aisément</string>
<!-- Short description has to have max 80 characters -->
<string name="app_short_description">Une façon simple et rapide de gérer ses SMS et MMS sans publicités.</string>
<string name="app_long_description">
Une excellente façon de rester en contact avec vos proches, en envoyant à la fois des SMS et des MMS. L\'application gère parfaitement les messages de groupe, tout comme le blocage des numéros sur Android 7+.
Elle offre un choix large de format de date, pour être confortable à utiliser. Vous pouvez choisir entre un format 12 et 24 heures.
La taille de l\'application est très légère comparée à la concurrence, ce qui la rend rapide à télécharger.
Avec un Material Design et un thème sombre par défaut, elle offre une excellente expérience utilisateur pour une utilisation facile. L\'absence d\'accès à internet vous donne plus de confidentialité, de sécurité et de stabilité que les autres applications.
Ne contient aucune publicité ou autorisation inutile. Elle est entièrement open source, avec des couleurs personnalisables.
<b>Découvrez la suite complète des applications Simple Mobile Tools ici :</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,82 @@
<resources>
<string name="app_name">Simple SMS Messenger</string>
<string name="app_launcher_name">SMS Messenger</string>
<string name="type_a_message">Ketik pesan…</string>
<string name="message_not_sent_short">Message not sent</string>
<string name="message_not_sent_touch_retry">Not sent. Touch to retry.</string>
<string name="message_sending_error">Your message to \'%s\' has not been sent</string>
<string name="add_person">Tambahkan Orang</string>
<string name="attachment">Lampiran</string>
<string name="no_conversations_found">Tidak ada percakapan tersimpan yang ditemukan</string>
<string name="start_conversation">Mulailah percakapan</string>
<string name="reply">Balas</string>
<string name="show_character_counter">Tunjukkan penghitung karakter saat menulis pesan</string>
<string name="loading_messages">Loading messages…</string>
<string name="no_reply_support">Sender doesn\'t support replies</string>
<string name="draft">Draft</string>
<string name="sending">Sending…</string>
<string name="export_messages">Export messages</string>
<string name="import_messages">Import messages</string>
<!-- New conversation -->
<string name="new_conversation">Percakapan baru</string>
<string name="add_contact_or_number">Tambahkan Kontak atau Nomor…</string>
<string name="suggestions">Saran</string>
<!-- Notifications -->
<string name="channel_received_sms">Menerima SMS</string>
<string name="new_message">Pesan baru</string>
<string name="mark_as_read">Tandai sebagai Dibaca</string>
<string name="mark_as_unread">Tandai sebagai Belum dibaca</string>
<!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Apakah Anda yakin ingin menghapus semua pesan dari percakapan ini?</string>
<!-- Are you sure you want to delete 5 conversations? -->
<plurals name="delete_conversations">
<item quantity="one">%d conversation</item>
<item quantity="other">%d conversations</item>
</plurals>
<!-- Are you sure you want to delete 5 messages? -->
<plurals name="delete_messages">
<item quantity="one">%d pesan</item>
<item quantity="other">%d pesan</item>
</plurals>
<!-- FAQ -->
<string name="faq_1_title">Mengapa aplikasi membutuhkan akses ke internet?</string>
<string name="faq_1_text">Sayangnya itu diperlukan untuk mengirim lampiran MMS. Tidak dapat mengirim MMS akan menjadi kerugian yang sangat besar dibandingkan dengan aplikasi lain, jadi kami memutuskan untuk menggunakan cara ini.
Namun, seperti biasanya, tidak ada iklan, pelacakan atau analitik apa pun, internet hanya digunakan untuk mengirim 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,81 @@
<resources>
<string name="app_name">SMS Messenger</string>
<string name="app_launcher_name">SMS Messenger</string>
<string name="type_a_message">Componi messaggio</string>
<string name="message_not_sent_short">Messaggio non inviato</string>
<string name="message_not_sent_touch_retry">Non inviato. Tocca per riprovare</string>
<string name="message_sending_error">Il tuo messaggio a \'%s\' non è stato inviato</string>
<string name="add_person">Aggiungi persona</string>
<string name="attachment">Allegato</string>
<string name="no_conversations_found">Nessuna conversazione trovata</string>
<string name="start_conversation">Inizia conversazione</string>
<string name="reply">Rispondi</string>
<string name="show_character_counter">Mostra contatore caratteri</string>
<string name="loading_messages">Caricamento messaggi…</string>
<string name="no_reply_support">Il mittente non accetta risposte</string>
<string name="draft">Bozza</string>
<string name="sending">Invio…</string>
<string name="export_messages">Export messages</string>
<string name="import_messages">Import messages</string>
<!-- New conversation -->
<string name="new_conversation">Nuova conversazione</string>
<string name="add_contact_or_number">Inserisci contatto o numero…</string>
<string name="suggestions">Suggerimenti</string>
<!-- Notifications -->
<string name="channel_received_sms">SMS ricevuto</string>
<string name="new_message">Nuovo messaggio</string>
<string name="mark_as_read">Letto</string>
<string name="mark_as_unread">Non letto</string>
<!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Vuoi cancellare l\'intera conversazione?</string>
<!-- Are you sure you want to delete 5 conversations? -->
<plurals name="delete_conversations">
<item quantity="one">%d conversazione</item>
<item quantity="other">%d conversazioni</item>
</plurals>
<!-- Are you sure you want to delete 5 messages? -->
<plurals name="delete_messages">
<item quantity="one">%d messaggio</item>
<item quantity="other">%d messaggi</item>
</plurals>
<!-- FAQ -->
<string name="faq_1_title">Perché l\'applicazione richiede l\'accesso ad internet?</string>
<string name="faq_1_text">Purtroppo è necessario per poter inviare gli allegati degli MMS. Non essere in grado di inviare gli MMS sarebbe un grosso svantaggio in confronto ad altre applicazioni, quindi abbiamo deciso di intraprendere questa strada.
Ad ogni modo, come sempre, non ci sono pubblicità o tracciamenti, internet è utilizzato soltanto per l\'invio degli 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

@@ -0,0 +1,80 @@
<resources>
<string name="app_name">Simple ショートメール</string>
<string name="app_launcher_name">ショートメール</string>
<string name="type_a_message">メッセージを入力してください…</string>
<string name="message_not_sent_short">Message not sent</string>
<string name="message_not_sent_touch_retry">Not sent. Touch to retry.</string>
<string name="message_sending_error">Your message to \'%s\' has not been sent</string>
<string name="add_person">宛先を追加</string>
<string name="attachment">添付</string>
<string name="no_conversations_found">保存された会話はありません</string>
<string name="start_conversation">会話を始める</string>
<string name="reply">返信</string>
<string name="show_character_counter">メッセージ入力中に文字カウントを表示する</string>
<string name="loading_messages">Loading messages…</string>
<string name="no_reply_support">Sender doesn\'t support replies</string>
<string name="draft">Draft</string>
<string name="sending">Sending…</string>
<string name="export_messages">Export messages</string>
<string name="import_messages">Import messages</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">受信したショートメール</string>
<string name="new_message">新しいメッセージ</string>
<string name="mark_as_read">既読にする</string>
<string name="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">%件のメッセージ</item>
<item quantity="other">%件のメッセージ</item>
</plurals>
<!-- FAQ -->
<string name="faq_1_title">なぜアプリ使用にインターネットへのアクセスが必要なのですか?</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メッセンジャー-メッセージを簡単に管理</string>
<!-- Short description has to have max 80 characters -->
<string name="app_short_description">SMS、MMSメッセージをすばやく送信。広告なしのきれいで美しいカスタマイズ可能なインターフェイス</string>
<string name="app_long_description">
SMSやMMSメッセージは親戚と連絡を取るのに便利です。グループメッセージも可能で、アンドロイド7以降のように連絡先をブロックすることも出来ます。
日付フォーマットも複数から使いやすいものを選ぶことができます。日時設定は12時間設定と24時間設定に切り変えることも可能です。
容量が他のアプリと比べて小さいため早くダウンロードができます。
広告や不要な権限はありません。 完全にオープンソースで、カラーもカスタマイズ可能。
<b>シンプルツールの完全なリストはこちらからご確認ください:</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

@@ -2,12 +2,21 @@
<string name="app_name">Paprastas SMS Siuntiklis</string> <string name="app_name">Paprastas SMS Siuntiklis</string>
<string name="app_launcher_name">SMS Siuntiklis</string> <string name="app_launcher_name">SMS Siuntiklis</string>
<string name="type_a_message">Rašykite žinutę…</string> <string name="type_a_message">Rašykite žinutę…</string>
<string name="message_not_sent">Žinutė neišsiųsta.</string> <string name="message_not_sent_short">Message not sent</string>
<string name="message_not_sent_touch_retry">Not sent. Touch to retry.</string>
<string name="message_sending_error">Your message to \'%s\' has not been sent</string>
<string name="add_person">Pridėti žmogų</string> <string name="add_person">Pridėti žmogų</string>
<string name="attachment">Priedas</string> <string name="attachment">Priedas</string>
<string name="no_conversations_found">Nebuvo rasta išsaugotų pokalbių</string> <string name="no_conversations_found">Nebuvo rasta išsaugotų pokalbių</string>
<string name="start_conversation">Pradėtipokalbį</string> <string name="start_conversation">Pradėtipokalbį</string>
<string name="reply">Reply</string> <string name="reply">Reply</string>
<string name="show_character_counter">Show a character counter at writing messages</string>
<string name="loading_messages">Loading messages…</string>
<string name="no_reply_support">Sender doesn\'t support replies</string>
<string name="draft">Draft</string>
<string name="sending">Sending…</string>
<string name="export_messages">Export messages</string>
<string name="import_messages">Import messages</string>
<!-- New conversation --> <!-- New conversation -->
<string name="new_conversation">Naujas pokalbis</string> <string name="new_conversation">Naujas pokalbis</string>

View File

@@ -2,12 +2,21 @@
<string name="app_name">Simple SMS Messenger</string> <string name="app_name">Simple SMS Messenger</string>
<string name="app_launcher_name">SMS മെസഞ്ചർ</string> <string name="app_launcher_name">SMS മെസഞ്ചർ</string>
<string name="type_a_message">മെസ്സേജ് ടൈപ്പ് ചെയ്യുക</string> <string name="type_a_message">മെസ്സേജ് ടൈപ്പ് ചെയ്യുക</string>
<string name="message_not_sent">മെസ്സേജ് അയച്ചിട്ടില്ല.</string> <string name="message_not_sent_short">Message not sent</string>
<string name="message_not_sent_touch_retry">Not sent. Touch to retry.</string>
<string name="message_sending_error">Your message to \'%s\' has not been sent</string>
<string name="add_person">വ്യക്തിയെ ചേർക്കുക</string> <string name="add_person">വ്യക്തിയെ ചേർക്കുക</string>
<string name="attachment">അറ്റാച്ചുമെന്റ്</string> <string name="attachment">അറ്റാച്ചുമെന്റ്</string>
<string name="no_conversations_found">സ്റ്റോർ ചെയ്ത സംഭാഷണങ്ങളൊന്നും കണ്ടെത്തിയില്ല</string> <string name="no_conversations_found">സ്റ്റോർ ചെയ്ത സംഭാഷണങ്ങളൊന്നും കണ്ടെത്തിയില്ല</string>
<string name="start_conversation">ഒരു സംഭാഷണം ആരംഭിക്കുക</string> <string name="start_conversation">ഒരു സംഭാഷണം ആരംഭിക്കുക</string>
<string name="reply">ഒരു സംഭാഷണം ആരംഭിക്കുക</string> <string name="reply">ഒരു സംഭാഷണം ആരംഭിക്കുക</string>
<string name="show_character_counter">Show a character counter at writing messages</string>
<string name="loading_messages">Loading messages…</string>
<string name="no_reply_support">Sender doesn\'t support replies</string>
<string name="draft">Draft</string>
<string name="sending">Sending…</string>
<string name="export_messages">Export messages</string>
<string name="import_messages">Import messages</string>
<!-- New conversation --> <!-- New conversation -->
<string name="new_conversation">പുതിയ സംഭാഷണം</string> <string name="new_conversation">പുതിയ സംഭാഷണം</string>

View File

@@ -2,12 +2,21 @@
<string name="app_name">Eenvoudig Berichtenbeheer (SMS)</string> <string name="app_name">Eenvoudig Berichtenbeheer (SMS)</string>
<string name="app_launcher_name">Berichten</string> <string name="app_launcher_name">Berichten</string>
<string name="type_a_message">Typ een bericht…</string> <string name="type_a_message">Typ een bericht…</string>
<string name="message_not_sent">Bericht niet verzonden.</string> <string name="message_not_sent_short">Bericht niet verzonden</string>
<string name="message_not_sent_touch_retry">Niet verzonden. Probeer het nogmaals.</string>
<string name="message_sending_error">Bericht naar \'%s\' is niet verzonden</string>
<string name="add_person">Persoon toevoegen</string> <string name="add_person">Persoon toevoegen</string>
<string name="attachment">Bijlage</string> <string name="attachment">Bijlage</string>
<string name="no_conversations_found">Geen opgeslagen berichten gevonden</string> <string name="no_conversations_found">Geen opgeslagen berichten gevonden</string>
<string name="start_conversation">Een gesprek starten</string> <string name="start_conversation">Een gesprek starten</string>
<string name="reply">Beantwoorden</string> <string name="reply">Beantwoorden</string>
<string name="show_character_counter">Teller voor het aantal tekens weergeven</string>
<string name="loading_messages">Berichten laden…</string>
<string name="no_reply_support">Afzender ondersteunt geen antwoorden</string>
<string name="draft">Concept</string>
<string name="sending">Versturen…</string>
<string name="export_messages">Berichten exporteren</string>
<string name="import_messages">Berichten importeren</string>
<!-- New conversation --> <!-- New conversation -->
<string name="new_conversation">Nieuw gesprek</string> <string name="new_conversation">Nieuw gesprek</string>

View File

@@ -2,12 +2,21 @@
<string name="app_name">Simple SMS Messenger</string> <string name="app_name">Simple SMS Messenger</string>
<string name="app_launcher_name">SMS Messenger</string> <string name="app_launcher_name">SMS Messenger</string>
<string name="type_a_message">Escrever uma mensagem…</string> <string name="type_a_message">Escrever uma mensagem…</string>
<string name="message_not_sent">Mensagem não enviada</string> <string name="message_not_sent_short">Mensagem não enviada</string>
<string name="message_not_sent_touch_retry">Não enviada. Toque para tentar novamente.</string>
<string name="message_sending_error">A mensagem enviada para \'%s\' não foi enviada.</string>
<string name="add_person">Adicionar pessoa</string> <string name="add_person">Adicionar pessoa</string>
<string name="attachment">Anexo</string> <string name="attachment">Anexo</string>
<string name="no_conversations_found">Não foram encontradas conversas</string> <string name="no_conversations_found">Não foram encontradas conversas</string>
<string name="start_conversation">Iniciar uma conversa</string> <string name="start_conversation">Iniciar uma conversa</string>
<string name="reply">Responder</string> <string name="reply">Responder</string>
<string name="show_character_counter">Mostrar número de caracteres ao escrever a mensagem</string>
<string name="loading_messages">A carregar mensagens…</string>
<string name="no_reply_support">O remetente não aceita respostas</string>
<string name="draft">Rascunho</string>
<string name="sending">A enviar…</string>
<string name="export_messages">Exportar mensagens</string>
<string name="import_messages">Importar mensagens</string>
<!-- New conversation --> <!-- New conversation -->
<string name="new_conversation">Nova conversa</string> <string name="new_conversation">Nova conversa</string>

View File

@@ -2,12 +2,21 @@
<string name="app_name">Simple SMS Messenger</string> <string name="app_name">Simple SMS Messenger</string>
<string name="app_launcher_name">Сообщения</string> <string name="app_launcher_name">Сообщения</string>
<string name="type_a_message">Введите сообщение…</string> <string name="type_a_message">Введите сообщение…</string>
<string name="message_not_sent">Сообщение не отправлено.</string> <string name="message_not_sent_short">Сообщение не отправлено</string>
<string name="message_not_sent_touch_retry">Не отправлено. Нажмите, чтобы повторить попытку.</string>
<string name="message_sending_error">Ваше сообщение для \"%s\" не было отправлено</string>
<string name="add_person">Добавить участника</string> <string name="add_person">Добавить участника</string>
<string name="attachment">Вложение</string> <string name="attachment">Вложение</string>
<string name="no_conversations_found">Нет сохранённых переписок</string> <string name="no_conversations_found">Нет сохранённых переписок</string>
<string name="start_conversation">Начать переписку</string> <string name="start_conversation">Начать переписку</string>
<string name="reply">Ответ</string> <string name="reply">Ответ</string>
<string name="show_character_counter">Показывать счётчик символов при написании сообщений</string>
<string name="loading_messages">Загрузка сообщений…</string>
<string name="no_reply_support">Отправитель не поддерживает ответы</string>
<string name="draft">Черновик</string>
<string name="sending">Отправка…</string>
<string name="export_messages">Экспорт сообщений</string>
<string name="import_messages">Импорт сообщений</string>
<!-- New conversation --> <!-- New conversation -->
<string name="new_conversation">Новая переписка</string> <string name="new_conversation">Новая переписка</string>
@@ -15,13 +24,13 @@
<string name="suggestions">Предложения</string> <string name="suggestions">Предложения</string>
<!-- Notifications --> <!-- Notifications -->
<string name="channel_received_sms">Получено SMS</string> <string name="channel_received_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">Не прочитано</string> <string name="mark_as_unread">Не прочитано</string>
<!-- Confirmation dialog --> <!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Вы уверены, что хотите удалить все сообщения в этой переписке?</string> <string name="delete_whole_conversation_confirmation">Удалить все сообщения в этой переписке?</string>
<!-- Are you sure you want to delete 5 conversations? --> <!-- Are you sure you want to delete 5 conversations? -->
<plurals name="delete_conversations"> <plurals name="delete_conversations">

View File

@@ -2,12 +2,21 @@
<string name="app_name">Jednoduché SMS správy</string> <string name="app_name">Jednoduché SMS správy</string>
<string name="app_launcher_name">SMS správy</string> <string name="app_launcher_name">SMS správy</string>
<string name="type_a_message">Zadajte správu…</string> <string name="type_a_message">Zadajte správu…</string>
<string name="message_not_sent">Správa nebola odoslaná.</string> <string name="message_not_sent_short">Správa nebola odoslaná</string>
<string name="message_not_sent_touch_retry">Správa neodoslaná. Dotykom to skúste znova.</string>
<string name="message_sending_error">Vaša správa pre \'%s\' nebola odoslaná</string>
<string name="add_person">Pridať osobu</string> <string name="add_person">Pridať osobu</string>
<string name="attachment">Príloha</string> <string name="attachment">Príloha</string>
<string name="no_conversations_found">Nenašli sa žiadne uložené konverzácie</string> <string name="no_conversations_found">Nenašli sa žiadne uložené konverzácie</string>
<string name="start_conversation">Začať konverzáciu</string> <string name="start_conversation">Začať konverzáciu</string>
<string name="reply">Odpovedať</string> <string name="reply">Odpovedať</string>
<string name="show_character_counter">Zobraziť počítadlo znakov pri písaní správ</string>
<string name="loading_messages">Načítanie správ…</string>
<string name="no_reply_support">Sender doesn\'t support replies</string>
<string name="draft">Koncept</string>
<string name="sending">Odosiela sa…</string>
<string name="export_messages">Exportovať správy</string>
<string name="import_messages">Importovať správy</string>
<!-- New conversation --> <!-- New conversation -->
<string name="new_conversation">Nová konverzácia</string> <string name="new_conversation">Nová konverzácia</string>

View File

@@ -0,0 +1,82 @@
<resources>
<string name="app_name">Basit SMS Messenger</string>
<string name="app_launcher_name">SMS Messenger</string>
<string name="type_a_message">Bir mesaj yazın…</string>
<string name="message_not_sent_short">Message not sent</string>
<string name="message_not_sent_touch_retry">Not sent. Touch to retry.</string>
<string name="message_sending_error">Your message to \'%s\' has not been sent</string>
<string name="add_person">Kişi Ekle</string>
<string name="attachment">Ek</string>
<string name="no_conversations_found">Kaydedilmiş görüşme bulunamadı</string>
<string name="start_conversation">Bir görüşme başlat</string>
<string name="reply">Yanıtla</string>
<string name="show_character_counter">Mesaj yazarken bir karakter sayacı göster</string>
<string name="loading_messages">Loading messages…</string>
<string name="no_reply_support">Sender doesn\'t support replies</string>
<string name="draft">Draft</string>
<string name="sending">Sending…</string>
<string name="export_messages">Export messages</string>
<string name="import_messages">Import messages</string>
<!-- New conversation -->
<string name="new_conversation">Yeni görüşme</string>
<string name="add_contact_or_number">Kişi veya Numara Ekle…</string>
<string name="suggestions">Öneriler</string>
<!-- Notifications -->
<string name="channel_received_sms">SMS alındı</string>
<string name="new_message">Yeni mesaj</string>
<string name="mark_as_read">Okundu olarak işaretle</string>
<string name="mark_as_unread">Okunmadı olarak işaretle</string>
<!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Bu görüşmenin tüm mesajlarını silmek istediğinizden emin misiniz?</string>
<!-- Are you sure you want to delete 5 conversations? -->
<plurals name="delete_conversations">
<item quantity="one">%d görüşme</item>
<item quantity="other">%d görüşme</item>
</plurals>
<!-- Are you sure you want to delete 5 messages? -->
<plurals name="delete_messages">
<item quantity="one">%d mesaj</item>
<item quantity="other">%d mesaj</item>
</plurals>
<!-- FAQ -->
<string name="faq_1_title">Uygulama neden internete erişim gerektiriyor?</string>
<string name="faq_1_text">Ne yazık ki MMS eklerini göndermek için gerekli. MMS gönderememek, diğer uygulamalara kıyasla gerçekten çok büyük bir dezavantaj olacaktır, bu yüzden bu şekilde gitmeye karar verdik.
Bununla birlikte, genellikle olduğu gibi, hiçbir reklam, izleme veya analiz yoktur, internet yalnızca MMS göndermek için kullanılır.</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">Basit SMS Messenger - Mesajları kolayca yönetin</string>
<!-- Short description has to have max 80 characters -->
<string name="app_short_description">SMS ve MMS mesajlarını reklamsız yönetmenin kolay ve hızlı bir yolu.</string>
<string name="app_long_description">
Hem SMS hem de MMS göndererek iletişim halinde kalmanın harika bir yolu. Uygulama, tıpkı Android 7 ve üzerinden gelen numaraları engellemek gibi grup mesajlaşmasını da düzgün bir şekilde yönetiyor.
Kullanırken kendinizi rahat hissetmeniz için pek çok tarih formatı sunar. 12 ve 24 saatlik zaman formatı arasında da geçiş yapabilirsiniz.
Rakiplerine kıyasla gerçekten çok küçük bir uygulama boyutuna sahip ve indirmeyi gerçekten hızlı hale getiriyor.
Varsayılan olarak materyal tasarım ve koyu tema ile birlikte gelir, kolay kullanım için harika bir kullanıcı deneyimi sağlar. İnternet erişiminin olmaması size diğer uygulamalardan daha fazla gizlilik, güvenlik ve istikrar sağlar.
Hiçbir reklam veya gereksiz izinler içermez. Tamamen açık kaynaklıdır, özelleştirilebilir renkler sağlar.
<b>Basit Araçlar paketinin tamamına buradan göz atın:</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,82 @@
<resources>
<string name="app_name">Simple SMS Messenger</string>
<string name="app_launcher_name">Повідомлення</string>
<string name="type_a_message">Введіть повідомлення…</string>
<string name="message_not_sent_short">Message not sent</string>
<string name="message_not_sent_touch_retry">Not sent. Touch to retry.</string>
<string name="message_sending_error">Your message to \'%s\' has not been sent</string>
<string name="add_person">Додати учасник а</string>
<string name="attachment">Вкладення</string>
<string name="no_conversations_found">Немає збережених листувань</string>
<string name="start_conversation">Почати листування</string>
<string name="reply">Відповідь</string>
<string name="show_character_counter">Показувати кількість символів</string>
<string name="loading_messages">Loading messages…</string>
<string name="no_reply_support">Sender doesn\'t support replies</string>
<string name="draft">Draft</string>
<string name="sending">Sending…</string>
<string name="export_messages">Export messages</string>
<string name="import_messages">Import messages</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">Отримано повідомлення</string>
<string name="new_message">Нове повідомлення</string>
<string name="mark_as_read">Прочитано</string>
<string name="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="many">%d листувань</item>
</plurals>
<!-- Are you sure you want to delete 5 messages? -->
<plurals name="delete_messages">
<item quantity="one">%d повідомлення</item>
<item quantity="many">%d повідомлень</item>
</plurals>
<!-- FAQ -->
<string name="faq_1_title">Чому додаток потрубує доступу до інтернету?</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">Simple SMS Messenger - просте управління SMS</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 тут:</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,82 @@
<resources>
<string name="app_name">简易短信</string>
<string name="app_launcher_name">短信</string>
<string name="type_a_message">输入一个消息…</string>
<string name="message_not_sent_short">Message not sent</string>
<string name="message_not_sent_touch_retry">Not sent. Touch to retry.</string>
<string name="message_sending_error">Your message to \'%s\' has not been sent</string>
<string name="add_person">添加人</string>
<string name="attachment">附件</string>
<string name="no_conversations_found">未找到保存的对话</string>
<string name="start_conversation">开始一个对话</string>
<string name="reply">回复</string>
<string name="show_character_counter">Show a character counter at writing messages</string>
<string name="loading_messages">Loading messages…</string>
<string name="no_reply_support">Sender doesn\'t support replies</string>
<string name="draft">Draft</string>
<string name="sending">Sending…</string>
<string name="export_messages">Export messages</string>
<string name="import_messages">Import messages</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">接收到的短信</string>
<string name="new_message">新消息</string>
<string name="mark_as_read">标记为已读</string>
<string name="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">为什么该应用需要访问互联网?</string>
<string name="faq_1_text">很遗憾这对于发送彩信附件是必须的。如果不能发送彩信的话这相比其他应用会是一个巨大的劣势,所以我们决定这么采取现在的方式。
但是和其他应用一样,不包含广告、追踪或者任意的分析工具, 互联网访问仅用于发送彩信。</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">简易短信 - 轻松管理消息</string>
<!-- Short description has to have max 80 characters -->
<string name="app_short_description">无广告的管理短信和彩信的轻松和简便方式。</string>
<string name="app_long_description">
发送短信和彩信是一个和您亲友保持联系的绝佳方式。这个应用也可以可以正确处理群消息,在 Android 7 以上版本亦可阻止号码。
它提供多个日期格式以便选择,符合您使用习惯。您也可以在 12 和 24 时制格式之间选择。
它相对于其他竞品有着极小的大小,使得其下载非常快。
它遵循质感设计且默认应用黑暗模式,提供便于使用的极佳的用户体验。
不包含广告及非必要的权限,而且完全开放源代码,并提供自定义颜色。
<b>于此查看简易工具系列全套:</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,5 @@
<resources>
<string name="package_name">com.simplemobiletools.smsmessenger</string>
</resources>

View File

@@ -2,12 +2,21 @@
<string name="app_name">Simple SMS Messenger</string> <string name="app_name">Simple SMS Messenger</string>
<string name="app_launcher_name">SMS Messenger</string> <string name="app_launcher_name">SMS Messenger</string>
<string name="type_a_message">Type a message…</string> <string name="type_a_message">Type a message…</string>
<string name="message_not_sent">Message has not been sent.</string> <string name="message_not_sent_short">Message not sent</string>
<string name="message_not_sent_touch_retry">Not sent. Touch to retry.</string>
<string name="message_sending_error">Your message to \'%s\' has not been sent</string>
<string name="add_person">Add Person</string> <string name="add_person">Add Person</string>
<string name="attachment">Attachment</string> <string name="attachment">Attachment</string>
<string name="no_conversations_found">No stored conversations have been found</string> <string name="no_conversations_found">No stored conversations have been found</string>
<string name="start_conversation">Start a conversation</string> <string name="start_conversation">Start a conversation</string>
<string name="reply">Reply</string> <string name="reply">Reply</string>
<string name="show_character_counter">Show a character counter at writing messages</string>
<string name="loading_messages">Loading messages…</string>
<string name="no_reply_support">Sender doesn\'t support replies</string>
<string name="draft">Draft</string>
<string name="sending">Sending…</string>
<string name="export_messages">Export messages</string>
<string name="import_messages">Import messages</string>
<!-- New conversation --> <!-- New conversation -->
<string name="new_conversation">New conversation</string> <string name="new_conversation">New conversation</string>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<searchable
xmlns:android="http://schemas.android.com/apk/res/android"
android:hint="@string/search"
android:label="@string/app_name"/>

View File

@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.4.10' ext.kotlin_version = '1.4.30'
repositories { repositories {
google() google()
jcenter() jcenter()

View File

@@ -0,0 +1 @@
* Fixed some smaller glitches + translation improvements

View File

@@ -0,0 +1,4 @@
* Allow dialing or copying selected conversation phone numbers
* Allow copying specific parts of messages into clipboard
* Adding an option to show character counter at outgoing messages
* Couple other UI, translation and stability improvements

View File

@@ -0,0 +1,3 @@
* Properly show private contact names at group conversations
* Fixed private contacts not being visible on Android 11+
* Some stability and translation improvements

View File

@@ -0,0 +1,3 @@
* Cache messages for better performance
* Add a scrollbar on the main screen
* Adding some stability and translation improvements

View File

@@ -0,0 +1 @@
* Fixing a crash at devices with multiple SIM cards

View File

@@ -0,0 +1 @@
Fixed messages not being sent in some cases

View File

@@ -0,0 +1,3 @@
* Prefetch all messages at first launch for better performance
* Require Simple Thank You for color customization
* Some stability, translation and UX improvements

View File

@@ -0,0 +1,4 @@
* Improved delivery reports, show a notification at failed messages
* Allow resending failed messages with tapping them
* Added multiple dual-SIM related improvements
* Many other UX, stability, performance and translation improvements

View File

@@ -0,0 +1,3 @@
* Fixed a glitch with "Sending..." stuck at messages
* Allow selecting a phone numbers at contacts with multiple ones
* Some translation and stability improvements

View File

@@ -0,0 +1,3 @@
* Fixed a glitch with inability to send messages in empty conversations
* Adding a settings item for quickly getting into notification settings
* Some stability and translation improvements

View File

@@ -0,0 +1 @@
* Adding some stability and translation improvements

View File

@@ -0,0 +1,3 @@
* Added Search
* Added a White theme with special handling
* Some stability and translation improvements

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View File

@@ -1,6 +1,6 @@
#Sat Mar 06 06:37:14 PST 2021 #Sat Mar 06 06:37:14 PST 2021
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip