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

View File

@ -17,7 +17,7 @@ insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 4
continuation_indent_size = 8
continuation_indent_size = 4
[*.xml]
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
==========
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)*
----------------------------

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.
Check out the full suite of Simple Tools here:
Check out the full suite of Simple Tools here:
https://www.simplemobiletools.com
Facebook:
Facebook:
https://www.facebook.com/simplemobiletools
Reddit:
Reddit:
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://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;">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app_1.jpg" width="30%">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app_2.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/english/2.jpg" width="30%">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/english/3.jpg" width="30%">
</div>

View File

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

View File

@ -38,6 +38,21 @@
android:name=".activities.ThreadActivity"
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
android:name=".activities.NewConversationActivity"
android:parentActivityName=".activities.MainActivity">
@ -121,6 +136,10 @@
<service android:name="com.android.mms.transaction.TransactionService" />
<receiver android:name=".receivers.SmsStatusSentReceiver" />
<receiver android:name=".receivers.SmsStatusDeliveredReceiver" />
<receiver
android:name=".receivers.SmsReceiver"
android:permission="android.permission.BROADCAST_SMS">
@ -149,11 +168,6 @@
android:exported="true"
android:taskAffinity="com.klinker.android.messaging.MMS_RECEIVED" />
<receiver
android:name=".receivers.SmsSentReceiver"
android:exported="true"
android:taskAffinity="${applicationId}.SMS_SENT" />
<receiver
android:name=".receivers.MarkAsReadReceiver"
android:enabled="true"

View File

@ -18,10 +18,7 @@ import com.simplemobiletools.commons.models.FAQItem
import com.simplemobiletools.smsmessenger.BuildConfig
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.adapters.ConversationsAdapter
import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.extensions.conversationsDB
import com.simplemobiletools.smsmessenger.extensions.getConversations
import com.simplemobiletools.smsmessenger.extensions.updateUnreadCountBadge
import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.THREAD_ID
import com.simplemobiletools.smsmessenger.helpers.THREAD_TITLE
import com.simplemobiletools.smsmessenger.models.Conversation
@ -86,6 +83,8 @@ class MainActivity : SimpleActivity() {
updateTextColors(main_coordinator)
no_conversations_placeholder_2.setTextColor(getAdjustedPrimaryColor())
no_conversations_placeholder_2.underlineText()
conversations_fastscroller.updatePrimaryColor()
conversations_fastscroller.updateBubbleColors()
checkShortcut()
}
@ -101,11 +100,13 @@ class MainActivity : SimpleActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
updateMenuItemColors(menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.search -> launchSearch()
R.id.settings -> launchSettings()
R.id.about -> launchAbout()
else -> return super.onOptionsItemSelected(item)
@ -138,7 +139,10 @@ class MainActivity : SimpleActivity() {
handlePermission(PERMISSION_READ_CONTACTS) {
initMessenger()
bus = EventBus.getDefault()
bus!!.register(this)
try {
bus!!.register(this)
} catch (e: Exception) {
}
}
} else {
finish()
@ -165,7 +169,12 @@ class MainActivity : SimpleActivity() {
private fun getCachedConversations() {
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)
runOnUiThread {
setupConversations(conversations)
@ -175,46 +184,43 @@ class MainActivity : SimpleActivity() {
}
private fun getNewConversations(cachedConversations: ArrayList<Conversation>) {
val privateCursor = getMyContactsCursor().loadInBackground()
val privateCursor = getMyContactsCursor()?.loadInBackground()
ensureBackgroundThread {
val conversations = getConversations()
// check if no message came from a privately stored contact in Simple Contacts
val privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor)
if (privateContacts.isNotEmpty()) {
conversations.filter { it.title == it.phoneNumber }.forEach { conversation ->
privateContacts.forEach { contact ->
if (contact.doesContainPhoneNumber(conversation.phoneNumber)) {
conversation.title = contact.name
conversation.photoUri = contact.photoUri
}
}
}
}
val conversations = getConversations(privateContacts = privateContacts)
runOnUiThread {
setupConversations(conversations)
}
conversations.forEach { clonedConversation ->
if (!cachedConversations.map { it.thread_id }.contains(clonedConversation.thread_id)) {
if (!cachedConversations.map { it.threadId }.contains(clonedConversation.threadId)) {
conversationsDB.insertOrUpdate(clonedConversation)
cachedConversations.add(clonedConversation)
}
}
cachedConversations.forEach { cachedConversation ->
if (!conversations.map { it.thread_id }.contains(cachedConversation.thread_id)) {
conversationsDB.delete(cachedConversation.id!!)
if (!conversations.map { it.threadId }.contains(cachedConversation.threadId)) {
conversationsDB.deleteThreadId(cachedConversation.threadId)
}
}
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) {
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_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
if (currAdapter == null) {
ConversationsAdapter(this, conversations, conversations_list, conversations_fastscroller) {
Intent(this, ThreadActivity::class.java).apply {
putExtra(THREAD_ID, (it as Conversation).thread_id)
putExtra(THREAD_ID, (it as Conversation).threadId)
putExtra(THREAD_TITLE, it.title)
startActivity(this)
}
}.apply {
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 {
try {
(currAdapter as ConversationsAdapter).updateConversations(conversations)
@ -281,6 +297,10 @@ class MainActivity : SimpleActivity() {
.build()
}
private fun launchSearch() {
startActivity(Intent(applicationContext, SearchActivity::class.java))
}
private fun launchSettings() {
startActivity(Intent(applicationContext, SettingsActivity::class.java))
}
@ -290,7 +310,8 @@ class MainActivity : SimpleActivity() {
val faqItems = arrayListOf(
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)

View File

@ -5,12 +5,12 @@ import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.WindowManager
import com.google.gson.Gson
import com.reddit.indicatorfastscroll.FastScrollItemIndicator
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.MyContactsContentProvider
import com.simplemobiletools.commons.helpers.PERMISSION_READ_CONTACTS
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.smsmessenger.R
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.pressedTextColor = adjustedPrimaryColor
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 {
@ -134,7 +137,20 @@ class NewConversationActivity : SimpleActivity() {
ContactsAdapter(this, contacts, contacts_list, null) {
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 {
contacts_list.adapter = this
}
@ -143,7 +159,7 @@ class NewConversationActivity : SimpleActivity() {
}
private fun fillSuggestedContacts(callback: () -> Unit) {
val privateCursor = getMyContactsCursor().loadInBackground()
val privateCursor = getMyContactsCursor()?.loadInBackground()
ensureBackgroundThread {
privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor)
val suggestions = getSuggestedContacts(privateContacts)
@ -160,10 +176,13 @@ class NewConversationActivity : SimpleActivity() {
layoutInflater.inflate(R.layout.item_suggested_contact, null).apply {
suggested_contact_name.text = contact.name
suggested_contact_name.setTextColor(baseConfig.textColor)
SimpleContactsHelper(this@NewConversationActivity).loadContactImage(contact.photoUri, suggested_contact_image, contact.name)
suggestions_holder.addView(this)
setOnClickListener {
launchThreadActivity(contact.phoneNumbers.first(), contact.name)
if (!isDestroyed) {
SimpleContactsHelper(this@NewConversationActivity).loadContactImage(contact.photoUri, suggested_contact_image, 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) {
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 {
putExtra(THREAD_ID, getThreadId(phoneNumber).toInt())
putExtra(THREAD_ID, getThreadId(numbers))
putExtra(THREAD_TITLE, name)
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) {
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()
setupCustomizeColors()
setupCustomizeNotifications()
setupUseEnglish()
setupManageBlockedNumbers()
setupChangeDateTimeFormat()
setupFontSize()
setupShowCharacterCounter()
updateTextColors(settings_scrollview)
if (blockedNumbersAtPause != -1 && blockedNumbersAtPause != getBlockedNumbers().hashCode()) {
@ -52,15 +54,23 @@ class SettingsActivity : SimpleActivity() {
}
private fun setupPurchaseThankYou() {
settings_purchase_thank_you_holder.beVisibleIf(!isThankYouInstalled())
settings_purchase_thank_you_holder.beGoneIf(isOrWasThankYouInstalled())
settings_purchase_thank_you_holder.setOnClickListener {
launchPurchaseThankYouIntent()
}
}
private fun setupCustomizeColors() {
settings_customize_colors_label.text = getCustomizeColorsString()
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.graphics.BitmapFactory
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.provider.Telephony
import android.telephony.SubscriptionManager
import android.text.TextUtils
import android.util.TypedValue
import android.view.*
import android.view.inputmethod.EditorInfo
import android.widget.LinearLayout
@ -38,6 +41,9 @@ import com.simplemobiletools.smsmessenger.adapters.ThreadAdapter
import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.models.*
import com.simplemobiletools.smsmessenger.receivers.SmsStatusDeliveredReceiver
import com.simplemobiletools.smsmessenger.receivers.SmsStatusSentReceiver
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_thread.*
import kotlinx.android.synthetic.main.item_attachment.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 PICK_ATTACHMENT_INTENT = 1
private var threadId = 0
private var threadId = 0L
private var currentSIMCardIndex = 0
private var isActivityVisible = false
private var refreshedSinceSent = false
private var threadItems = ArrayList<ThreadItem>()
private var bus: EventBus? = null
private var participants = ArrayList<SimpleContact>()
@ -71,7 +78,7 @@ class ThreadActivity : SimpleActivity() {
return
}
threadId = intent.getIntExtra(THREAD_ID, 0)
threadId = intent.getLongExtra(THREAD_ID, 0L)
intent.getStringExtra(THREAD_TITLE)?.let {
supportActionBar?.title = it
}
@ -80,7 +87,19 @@ class ThreadActivity : SimpleActivity() {
bus!!.register(this)
handlePermission(PERMISSION_READ_PHONE_STATE) {
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 {
finish()
}
@ -97,94 +116,6 @@ class ThreadActivity : SimpleActivity() {
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() {
super.onDestroy()
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() {
threadItems = getThreadItems()
invalidateOptionsMenu()
runOnUiThread {
val adapter = ThreadAdapter(this, threadItems, thread_messages_list, thread_messages_fastscroller) {}
thread_messages_list.adapter = adapter
val currAdapter = thread_messages_list.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 ->
@ -252,7 +268,7 @@ class ThreadActivity : SimpleActivity() {
confirm_inserted_number?.setOnClickListener {
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)
}
}
@ -264,6 +280,10 @@ class ThreadActivity : SimpleActivity() {
confirm_manage_contacts.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 {
sendMessage()
}
@ -271,6 +291,7 @@ class ThreadActivity : SimpleActivity() {
thread_send_message.isClickable = false
thread_type_message.onTextChangeListener {
checkSendMessageAvailability()
thread_character_counter.text = it.length.toString()
}
confirm_manage_contacts.setOnClickListener {
@ -284,7 +305,7 @@ class ThreadActivity : SimpleActivity() {
}
}
val newThreadId = getThreadId(numbers).toInt()
val newThreadId = getThreadId(numbers)
if (threadId != newThreadId) {
Intent(this, ThreadActivity::class.java).apply {
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")
private fun setupSIMSelector() {
val availableSIMs = SubscriptionManager.from(this).activeSubscriptionInfoList ?: return
if (availableSIMs.size > 1) {
availableSIMs.forEachIndexed { index, subscriptionInfo ->
var label = subscriptionInfo.displayName.toString()
var label = subscriptionInfo.displayName?.toString() ?: ""
if (subscriptionInfo.number?.isNotEmpty() == true) {
label += " (${subscriptionInfo.number})"
}
@ -329,6 +397,10 @@ class ThreadActivity : SimpleActivity() {
}
}
if (numbers.isEmpty()) {
return
}
currentSIMCardIndex = availableSIMs.indexOfFirstOrNull { it.subscriptionId == config.getUseSIMIdAtNumber(numbers.first()) } ?: 0
thread_select_sim_icon.applyColorFilter(config.textColor)
@ -396,11 +468,20 @@ class ThreadActivity : SimpleActivity() {
}
private fun showSelectedContacts() {
val adjustedColor = getAdjustedPrimaryColor()
val views = ArrayList<View>()
participants.forEach {
val contact = it
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.setTextColor(adjustedColor.getContrastColor())
selected_contact_remove.applyColorFilter(adjustedColor.getContrastColor())
selected_contact_remove.setOnClickListener {
if (contact.rawId != participants.first().rawId) {
removeSelectedContact(contact.rawId)
@ -424,7 +505,7 @@ class ThreadActivity : SimpleActivity() {
private fun markAsUnread() {
ensureBackgroundThread {
conversationsDB.markUnread(threadId.toLong())
conversationsDB.markUnread(threadId)
markThreadMessagesUnread(threadId)
runOnUiThread {
finish()
@ -462,13 +543,17 @@ class ThreadActivity : SimpleActivity() {
items.add(message)
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) {
hadUnreadItems = true
markMessageRead(message.id, message.isMMS)
conversationsDB.markRead(threadId.toLong())
conversationsDB.markRead(threadId)
}
if (i == cnt - 1 && message.type == Telephony.Sms.MESSAGE_TYPE_SENT) {
@ -562,6 +647,7 @@ class ThreadActivity : SimpleActivity() {
val settings = Settings()
settings.useSystemSending = true
settings.deliveryReports = true
val SIMId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId
if (SIMId != null) {
@ -582,19 +668,35 @@ class ThreadActivity : SimpleActivity() {
message.addMedia(byteArray, mimeType)
} catch (e: Exception) {
showErrorToast(e)
} catch (e: Error) {
toast(e.localizedMessage ?: getString(R.string.unknown_error_occurred))
}
}
}
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("")
attachmentUris.clear()
thread_attachments_holder.beGone()
thread_attachments_wrapper.removeAllViews()
Handler().postDelayed({
if (!refreshedSinceSent) {
refreshMessages()
}
}, 2000)
} catch (e: Exception) {
showErrorToast(e)
} catch (e: Error) {
toast(e.localizedMessage ?: getString(R.string.unknown_error_occurred))
}
}
@ -652,13 +754,30 @@ class ThreadActivity : SimpleActivity() {
showSelectedContacts()
}
@SuppressLint("MissingPermission")
@Subscribe(threadMode = ThreadMode.ASYNC)
fun refreshMessages(event: Events.RefreshMessages) {
refreshedSinceSent = true
if (isActivityVisible) {
notificationManager.cancel(threadId)
notificationManager.cancel(threadId.hashCode())
}
val lastMaxId = messages.maxByOrNull { it.id }?.id ?: 0L
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()
}
}

View File

@ -5,12 +5,16 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Filter
import android.widget.RelativeLayout
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.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.smsmessenger.R
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) {
var resultList = ArrayList<SimpleContact>()
@ -30,8 +34,13 @@ class AutoCompleteTextViewAdapter(val activity: SimpleActivity, val contacts: Ar
isFocusable = false
}
val backgroundColor = activity.config.backgroundColor
findViewById<TextView>(R.id.item_contact_name).text = contact.name
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)
}

View File

@ -2,6 +2,7 @@ package com.simplemobiletools.smsmessenger.adapters
import android.content.Intent
import android.graphics.Typeface
import android.net.Uri
import android.text.TextUtils
import android.util.TypedValue
import android.view.Menu
@ -37,8 +38,10 @@ class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayLis
override fun prepareActionMode(menu: Menu) {
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_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) {
R.id.cab_add_number_to_contact -> addNumberToContact()
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_select_all -> selectAll()
}
}
@ -59,9 +64,9 @@ class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayLis
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() {}
@ -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() {
val itemsCnt = selectedKeys.size
val items = resources.getQuantityString(R.plurals.delete_conversations, itemsCnt, itemsCnt)
@ -129,13 +154,17 @@ class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayLis
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()
conversationsToRemove.forEach {
activity.deleteConversation(it.thread_id)
activity.notificationManager.cancel(it.thread_id)
activity.deleteConversation(it.threadId)
activity.notificationManager.cancel(it.hashCode())
}
try {
conversations.removeAll(conversationsToRemove)
} catch (ignored: Exception) {
}
conversations.removeAll(conversationsToRemove)
activity.runOnUiThread {
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) {
super.onViewRecycled(holder)
@ -190,7 +219,7 @@ class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayLis
private fun setupView(view: View, conversation: Conversation) {
view.apply {
conversation_frame.isSelected = selectedKeys.contains(conversation.thread_id)
conversation_frame.isSelected = selectedKeys.contains(conversation.hashCode())
conversation_address.apply {
text = conversation.title
@ -203,7 +232,7 @@ class ConversationsAdapter(activity: SimpleActivity, var conversations: ArrayLis
}
conversation_date.apply {
text = conversation.date.formatDateOrTime(context, true)
text = conversation.date.formatDateOrTime(context, true, false)
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.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.dialogs.SelectTextDialog
import com.simplemobiletools.smsmessenger.extensions.deleteMessage
import com.simplemobiletools.smsmessenger.helpers.*
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_sent_unknown_attachment.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.*
class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem>, recyclerView: MyRecyclerView, fastScroller: FastScroller,
itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) {
class ThreadAdapter(
activity: SimpleActivity, var messages: ArrayList<ThreadItem>, recyclerView: MyRecyclerView, fastScroller: FastScroller,
itemClick: (Any) -> Unit
) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) {
private val roundedCornersRadius = resources.getDimension(R.dimen.normal_margin).toInt()
private var fontSize = activity.getTextSize()
@ -58,6 +63,7 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
menu.apply {
findItem(R.id.cab_copy_to_clipboard).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) {
R.id.cab_copy_to_clipboard -> copyToClipboard()
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_select_all -> selectAll()
}
}
@ -78,9 +85,9 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
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() {}
@ -92,6 +99,7 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
THREAD_RECEIVED_MESSAGE -> R.layout.item_received_message
THREAD_SENT_MESSAGE_ERROR -> R.layout.item_thread_error
THREAD_SENT_MESSAGE_SUCCESS -> R.layout.item_thread_success
THREAD_SENT_MESSAGE_SENDING -> R.layout.item_thread_sending
else -> R.layout.item_sent_message
}
return createViewHolder(layout, parent)
@ -99,11 +107,15 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
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) {
is ThreadDateTime -> setupDateTime(itemView, item)
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)
@ -118,23 +130,38 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
(messages[position] as? Message)?.isReceivedMessage() == true -> THREAD_RECEIVED_MESSAGE
item is ThreadError -> THREAD_SENT_MESSAGE_ERROR
item is ThreadSuccess -> THREAD_SENT_MESSAGE_SUCCESS
item is ThreadSending -> THREAD_SENT_MESSAGE_SENDING
else -> THREAD_SENT_MESSAGE
}
}
private fun copyToClipboard() {
val firstItem = getSelectedItems().first() as? Message ?: return
val firstItem = getSelectedItems().firstOrNull() as? Message ?: return
activity.copyToClipboard(firstItem.body)
}
private fun shareText() {
val firstItem = getSelectedItems().first() as? Message ?: return
val firstItem = getSelectedItems().firstOrNull() as? Message ?: return
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() {
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 question = String.format(resources.getString(baseString), items)
@ -151,7 +178,7 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
return
}
val messagesToRemove = messages.filter { selectedKeys.contains((it as? Message)?.id ?: 0) } as ArrayList<ThreadItem>
val messagesToRemove = getSelectedItems()
val positions = getSelectedItemPositions()
messagesToRemove.forEach {
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
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)
fun updateMessages(newMessages: ArrayList<ThreadItem>) {
val oldHashCode = messages.hashCode()
val newHashCode = newMessages.hashCode()
if (newHashCode != oldHashCode) {
messages = newMessages
notifyDataSetChanged()
recyclerView.scrollToPosition(messages.size - 1)
}
}
private fun setupView(view: View, message: Message) {
view.apply {
thread_message_holder.isSelected = selectedKeys.contains(message.id)
thread_message_holder.isSelected = selectedKeys.contains(message.hashCode())
thread_message_body.apply {
text = message.body
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize)
@ -196,7 +226,7 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
} else {
thread_message_sender_photo?.beGone()
val background = context.getAdjustedPrimaryColor()
thread_message_body.background.applyColorFilter(background.adjustAlpha(0.8f))
thread_message_body.background.applyColorFilter(background)
val contrastColor = background.getContrastColor()
thread_message_body.setTextColor(contrastColor)
@ -207,7 +237,7 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
if (message.attachment?.attachments?.isNotEmpty() == true) {
for (attachment in message.attachment.attachments) {
val mimetype = attachment.mimetype
val uri = attachment.uri
val uri = attachment.getUri()
if (mimetype.startsWith("image/") || mimetype.startsWith("video/")) {
val imageView = layoutInflater.inflate(R.layout.item_attachment_image, null)
thread_mesage_attachments_holder.addView(imageView)
@ -238,7 +268,9 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
}
builder.into(imageView.attachment_image)
imageView.attachment_image.setOnClickListener { launchViewIntent(uri, mimetype, attachment.filename) }
imageView.attachment_image.setOnClickListener {
launchViewIntent(uri, mimetype, attachment.filename)
}
} else {
if (message.isReceivedMessage()) {
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
}
setTextColor(textColor)
setOnClickListener { launchViewIntent(uri, mimetype, attachment.filename) }
setOnClickListener {
launchViewIntent(uri, mimetype, attachment.filename)
}
}
}
thread_mesage_attachments_holder.addView(attachmentView)
@ -255,12 +289,14 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
val background = context.getAdjustedPrimaryColor()
val attachmentView = layoutInflater.inflate(R.layout.item_sent_unknown_attachment, null).apply {
thread_sent_attachment_label.apply {
this.background.applyColorFilter(background.adjustAlpha(0.8f))
this.background.applyColorFilter(background)
setTextColor(background.getContrastColor())
if (attachment.filename.isNotEmpty()) {
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)
@ -295,7 +331,7 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
private fun setupDateTime(view: View, dateTime: ThreadDateTime) {
view.apply {
thread_date_time.apply {
text = dateTime.date.formatDateOrTime(context, false)
text = dateTime.date.formatDateOrTime(context, false, false)
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize)
}
thread_date_time.setTextColor(textColor)
@ -313,4 +349,22 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList<ThreadItem
private fun setupThreadSuccess(view: View) {
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.Room
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.MessageAttachmentsDao
import com.simplemobiletools.smsmessenger.interfaces.MessagesDao
import com.simplemobiletools.smsmessenger.models.Attachment
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 fun ConversationsDao(): ConversationsDao
abstract fun AttachmentsDao(): AttachmentsDao
abstract fun MessageAttachmentsDao(): MessageAttachmentsDao
abstract fun MessagesDao(): MessagesDao
companion object {
private var db: MessagesDatabase? = null
@ -20,11 +37,44 @@ abstract class MessagesDatabase : RoomDatabase() {
synchronized(MessagesDatabase::class) {
if (db == null) {
db = Room.databaseBuilder(context.applicationContext, MessagesDatabase::class.java, "conversations.db")
.fallbackToDestructiveMigration()
.addMigrations(MIGRATION_1_2)
.addMigrations(MIGRATION_2_3)
.build()
}
}
}
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 com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.smsmessenger.models.Conversation
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.Context
import android.content.Intent
import android.database.Cursor
import android.graphics.Bitmap
import android.media.AudioAttributes
import android.media.AudioManager
@ -27,7 +28,10 @@ import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.ThreadActivity
import com.simplemobiletools.smsmessenger.databases.MessagesDatabase
import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.interfaces.AttachmentsDao
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.receivers.DirectReplyReceiver
import com.simplemobiletools.smsmessenger.receivers.MarkAsReadReceiver
@ -41,7 +45,13 @@ fun Context.getMessagessDB() = MessagesDatabase.getInstance(this)
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 projection = arrayOf(
Sms._ID,
@ -76,17 +86,17 @@ fun Context.getMessages(threadId: Int): ArrayList<Message> {
return@queryCursor
}
val id = cursor.getIntValue(Sms._ID)
val id = cursor.getLongValue(Sms._ID)
val body = cursor.getStringValue(Sms.BODY)
val type = cursor.getIntValue(Sms.TYPE)
val namePhoto = getNameAndPhotoFromPhoneNumber(senderNumber)
val senderName = namePhoto?.name ?: ""
val photoUri = namePhoto?.photoUri ?: ""
val senderName = namePhoto.name
val photoUri = namePhoto.photoUri ?: ""
val date = (cursor.getLongValue(Sms.DATE) / 1000).toInt()
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 participant = SimpleContact(0, 0, senderName, photoUri, arrayListOf(senderNumber))
val participant = SimpleContact(0, 0, senderName, photoUri, arrayListOf(senderNumber), ArrayList(), ArrayList())
val isMMS = false
val message = Message(id, body, type, arrayListOf(participant), date, read, thread, isMMS, null, senderName, photoUri, subscriptionId)
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
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 projection = arrayOf(
Mms._ID,
@ -125,13 +135,13 @@ fun Context.getMMS(threadId: Int? = null, sortOrder: String? = null): ArrayList<
val messages = ArrayList<Message>()
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 ->
val mmsId = cursor.getIntValue(Mms._ID)
val mmsId = cursor.getLongValue(Mms._ID)
val type = cursor.getIntValue(Mms.MESSAGE_BOX)
val date = cursor.getLongValue(Mms.DATE).toInt()
val read = cursor.getIntValue(Mms.READ) == 1
val threadId = cursor.getIntValue(Mms.THREAD_ID)
val threadId = cursor.getLongValue(Mms.THREAD_ID)
val subscriptionId = cursor.getIntValue(Mms.SUBSCRIPTION_ID)
val participants = if (threadParticipants.containsKey(threadId)) {
threadParticipants[threadId]!!
@ -143,17 +153,15 @@ fun Context.getMMS(threadId: Int? = null, sortOrder: String? = null): ArrayList<
val isMMS = true
val attachment = getMmsAttachment(mmsId)
val body = attachment?.text ?: ""
val body = attachment.text
var senderName = ""
var senderPhotoUri = ""
if (type != Mms.MESSAGE_BOX_SENT && type != Mms.MESSAGE_BOX_FAILED) {
val number = getMMSSender(mmsId)
val namePhoto = getNameAndPhotoFromPhoneNumber(number)
if (namePhoto != null) {
senderName = namePhoto.name
senderPhotoUri = namePhoto.photoUri ?: ""
}
senderName = namePhoto.name
senderPhotoUri = namePhoto.photoUri ?: ""
}
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
}
fun Context.getMMSSender(msgId: Int): String {
fun Context.getMMSSender(msgId: Long): String {
val uri = Uri.parse("${Mms.CONTENT_URI}/$msgId/addr")
val projection = arrayOf(
Mms.Addr.ADDRESS
@ -185,7 +193,7 @@ fun Context.getMMSSender(msgId: Int): String {
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 projection = arrayOf(
Threads._ID,
@ -208,7 +216,7 @@ fun Context.getConversations(threadId: Long? = null): ArrayList<Conversation> {
val simpleContactHelper = SimpleContactsHelper(this)
val blockedNumbers = getBlockedNumbers()
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) ?: ""
if (snippet.isEmpty()) {
snippet = getThreadSnippet(id)
@ -226,12 +234,12 @@ fun Context.getConversations(threadId: Long? = null): ArrayList<Conversation> {
return@queryCursor
}
val names = getThreadContactNames(phoneNumbers)
val names = getThreadContactNames(phoneNumbers, privateContacts)
val title = TextUtils.join(", ", names.toTypedArray())
val photoUri = if (phoneNumbers.size == 1) simpleContactHelper.getPhotoUriFromPhoneNumber(phoneNumbers.first()) else ""
val isGroupConversation = phoneNumbers.size > 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)
}
@ -241,7 +249,7 @@ fun Context.getConversations(threadId: Long? = null): ArrayList<Conversation> {
// based on https://stackoverflow.com/a/6446831/1967672
@SuppressLint("NewApi")
fun Context.getMmsAttachment(id: Int): MessageAttachment? {
fun Context.getMmsAttachment(id: Long): MessageAttachment {
val uri = if (isQPlus()) {
Mms.Part.CONTENT_URI
} else {
@ -259,15 +267,15 @@ fun Context.getMmsAttachment(id: Int): MessageAttachment? {
var attachmentName = ""
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)
if (mimetype == "text/plain") {
messageAttachment.text = cursor.getStringValue(Mms.Part.TEXT) ?: ""
} 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)
} 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)
} else {
val text = cursor.getStringValue(Mms.Part.TEXT)
@ -286,7 +294,7 @@ fun Context.getLatestMMS(): Message? {
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 latestMms = getMMS(threadId, sortOrder).firstOrNull()
var snippet = latestMms?.body ?: ""
@ -313,7 +321,29 @@ fun Context.getThreadSnippet(threadId: Int): String {
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 projection = arrayOf(
ThreadsColumns.RECIPIENT_IDS
@ -335,9 +365,9 @@ fun Context.getThreadParticipants(threadId: Int, contactsMap: HashMap<Int, Simpl
val phoneNumber = getPhoneNumberFromAddressId(addressId)
val namePhoto = getNameAndPhotoFromPhoneNumber(phoneNumber)
val name = namePhoto?.name ?: ""
val photoUri = namePhoto?.photoUri ?: ""
val contact = SimpleContact(addressId, addressId, name, photoUri, arrayListOf(phoneNumber))
val name = namePhoto.name
val photoUri = namePhoto.photoUri ?: ""
val contact = SimpleContact(addressId, addressId, name, photoUri, arrayListOf(phoneNumber), ArrayList(), ArrayList())
participants.add(contact)
}
}
@ -356,10 +386,20 @@ fun Context.getThreadPhoneNumbers(recipientIds: List<Int>): ArrayList<String> {
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>()
phoneNumbers.forEach {
names.add(SimpleContactsHelper(this).getNameFromPhoneNumber(it))
phoneNumbers.forEach { number ->
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
}
@ -400,9 +440,9 @@ fun Context.getSuggestedContacts(privateContacts: ArrayList<SimpleContact>): Arr
queryCursor(uri, projection, selection, selectionArgs, sortOrder, showErrors = true) { cursor ->
val senderNumber = cursor.getStringValue(Sms.ADDRESS) ?: return@queryCursor
val namePhoto = getNameAndPhotoFromPhoneNumber(senderNumber)
var senderName = namePhoto?.name ?: ""
var photoUri = namePhoto?.photoUri ?: ""
if (namePhoto == null || isNumberBlocked(senderNumber, blockedNumbers)) {
var senderName = namePhoto.name
var photoUri = namePhoto.photoUri ?: ""
if (isNumberBlocked(senderNumber, blockedNumbers)) {
return@queryCursor
} else if (namePhoto.name == senderNumber) {
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())) {
contacts.add(contact)
}
@ -427,7 +467,7 @@ fun Context.getSuggestedContacts(privateContacts: ArrayList<SimpleContact>): Arr
return contacts
}
fun Context.getNameAndPhotoFromPhoneNumber(number: String): NamePhoto? {
fun Context.getNameAndPhotoFromPhoneNumber(number: String): NamePhoto {
if (!hasPermission(PERMISSION_READ_CONTACTS)) {
return NamePhoto(number, null)
}
@ -454,7 +494,7 @@ fun Context.getNameAndPhotoFromPhoneNumber(number: String): NamePhoto? {
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 contentValues = ContentValues().apply {
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)
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
val selection = "${Sms.THREAD_ID} = ?"
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
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 selection = "${Sms._ID} = ?"
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 contentValues = ContentValues().apply {
put(Sms.READ, 1)
@ -499,9 +549,10 @@ fun Context.markMessageRead(id: Int, isMMS: Boolean) {
val selection = "${Sms._ID} = ?"
val selectionArgs = arrayOf(id.toString())
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 ->
val contentValues = ContentValues().apply {
put(Sms.READ, 1)
@ -511,9 +562,10 @@ fun Context.markThreadMessagesRead(threadId: Int) {
val selectionArgs = arrayOf(threadId.toString())
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 ->
val contentValues = ContentValues().apply {
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>) {
val unreadCount = conversations.count { !it.read }
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?) {
val privateCursor = getMyContactsCursor().loadInBackground()
fun Context.showReceivedMessageNotification(address: String, body: String, threadId: Long, bitmap: Bitmap?) {
val privateCursor = getMyContactsCursor()?.loadInBackground()
ensureBackgroundThread {
var sender = getNameAndPhotoFromPhoneNumber(address)?.name ?: ""
if (address == sender) {
val privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor)
sender = privateContacts.firstOrNull { it.doesContainPhoneNumber(address) }?.name ?: address
}
val senderName = getNameFromAddress(address, privateCursor)
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")
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 soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
if (isOreoPlus()) {
@ -598,14 +675,14 @@ fun Context.showMessageNotification(address: String, body: String, threadID: Int
}
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 markAsReadIntent = Intent(this, MarkAsReadReceiver::class.java).apply {
action = MARK_AS_READ
putExtra(THREAD_ID, threadID)
putExtra(THREAD_ID, threadId)
}
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()
val replyIntent = Intent(this, DirectReplyReceiver::class.java).apply {
putExtra(THREAD_ID, threadID)
putExtra(THREAD_ID, threadId)
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)
.addRemoteInput(remoteInput)
.build()
@ -632,7 +709,7 @@ fun Context.showMessageNotification(address: String, body: String, threadID: Int
val builder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL)
.setContentTitle(sender)
.setContentText(body)
.setColor(config.primaryColor)
.setColor(getAdjustedPrimaryColor())
.setSmallIcon(R.drawable.ic_messenger)
.setLargeIcon(largeIcon)
.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)
.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)
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_ATTACHMENT_URI = "thread_attachment_uri"
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 NOTIFICATION_CHANNEL = "simple_sms_messenger"
const val SHOW_CHARACTER_COUNTER = "show_character_counter"
private const val PATH = "com.simplemobiletools.smsmessenger.action."
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_ERROR = 4
const val THREAD_SENT_MESSAGE_SUCCESS = 5
const val THREAD_SENT_MESSAGE_SENDING = 6
fun 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")
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")
fun markRead(threadId: Long)
@Query("UPDATE conversations SET read = 0 WHERE thread_id = :threadId")
fun markUnread(threadId: Long)
@Query("DELETE FROM conversations WHERE id = :id")
fun delete(id: Long)
@Query("DELETE FROM conversations WHERE thread_id = :threadId")
fun deleteThreadId(threadId: Long)
}

View File

@ -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
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))])
data class Conversation(
@PrimaryKey(autoGenerate = true) var id: Long?,
@ColumnInfo(name = "thread_id") var thread_id: Int,
@PrimaryKey @ColumnInfo(name = "thread_id") var threadId: Long,
@ColumnInfo(name = "snippet") var snippet: String,
@ColumnInfo(name = "date") var date: Int,
@ColumnInfo(name = "read") var read: Boolean,
@ -16,8 +15,4 @@ data class Conversation(
@ColumnInfo(name = "photo_uri") var photoUri: String,
@ColumnInfo(name = "is_group_conversation") var isGroupConversation: Boolean,
@ColumnInfo(name = "phone_number") var phoneNumber: String
) {
fun getStringToCompare(): String {
return copy(id = 0).toString()
}
}
)

View File

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

View File

@ -1,3 +1,11 @@
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
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
// 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() {
override fun onReceive(context: Context, intent: Intent) {
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 settings = Settings()
@ -31,7 +31,7 @@ class DirectReplyReceiver : BroadcastReceiver() {
val message = com.klinker.android.send_message.Message(msg, address)
try {
transaction.sendNewMessage(message, threadId.toLong())
transaction.sendNewMessage(message, threadId)
} catch (e: Exception) {
context.showErrorToast(e)
}
@ -41,11 +41,11 @@ class DirectReplyReceiver : BroadcastReceiver() {
.setContentText(msg)
.build()
context.notificationManager.notify(threadId, repliedNotification)
context.notificationManager.notify(threadId.hashCode(), repliedNotification)
ensureBackgroundThread {
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) {
when (intent.action) {
MARK_AS_READ -> {
val threadId = intent.getIntExtra(THREAD_ID, 0)
context.notificationManager.cancel(threadId)
val threadId = intent.getLongExtra(THREAD_ID, 0L)
context.notificationManager.cancel(threadId.hashCode())
ensureBackgroundThread {
context.markThreadMessagesRead(threadId)
context.conversationsDB.markRead(threadId.toLong())
context.conversationsDB.markRead(threadId)
context.updateUnreadCountBadge(context.conversationsDB.getUnreadConversations())
}
}

View File

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

View File

@ -3,11 +3,15 @@ package com.simplemobiletools.smsmessenger.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.provider.Telephony
import com.simplemobiletools.commons.extensions.isNumberBlocked
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
import com.simplemobiletools.smsmessenger.models.Message
class SmsReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
@ -21,23 +25,33 @@ class SmsReceiver : BroadcastReceiver() {
val read = 0
val subscriptionId = intent.getIntExtra("subscription", -1)
messages.forEach {
address = it.originatingAddress ?: ""
subject = it.pseudoSubject
body += it.messageBody
date = Math.min(it.timestampMillis, System.currentTimeMillis())
threadId = context.getThreadId(address)
}
ensureBackgroundThread {
messages.forEach {
address = it.originatingAddress ?: ""
subject = it.pseudoSubject
body += it.messageBody
date = Math.min(it.timestampMillis, System.currentTimeMillis())
threadId = context.getThreadId(address)
}
if (!context.isNumberBlocked(address)) {
context.insertNewSMS(address, subject, body, date, read, threadId, type, subscriptionId)
context.showReceivedMessageNotification(address, body, threadId.toInt(), null)
refreshMessages()
Handler(Looper.getMainLooper()).post {
if (!context.isNumberBlocked(address)) {
ensureBackgroundThread {
val newMessageId = context.insertNewSMS(address, subject, body, date, read, threadId, type, subscriptionId)
ensureBackgroundThread {
val conversation = context.getConversations(threadId).firstOrNull() ?: return@ensureBackgroundThread
context.conversationsDB.insertOrUpdate(conversation)
context.updateUnreadCountBadge(context.conversationsDB.getUnreadConversations())
val conversation = context.getConversations(threadId).firstOrNull() ?: return@ensureBackgroundThread
context.conversationsDB.insertOrUpdate(conversation)
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"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<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:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/message_divider_two"
android:layout_below="@+id/suggestions_scrollview"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/bigger_margin"
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
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
android:id="@+id/settings_manage_blocked_numbers_holder"
android:layout_width="match_parent"
@ -149,5 +170,28 @@
android:clickable="false" />
</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>
</ScrollView>

View File

@ -66,6 +66,7 @@
android:layout_marginStart="@dimen/medium_margin"
android:layout_marginEnd="@dimen/medium_margin"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/confirm_selection"
android:paddingStart="@dimen/medium_margin"
android:paddingEnd="@dimen/medium_margin"
android:src="@drawable/ic_check_vector"
@ -125,6 +126,7 @@
android:layout_marginEnd="@dimen/small_margin"
android:alpha="0.9"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/attachment"
android:padding="@dimen/normal_margin"
android:src="@drawable/ic_plus_vector" />
@ -166,14 +168,17 @@
<ImageView
android:id="@+id/thread_select_sim_icon"
android:layout_width="@dimen/normal_icon_size"
android:layout_height="@dimen/normal_icon_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
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:background="?selectableItemBackgroundBorderless"
android:padding="@dimen/normal_margin"
android:background="?attr/selectableItemBackgroundBorderless"
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:visibility="gone" />
@ -190,6 +195,21 @@
android:visibility="gone"
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
android:id="@+id/thread_send_message"
android:layout_width="@dimen/normal_icon_size"
@ -201,6 +221,7 @@
android:alpha="0.4"
android:background="?selectableItemBackgroundBorderless"
android:clickable="false"
android:contentDescription="@string/ok"
android:padding="@dimen/medium_margin"
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_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.7">
app:layout_constraintWidth_percent="0.8">
<ImageView
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_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.7">
app:layout_constraintWidth_percent="0.8">
<LinearLayout
android:id="@+id/thread_mesage_attachments_holder"

View File

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

View File

@ -6,6 +6,6 @@
android:gravity="end"
android:paddingStart="@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: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"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
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
android:id="@+id/cab_add_number_to_contact"
android:icon="@drawable/ic_add_person_vector"
@ -12,13 +17,16 @@
android:title="@string/block_number"
app:showAsAction="ifRoom" />
<item
android:id="@+id/cab_select_all"
android:icon="@drawable/ic_select_all_vector"
android:title="@string/select_all"
android:id="@+id/cab_dial_number"
android:icon="@drawable/ic_phone_vector"
android:title="@string/dial_number"
app:showAsAction="ifRoom" />
<item
android:id="@+id/cab_delete"
android:icon="@drawable/ic_delete_vector"
android:title="@string/delete"
app:showAsAction="ifRoom" />
android:id="@+id/cab_copy_number"
android:title="@string/copy_number_to_clipboard"
app:showAsAction="never" />
<item
android:id="@+id/cab_select_all"
android:title="@string/select_all"
app:showAsAction="never" />
</menu>

View File

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

View File

@ -1,6 +1,11 @@
<?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:showAsAction="always" />
<item
android:id="@+id/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>
<string name="app_name">Schlichter SMS Messenger</string>
<string name="app_launcher_name">SMS Messenger</string>
<string name="type_a_message">schreibe eine Nachricht…</string>
<string name="message_not_sent">Nachricht wurde nicht gesendet.</string>
<string name="type_a_message">Schreibe eine Nachricht…</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="attachment">Anhang</string>
<string name="no_conversations_found">Keine gespeicherten Chats gefunden</string>
<string name="start_conversation">Neuen Chat beginnen</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 -->
<string name="new_conversation">Neuer Chat</string>
@ -42,21 +51,21 @@
<!-- Strings displayed only on Google Playstore. Optional, but good to have -->
<!-- App title has to have less than 50 characters. If you cannot squeeze it, just remove a part of it -->
<string name="app_title">Simple SMS Messenger - Manage messages easily</string>
<string name="app_title">Simple SMS Messenger-Nachrichten simple verwalten</string>
<!-- Short description has to have max 80 characters -->
<string name="app_short_description">An easy and quick way of managing SMS and MMS messages without ads.</string>
<string name="app_short_description">Eine einfache und schnelle Art SMS &amp; MMS ohne Werbung zu verwalten.</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+.
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>Check out the full suite of Simple Tools here:</b>
<b>Entdecke alle Simple Tools hier:</b>
https://www.simplemobiletools.com
<b>Facebook:</b>

View File

@ -2,12 +2,21 @@
<string name="app_name">Απλός SMS Messenger</string>
<string name="app_launcher_name">SMS Messenger</string>
<string name="type_a_message">Πληκτρολογήστε ένα μήνυμα…</string>
<string name="message_not_sent">Το μήνυμα δεν έχει σταλεί.</string>
<string name="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="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">Φόρτωση μηνυμάτων…</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 -->
<string name="new_conversation">Νέα συνομιλία</string>

View File

@ -2,12 +2,21 @@
<string name="app_name">Mensajería SMS Simple</string>
<string name="app_launcher_name">Mensajería SMS</string>
<string name="type_a_message">Escribe un mensaje…</string>
<string name="message_not_sent">El mensaje no se ha enviado.</string>
<string name="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="attachment">Archivo adjunto</string>
<string name="no_conversations_found">No se han encontrado conversaciones</string>
<string name="start_conversation">Inicia una conversación</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 -->
<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_launcher_name">SMS Siuntiklis</string>
<string name="type_a_message">Rašykite žinutę…</string>
<string name="message_not_sent">Žinutė neišsiųsta.</string>
<string name="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="attachment">Priedas</string>
<string name="no_conversations_found">Nebuvo rasta išsaugotų pokalbių</string>
<string name="start_conversation">Pradėtipokalbį</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 -->
<string name="new_conversation">Naujas pokalbis</string>

View File

@ -2,12 +2,21 @@
<string name="app_name">Simple SMS Messenger</string>
<string name="app_launcher_name">SMS മെസഞ്ചർ</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="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>

View File

@ -2,12 +2,21 @@
<string name="app_name">Eenvoudig Berichtenbeheer (SMS)</string>
<string name="app_launcher_name">Berichten</string>
<string name="type_a_message">Typ een bericht…</string>
<string name="message_not_sent">Bericht niet verzonden.</string>
<string name="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="attachment">Bijlage</string>
<string name="no_conversations_found">Geen opgeslagen berichten gevonden</string>
<string name="start_conversation">Een gesprek starten</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 -->
<string name="new_conversation">Nieuw gesprek</string>

View File

@ -2,12 +2,21 @@
<string name="app_name">Simple SMS Messenger</string>
<string name="app_launcher_name">SMS Messenger</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="attachment">Anexo</string>
<string name="no_conversations_found">Não foram encontradas conversas</string>
<string name="start_conversation">Iniciar uma conversa</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 -->
<string name="new_conversation">Nova conversa</string>

View File

@ -2,12 +2,21 @@
<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">Сообщение не отправлено.</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="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">Загрузка сообщений…</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 -->
<string name="new_conversation">Новая переписка</string>
@ -15,13 +24,13 @@
<string name="suggestions">Предложения</string>
<!-- Notifications -->
<string name="channel_received_sms">Получено SMS</string>
<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>
<string name="delete_whole_conversation_confirmation">Удалить все сообщения в этой переписке?</string>
<!-- Are you sure you want to delete 5 conversations? -->
<plurals name="delete_conversations">

View File

@ -2,12 +2,21 @@
<string name="app_name">Jednoduché SMS správy</string>
<string name="app_launcher_name">SMS správy</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="attachment">Príloha</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="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 -->
<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_launcher_name">SMS Messenger</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="attachment">Attachment</string>
<string name="no_conversations_found">No stored conversations have been found</string>
<string name="start_conversation">Start a conversation</string>
<string name="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 -->
<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.
buildscript {
ext.kotlin_version = '1.4.10'
ext.kotlin_version = '1.4.30'
repositories {
google()
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
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
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