diff --git a/.editorconfig b/.editorconfig index 1606d61f..c79c990f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -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 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..9bad22a9 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +github: [tibbi] +patreon: tiborkaputa +custom: ["https://www.paypal.com/paypalme/simplemobiletools", "https://www.simplemobiletools.com/donate"] diff --git a/CHANGELOG.md b/CHANGELOG.md index e332694f..dd463d04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)* ---------------------------- diff --git a/README.md b/README.md index 6ab5c2c8..34019b8a 100644 --- a/README.md +++ b/README.md @@ -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 Get it on Google Play Get it on F-Droid
-App image -App image +App image +App image +App image
diff --git a/app/build.gradle b/app/build.gradle index 7e4c0ba9..e52f8787 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 814fde4a..0dabfa90 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -38,6 +38,21 @@ android:name=".activities.ThreadActivity" android:parentActivityName=".activities.MainActivity" /> + + + + + + + + + @@ -121,6 +136,10 @@ + + + + @@ -149,11 +168,6 @@ android:exported="true" android:taskAffinity="com.klinker.android.messaging.MMS_RECEIVED" /> - - 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 + val conversations = try { + conversationsDB.getAll().sortedByDescending { it.date }.toMutableList() as ArrayList + } catch (e: Exception) { + ArrayList() + } + updateUnreadCountBadge(conversations) runOnUiThread { setupConversations(conversations) @@ -175,46 +184,43 @@ class MainActivity : SimpleActivity() { } private fun getNewConversations(cachedConversations: ArrayList) { - 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) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/NewConversationActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/NewConversationActivity.kt index cc276e4d..88549ae2 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/NewConversationActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/NewConversationActivity.kt @@ -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() + 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(Intent.EXTRA_STREAM) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/SearchActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/SearchActivity.kt new file mode 100644 index 00000000..a3ea9ad5 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/SearchActivity.kt @@ -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, conversations: List, searchedText: String) { + val searchResults = ArrayList() + 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) + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/SettingsActivity.kt index c9a3d38d..5b57f772 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/SettingsActivity.kt @@ -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 + } + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt index 0831549f..2c46ac2f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -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() private var bus: EventBus? = null private var participants = ArrayList() @@ -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() - 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 + 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() + 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() 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() } } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/AutoCompleteTextViewAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/AutoCompleteTextViewAdapter.kt index 445c8294..1f9c20de 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/AutoCompleteTextViewAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/AutoCompleteTextViewAdapter.kt @@ -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) : ArrayAdapter(activity, 0, contacts) { var resultList = ArrayList() @@ -30,8 +34,13 @@ class AutoCompleteTextViewAdapter(val activity: SimpleActivity, val contacts: Ar isFocusable = false } + val backgroundColor = activity.config.backgroundColor findViewById(R.id.item_contact_name).text = contact.name findViewById(R.id.item_contact_number).text = contact.phoneNumbers.first() + findViewById(R.id.item_contact_holder).setBackgroundColor(backgroundColor.darkenColor()) + + findViewById(R.id.item_contact_name).setTextColor(backgroundColor.getContrastColor()) + findViewById(R.id.item_contact_number).setTextColor(backgroundColor.getContrastColor()) SimpleContactsHelper(context).loadContactImage(contact.photoUri, findViewById(R.id.item_contact_image), contact.name) } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt index a1d0a118..ed8f66ae 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt @@ -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 + val conversationsToRemove = conversations.filter { selectedKeys.contains(it.hashCode()) } as ArrayList 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 + private fun getSelectedItems() = conversations.filter { selectedKeys.contains(it.hashCode()) } as ArrayList 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) } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/SearchResultsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/SearchResultsAdapter.kt new file mode 100644 index 00000000..01df81e2 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/SearchResultsAdapter.kt @@ -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, 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, highlightText: String = "") { + if (newItems.hashCode() != searchResults.hashCode()) { + searchResults = newItems.clone() as ArrayList + 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) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt index 73086af1..5590a1d0 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt @@ -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, recyclerView: MyRecyclerView, fastScroller: FastScroller, - itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { +class ThreadAdapter( + activity: SimpleActivity, var messages: ArrayList, recyclerView: MyRecyclerView, fastScroller: FastScroller, + itemClick: (Any) -> Unit +) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { private val roundedCornersRadius = resources.getDimension(R.dimen.normal_margin).toInt() private var fontSize = activity.getTextSize() @@ -58,6 +63,7 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList 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 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 + 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 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 + 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 + private fun getSelectedItems() = messages.filter { selectedKeys.contains((it as? Message)?.hashCode() ?: 0) } as ArrayList 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) { + 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 { } } + .create().apply { + activity.setupDialogStuff(view, this) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/ArrayList.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/ArrayList.kt index 0ff90913..dfaf3202 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/ArrayList.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/ArrayList.kt @@ -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.getThreadTitle() = TextUtils.join(", ", map { it.name }.toTypedArray()) - -fun ArrayList.getHashToCompare() = map { it.getStringToCompare() }.hashCode() diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt index 7c8a734b..e7dfd13c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt @@ -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 { +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 { val uri = Sms.CONTENT_URI val projection = arrayOf( Sms._ID, @@ -76,17 +86,17 @@ fun Context.getMessages(threadId: Int): ArrayList { 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 { } // 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 { +fun Context.getMMS(threadId: Long? = null, sortOrder: String? = null): ArrayList { 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() val contactsMap = HashMap() - val threadParticipants = HashMap>() + val threadParticipants = HashMap>() 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 { +fun Context.getConversations(threadId: Long? = null, privateContacts: ArrayList = ArrayList()): ArrayList { 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 { 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 { 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 { // 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?): ArrayList { +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?): ArrayList { 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): ArrayList { return numbers } -fun Context.getThreadContactNames(phoneNumbers: List): ArrayList { +fun Context.getThreadContactNames(phoneNumbers: List, privateContacts: ArrayList): ArrayList { val names = ArrayList() - 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): 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): 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): 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) { val unreadCount = conversations.count { !it.read } if (unreadCount == 0) { @@ -560,23 +632,28 @@ fun Context.getThreadId(addresses: Set): 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()) } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Config.kt index c5e95e68..451fe60f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Config.kt @@ -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() } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt index d98055db..477c8f9b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt @@ -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()) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Converters.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Converters.kt new file mode 100644 index 00000000..b11cbeb3 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Converters.kt @@ -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>() {}.type + private val simpleContactType = object : TypeToken>() {}.type + private val messageAttachmentType = object : TypeToken() {}.type + + @TypeConverter + fun jsonToAttachmentList(value: String) = gson.fromJson>(value, attachmentType) + + @TypeConverter + fun attachmentListToJson(list: ArrayList) = gson.toJson(list) + + @TypeConverter + fun jsonToSimpleContactList(value: String) = gson.fromJson>(value, simpleContactType) + + @TypeConverter + fun simpleContactListToJson(list: ArrayList) = gson.toJson(list) + + @TypeConverter + fun jsonToMessageAttachment(value: String) = gson.fromJson(value, messageAttachmentType) + + @TypeConverter + fun messageAttachmentToJson(messageAttachment: MessageAttachment?) = gson.toJson(messageAttachment) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/AttachmentsDao.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/AttachmentsDao.kt new file mode 100644 index 00000000..240fa747 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/AttachmentsDao.kt @@ -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 +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/ConversationsDao.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/ConversationsDao.kt index d50507f7..534cdc1f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/ConversationsDao.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/ConversationsDao.kt @@ -17,15 +17,15 @@ interface ConversationsDao { @Query("SELECT * FROM conversations WHERE read = 0") fun getUnreadConversations(): List + @Query("SELECT * FROM conversations WHERE title LIKE :text") + fun getConversationsWithText(text: String): List + @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) } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/MessageAttachmentsDao.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/MessageAttachmentsDao.kt new file mode 100644 index 00000000..8517f80d --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/MessageAttachmentsDao.kt @@ -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 +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/MessagesDao.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/MessagesDao.kt new file mode 100644 index 00000000..c4689b4d --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/MessagesDao.kt @@ -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 + + @Query("SELECT * FROM messages WHERE thread_id = :threadId") + fun getThreadMessages(threadId: Long): List + + @Query("SELECT * FROM messages WHERE body LIKE :text") + fun getMessagesWithText(text: String): List + + @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) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Attachment.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Attachment.kt index f7f4ab7a..4aabd072 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Attachment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Attachment.kt @@ -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) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt index 73e557c6..fc5698f4 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt @@ -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() - } -} +) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt index e18df07c..54d96d04 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt @@ -1,10 +1,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, 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, + @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 } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MessageAttachment.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MessageAttachment.kt index 53f4b94f..a95ea775 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MessageAttachment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MessageAttachment.kt @@ -1,3 +1,11 @@ package com.simplemobiletools.smsmessenger.models -data class MessageAttachment(val id: Int, var text: String, var attachments: ArrayList) +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) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/SearchResult.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/SearchResult.kt new file mode 100644 index 00000000..e6fd47ed --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/SearchResult.kt @@ -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) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadError.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadError.kt index 69df786f..9e32530d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadError.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadError.kt @@ -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() diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadSending.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadSending.kt new file mode 100644 index 00000000..131f7386 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadSending.kt @@ -0,0 +1,3 @@ +package com.simplemobiletools.smsmessenger.models + +data class ThreadSending(val messageId: Long) : ThreadItem() diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadSuccess.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadSuccess.kt index ec7a4d20..fa75eb53 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadSuccess.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadSuccess.kt @@ -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() diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/DirectReplyReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/DirectReplyReceiver.kt index be837509..d4148c86 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/DirectReplyReceiver.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/DirectReplyReceiver.kt @@ -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) } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/MarkAsReadReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/MarkAsReadReceiver.kt index 3ef30ed4..4525f30d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/MarkAsReadReceiver.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/MarkAsReadReceiver.kt @@ -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()) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/MmsReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/MmsReceiver.kt index e8ae7d76..f19a826d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/MmsReceiver.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/MmsReceiver.kt @@ -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()) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/SmsReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/SmsReceiver.kt index 37380eca..cc2efb88 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/SmsReceiver.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/SmsReceiver.kt @@ -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() + } } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/SmsSentReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/SmsSentReceiver.kt deleted file mode 100644 index 2321c414..00000000 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/SmsSentReceiver.kt +++ /dev/null @@ -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() - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/SmsStatusDeliveredReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/SmsStatusDeliveredReceiver.kt new file mode 100644 index 00000000..b4c805e5 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/SmsStatusDeliveredReceiver.kt @@ -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() + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/SmsStatusSentReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/SmsStatusSentReceiver.kt new file mode 100644 index 00000000..0418c743 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/SmsStatusSentReceiver.kt @@ -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()) + } +} diff --git a/app/src/main/res/drawable/item_selected_contact_background.xml b/app/src/main/res/drawable/item_selected_contact_background.xml index f55fbc8f..f0d2b728 100644 --- a/app/src/main/res/drawable/item_selected_contact_background.xml +++ b/app/src/main/res/drawable/item_selected_contact_background.xml @@ -1,9 +1,12 @@ - + + + - + - + - + + + diff --git a/app/src/main/res/layout/activity_new_conversation.xml b/app/src/main/res/layout/activity_new_conversation.xml index f76e807f..aaf95c05 100644 --- a/app/src/main/res/layout/activity_new_conversation.xml +++ b/app/src/main/res/layout/activity_new_conversation.xml @@ -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" diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml new file mode 100644 index 00000000..a8e2fe2d --- /dev/null +++ b/app/src/main/res/layout/activity_search.xml @@ -0,0 +1,46 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index 618b9b70..25fd8250 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -53,6 +53,27 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_thread.xml b/app/src/main/res/layout/activity_thread.xml index 33ec03ed..240c0c39 100644 --- a/app/src/main/res/layout/activity_thread.xml +++ b/app/src/main/res/layout/activity_thread.xml @@ -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 @@ @@ -190,6 +195,21 @@ android:visibility="gone" tools:text="1" /> + + diff --git a/app/src/main/res/layout/dialog_select_text.xml b/app/src/main/res/layout/dialog_select_text.xml new file mode 100644 index 00000000..71f9cba7 --- /dev/null +++ b/app/src/main/res/layout/dialog_select_text.xml @@ -0,0 +1,12 @@ + + diff --git a/app/src/main/res/layout/item_received_message.xml b/app/src/main/res/layout/item_received_message.xml index 7d476239..c36e4b8c 100644 --- a/app/src/main/res/layout/item_received_message.xml +++ b/app/src/main/res/layout/item_received_message.xml @@ -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"> + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_sent_message.xml b/app/src/main/res/layout/item_sent_message.xml index 0a420c0c..3af7a076 100644 --- a/app/src/main/res/layout/item_sent_message.xml +++ b/app/src/main/res/layout/item_sent_message.xml @@ -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"> + android:layout_height="wrap_content" + android:background="?selectableItemBackground"> diff --git a/app/src/main/res/layout/item_thread_sending.xml b/app/src/main/res/layout/item_thread_sending.xml new file mode 100644 index 00000000..b89f4f6a --- /dev/null +++ b/app/src/main/res/layout/item_thread_sending.xml @@ -0,0 +1,10 @@ + + diff --git a/app/src/main/res/menu/cab_conversations.xml b/app/src/main/res/menu/cab_conversations.xml index 84e02dc5..19f7a611 100644 --- a/app/src/main/res/menu/cab_conversations.xml +++ b/app/src/main/res/menu/cab_conversations.xml @@ -1,6 +1,11 @@ + + android:id="@+id/cab_copy_number" + android:title="@string/copy_number_to_clipboard" + app:showAsAction="never" /> + diff --git a/app/src/main/res/menu/cab_thread.xml b/app/src/main/res/menu/cab_thread.xml index ab250efa..ed5b8a85 100644 --- a/app/src/main/res/menu/cab_thread.xml +++ b/app/src/main/res/menu/cab_thread.xml @@ -12,13 +12,16 @@ android:title="@string/share" app:showAsAction="ifRoom" /> + android:id="@+id/cab_select_text" + android:title="@string/select_text" + app:showAsAction="never" /> + diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index a0c32233..adda1b70 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -1,6 +1,11 @@ + + + + diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml new file mode 100644 index 00000000..007b7205 --- /dev/null +++ b/app/src/main/res/values-da/strings.xml @@ -0,0 +1,82 @@ + + Enkel SMS Beskeder + Beskeder + Skriv en besked… + Beskeden blev ikke sendt + Blev ikke sendt, tryk for at gensende + Din besked til \'%s\' blev ikke sendt + Tilføj Person + Vedhæftning + Ingen gemte samtaler er fundet + Start en samtale + Svar + Vis en karaktertæller ved skrivning af beskeder + Henter beskeder… + Afsender understøtter ikke svar + Udkast + Sender… + Eksporter beskeder + Importer beskeder + + + Ny Samtale + Tilføj kontakt eller nummer… + Forslag + + + Modtag SMS + Ny Besked + Marker som læst + Marker som ulæst + + + Er du sikker på, at du vil slette alle beskeder i denne samtale? + + + + %d samtale + %d samtaler + + + + + %d besked + %d beskeder + + + + Hvorfor kræver appen adgang til internettet? + 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. + + + + Simple SMS beskeder - Administrer dine smser nemt + + En nem og hurtig måde at administrere SMS og MMS-beskeder uden annoncer på. + + 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. + + Se hele pakken med enkle værktøjer her: + https://www.simplemobiletools.com + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 2659bceb..eba10927 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1,13 +1,22 @@ Schlichter SMS Messenger SMS Messenger - schreibe eine Nachricht… - Nachricht wurde nicht gesendet. + Schreibe eine Nachricht… + Nachricht nicht versendet. + Nachricht nicht versendet. Berühre, um es erneut zu versuchen. + Deine Nachricht am \'%s\' wurde nicht gesendet. Person hinzufügen Anhang Keine gespeicherten Chats gefunden Neuen Chat beginnen Antworten + Zeige einen Zeichenzähler während dem Schreiben von Nachrichten. + Lade Nachrichten… + Der Absender unterstützt keine Antworten. + Entwurf + Sende… + Export messages + Import messages Neuer Chat @@ -42,21 +51,21 @@ - Simple SMS Messenger - Manage messages easily + Simple SMS Messenger-Nachrichten simple verwalten - An easy and quick way of managing SMS and MMS messages without ads. + Eine einfache und schnelle Art SMS & MMS ohne Werbung zu verwalten. - 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. - - Check out the full suite of Simple Tools here: + Entdecke alle Simple Tools hier: https://www.simplemobiletools.com Facebook: diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index b9b74593..3bb881be 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -2,12 +2,21 @@ Απλός SMS Messenger SMS Messenger Πληκτρολογήστε ένα μήνυμα… - Το μήνυμα δεν έχει σταλεί. + Το μήνυμα δεν εστάλη + Δεν εστάλη. Αγγίξτε για επανάληψη. + Το μήνυμά σας προς το \'%s\' δεν έχει αποσταλεί Προσθήκη ατόμου Συνημμένο Δεν βρέθηκαν αποθηκευμένες συνομιλίες Έναρξη συνομιλίας Απάντηση + Εμφάνιση μετρητή χαρακτήρων κατά την πληκτρολόγηση μηνυμάτων + Φόρτωση μηνυμάτων… + Ο αποστολέας δεν υποστηρίζει απαντήσεις + Πρόχειρο + Γίνεται αποστολή… + Εξαγωγή μηνυμάτων + Εισαγωγή μηνυμάτων Νέα συνομιλία diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 250387ff..479f07a1 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -2,12 +2,21 @@ Mensajería SMS Simple Mensajería SMS Escribe un mensaje… - El mensaje no se ha enviado. + Message not sent + Not sent. Touch to retry. + Your message to \'%s\' has not been sent Añadir persona Archivo adjunto No se han encontrado conversaciones Inicia una conversación Responder + Mostrar un contador de caracteres al escribir mensajes + Loading messages… + Sender doesn\'t support replies + Draft + Sending… + Export messages + Import messages Nueva conversación diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml new file mode 100644 index 00000000..67afb2ea --- /dev/null +++ b/app/src/main/res/values-fi/strings.xml @@ -0,0 +1,82 @@ + + Simple SMS Messenger + Viestit + Lähetä viesti… + Viestiä ei lähetetty + Ei lähetetty. Yritä uudelleen koskemalla. + Viestiäsi henkilölle \'%s\' ei lähetetty. + Lisää henkilö + Liite + Tallennettuja keskusteluja ei löytyny + Aloita keskustelu + Vastaa + Näytä merkkimäärä kirjoittaessasi viestejä + Ladataan viestejä… + Lähettäjä ei tue vastauksia + Luonnos + Lähetetään… + Export messages + Import messages + + + Uusi keskustelu + Lisää yhteystieto tai numero… + Ehdotuksia + + + Vastaanotettu tekstiviesti + Uusi viesti + Merkitse luetuksi + Merkitse lukemattomaksi + + + Haluatko varmasti poistaa kaikki tämän keskustelun viestit? + + + + yhden keskustelun + %d keskustelua + + + + + yhden viestin + %d viestiä + + + + Miksi sovellus vaatii Internet-yhteyden? + 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. + + + + Simple SMS Messenger - Manage messages easily + + An easy and quick way of managing SMS and MMS messages without ads. + + A great way to stay in touch with your relatives, by sending both SMS and MMS messages. The app properly handles group messaging too, just like blocking numbers from Android 7+. + + It offers many date formats to choose from, to make you feel comfortable at using it. You can toggle between 12 and 24 hours time format too. + + It has a really tiny app size compared to the competition, making it really fast to download. + + It comes with material design and dark theme by default, provides great user experience for easy usage. The lack of internet access gives you more privacy, security and stability than other apps. + + Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors. + + Check out the full suite of Simple Tools here: + https://www.simplemobiletools.com + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml new file mode 100644 index 00000000..7e5952e1 --- /dev/null +++ b/app/src/main/res/values-fr/strings.xml @@ -0,0 +1,82 @@ + + Simple SMS Messenger + SMS Messenger + Écrivez un message… + Message not sent + Not sent. Touch to retry. + Your message to \'%s\' has not been sent + Ajouter une personne + Pièce jointe + Aucune conversation enregistrée n\'a été trouvée + Commencer une conversation + Répondre + Show a character counter at writing messages + Loading messages… + Sender doesn\'t support replies + Draft + Sending… + Export messages + Import messages + + + Nouvelle conversation + Ajouter un contact ou un numéro… + Suggestions + + + SMS reçu + Nouveau message + Marquer comme lu + Marquer comme non lu + + + Êtes-vous sûr de vouloir supprimer tous les messages de cette conversation ? + + + + %d conversation + %d conversations + + + + + %d message + %d messages + + + + Pourquoi cette application a besoin d\'un accès à internet ? + 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. + + + + Simple SMS Messenger - Gérez vos messages aisément + + Une façon simple et rapide de gérer ses SMS et MMS sans publicités. + + 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. + + Découvrez la suite complète des applications Simple Mobile Tools ici : + https://www.simplemobiletools.com + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml new file mode 100644 index 00000000..3c77b619 --- /dev/null +++ b/app/src/main/res/values-id/strings.xml @@ -0,0 +1,82 @@ + + Simple SMS Messenger + SMS Messenger + Ketik pesan… + Message not sent + Not sent. Touch to retry. + Your message to \'%s\' has not been sent + Tambahkan Orang + Lampiran + Tidak ada percakapan tersimpan yang ditemukan + Mulailah percakapan + Balas + Tunjukkan penghitung karakter saat menulis pesan + Loading messages… + Sender doesn\'t support replies + Draft + Sending… + Export messages + Import messages + + + Percakapan baru + Tambahkan Kontak atau Nomor… + Saran + + + Menerima SMS + Pesan baru + Tandai sebagai Dibaca + Tandai sebagai Belum dibaca + + + Apakah Anda yakin ingin menghapus semua pesan dari percakapan ini? + + + + %d conversation + %d conversations + + + + + %d pesan + %d pesan + + + + Mengapa aplikasi membutuhkan akses ke internet? + 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. + + + + Simple SMS Messenger - Manage messages easily + + An easy and quick way of managing SMS and MMS messages without ads. + + A great way to stay in touch with your relatives, by sending both SMS and MMS messages. The app properly handles group messaging too, just like blocking numbers from Android 7+. + + It offers many date formats to choose from, to make you feel comfortable at using it. You can toggle between 12 and 24 hours time format too. + + It has a really tiny app size compared to the competition, making it really fast to download. + + It comes with material design and dark theme by default, provides great user experience for easy usage. The lack of internet access gives you more privacy, security and stability than other apps. + + Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors. + + Check out the full suite of Simple Tools here: + https://www.simplemobiletools.com + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml new file mode 100644 index 00000000..150a384e --- /dev/null +++ b/app/src/main/res/values-it/strings.xml @@ -0,0 +1,81 @@ + + SMS Messenger + SMS Messenger + Componi messaggio + Messaggio non inviato + Non inviato. Tocca per riprovare + Il tuo messaggio a \'%s\' non è stato inviato + Aggiungi persona + Allegato + Nessuna conversazione trovata + Inizia conversazione + Rispondi + Mostra contatore caratteri + Caricamento messaggi… + Il mittente non accetta risposte + Bozza + Invio… + Export messages + Import messages + + + Nuova conversazione + Inserisci contatto o numero… + Suggerimenti + + + SMS ricevuto + Nuovo messaggio + Letto + Non letto + + + Vuoi cancellare l\'intera conversazione? + + + + %d conversazione + %d conversazioni + + + + + %d messaggio + %d messaggi + + + + Perché l\'applicazione richiede l\'accesso ad internet? + 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. + + + + Simple SMS Messenger - Manage messages easily + + An easy and quick way of managing SMS and MMS messages without ads. + + A great way to stay in touch with your relatives, by sending both SMS and MMS messages. The app properly handles group messaging too, just like blocking numbers from Android 7+. + + It offers many date formats to choose from, to make you feel comfortable at using it. You can toggle between 12 and 24 hours time format too. + + It has a really tiny app size compared to the competition, making it really fast to download. + + It comes with material design and dark theme by default, provides great user experience for easy usage. The lack of internet access gives you more privacy, security and stability than other apps. + + Neturi reklamų ar nereikalingų leidimų. Programėlė visiškai atviro kodo, yra galimybė keisti spalvas. + Check out the full suite of Simple Tools here: + https://www.simplemobiletools.com + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml new file mode 100644 index 00000000..a0a5cb95 --- /dev/null +++ b/app/src/main/res/values-ja/strings.xml @@ -0,0 +1,80 @@ + + Simple ショートメール + ショートメール + メッセージを入力してください… + Message not sent + Not sent. Touch to retry. + Your message to \'%s\' has not been sent + 宛先を追加 + 添付 + 保存された会話はありません + 会話を始める + 返信 + メッセージ入力中に文字カウントを表示する + Loading messages… + Sender doesn\'t support replies + Draft + Sending… + Export messages + Import messages + + + 新しい会話 + 連絡先や電話番号を追加する… + おすすめ + + + 受信したショートメール + 新しいメッセージ + 既読にする + 未読にする + + + 本当にこの会話の全てのメッセージを削除しますか? + + + + %d件の会話 + %d件の会話 + + + + + %件のメッセージ + %件のメッセージ + + + + なぜアプリ使用にインターネットへのアクセスが必要なのですか? + 生憎、MMS(マルチメディアメッセージサービス)にインターネットが必要となります。他のアプリと比較して、MMSを使用出来ないと大きな損になるので、こうすることに決めました。 + ただし、通常通り広告・追跡・分析は一切行われず、MMS送信にのみインターネットが使われます。 + + + + シンプルなSMSメッセンジャー-メッセージを簡単に管理 + + SMS、MMSメッセージをすばやく送信。広告なしのきれいで美しいカスタマイズ可能なインターフェイス + + SMSやMMSメッセージは親戚と連絡を取るのに便利です。グループメッセージも可能で、アンドロイド7以降のように連絡先をブロックすることも出来ます。 + + 日付フォーマットも複数から使いやすいものを選ぶことができます。日時設定は12時間設定と24時間設定に切り変えることも可能です。 + + 容量が他のアプリと比べて小さいため早くダウンロードができます。 + + 広告や不要な権限はありません。 完全にオープンソースで、カラーもカスタマイズ可能。 + + シンプルツールの完全なリストはこちらからご確認ください: + https://www.simplemobiletools.com + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index d56da48b..1d8cc5e3 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -2,12 +2,21 @@ Paprastas SMS Siuntiklis SMS Siuntiklis Rašykite žinutę… - Žinutė neišsiųsta. + Message not sent + Not sent. Touch to retry. + Your message to \'%s\' has not been sent Pridėti žmogų Priedas Nebuvo rasta išsaugotų pokalbių Pradėtipokalbį Reply + Show a character counter at writing messages + Loading messages… + Sender doesn\'t support replies + Draft + Sending… + Export messages + Import messages Naujas pokalbis diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index a0c2d877..e9d0b8ed 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -2,12 +2,21 @@ Simple SMS Messenger SMS മെസഞ്ചർ മെസ്സേജ് ടൈപ്പ് ചെയ്യുക - മെസ്സേജ് അയച്ചിട്ടില്ല. + Message not sent + Not sent. Touch to retry. + Your message to \'%s\' has not been sent വ്യക്തിയെ ചേർക്കുക അറ്റാച്ചുമെന്റ് സ്റ്റോർ ചെയ്ത സംഭാഷണങ്ങളൊന്നും കണ്ടെത്തിയില്ല ഒരു സംഭാഷണം ആരംഭിക്കുക ഒരു സംഭാഷണം ആരംഭിക്കുക + Show a character counter at writing messages + Loading messages… + Sender doesn\'t support replies + Draft + Sending… + Export messages + Import messages പുതിയ സംഭാഷണം diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 34d570c0..0e2a4ccd 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -2,12 +2,21 @@ Eenvoudig Berichtenbeheer (SMS) Berichten Typ een bericht… - Bericht niet verzonden. + Bericht niet verzonden + Niet verzonden. Probeer het nogmaals. + Bericht naar \'%s\' is niet verzonden Persoon toevoegen Bijlage Geen opgeslagen berichten gevonden Een gesprek starten Beantwoorden + Teller voor het aantal tekens weergeven + Berichten laden… + Afzender ondersteunt geen antwoorden + Concept + Versturen… + Berichten exporteren + Berichten importeren Nieuw gesprek diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 977a1839..3aefaa20 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -2,12 +2,21 @@ Simple SMS Messenger SMS Messenger Escrever uma mensagem… - Mensagem não enviada + Mensagem não enviada + Não enviada. Toque para tentar novamente. + A mensagem enviada para \'%s\' não foi enviada. Adicionar pessoa Anexo Não foram encontradas conversas Iniciar uma conversa Responder + Mostrar número de caracteres ao escrever a mensagem + A carregar mensagens… + O remetente não aceita respostas + Rascunho + A enviar… + Exportar mensagens + Importar mensagens Nova conversa diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index de374dbc..a4ec2cf8 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -2,12 +2,21 @@ Simple SMS Messenger Сообщения Введите сообщение… - Сообщение не отправлено. + Сообщение не отправлено + Не отправлено. Нажмите, чтобы повторить попытку. + Ваше сообщение для \"%s\" не было отправлено Добавить участника Вложение Нет сохранённых переписок Начать переписку Ответ + Показывать счётчик символов при написании сообщений + Загрузка сообщений… + Отправитель не поддерживает ответы + Черновик + Отправка… + Экспорт сообщений + Импорт сообщений Новая переписка @@ -15,13 +24,13 @@ Предложения - Получено SMS + Получено сообщение Новое сообщение Прочитано Не прочитано - Вы уверены, что хотите удалить все сообщения в этой переписке? + Удалить все сообщения в этой переписке? diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 5964e8b0..814a4c0f 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -2,12 +2,21 @@ Jednoduché SMS správy SMS správy Zadajte správu… - Správa nebola odoslaná. + Správa nebola odoslaná + Správa neodoslaná. Dotykom to skúste znova. + Vaša správa pre \'%s\' nebola odoslaná Pridať osobu Príloha Nenašli sa žiadne uložené konverzácie Začať konverzáciu Odpovedať + Zobraziť počítadlo znakov pri písaní správ + Načítanie správ… + Sender doesn\'t support replies + Koncept + Odosiela sa… + Exportovať správy + Importovať správy Nová konverzácia diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml new file mode 100644 index 00000000..f23b8a0d --- /dev/null +++ b/app/src/main/res/values-tr/strings.xml @@ -0,0 +1,82 @@ + + Basit SMS Messenger + SMS Messenger + Bir mesaj yazın… + Message not sent + Not sent. Touch to retry. + Your message to \'%s\' has not been sent + Kişi Ekle + Ek + Kaydedilmiş görüşme bulunamadı + Bir görüşme başlat + Yanıtla + Mesaj yazarken bir karakter sayacı göster + Loading messages… + Sender doesn\'t support replies + Draft + Sending… + Export messages + Import messages + + + Yeni görüşme + Kişi veya Numara Ekle… + Öneriler + + + SMS alındı + Yeni mesaj + Okundu olarak işaretle + Okunmadı olarak işaretle + + + Bu görüşmenin tüm mesajlarını silmek istediğinizden emin misiniz? + + + + %d görüşme + %d görüşme + + + + + %d mesaj + %d mesaj + + + + Uygulama neden internete erişim gerektiriyor? + 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. + + + + Basit SMS Messenger - Mesajları kolayca yönetin + + SMS ve MMS mesajlarını reklamsız yönetmenin kolay ve hızlı bir yolu. + + 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. + + Basit Araçlar paketinin tamamına buradan göz atın: + https://www.simplemobiletools.com + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml new file mode 100644 index 00000000..a2787b8b --- /dev/null +++ b/app/src/main/res/values-uk/strings.xml @@ -0,0 +1,82 @@ + + Simple SMS Messenger + Повідомлення + Введіть повідомлення… + Message not sent + Not sent. Touch to retry. + Your message to \'%s\' has not been sent + Додати учасник а + Вкладення + Немає збережених листувань + Почати листування + Відповідь + Показувати кількість символів + Loading messages… + Sender doesn\'t support replies + Draft + Sending… + Export messages + Import messages + + + Нове листування + Додати контакт аба номер… + Пропозиція + + + Отримано повідомлення + Нове повідомлення + Прочитано + Не прочитано + + + Видалити усі повідомлення у цьому листуванні? + + + + %d листування + %d листувань + + + + + %d повідомлення + %d повідомлень + + + + Чому додаток потрубує доступу до інтернету? + Нажаль, це необхідно для відправки вкладень MMS. Неспроможність надсилати MMS-повідомлення була б великим недоліком нашого додатку порівняно з іншими, тому ми так зробили. + Тим не менше, як і в інших наших додатках, цей не містить реклами, відстеження та аналітики. Інтернет використовується лише для відправки MMS. + + + + Simple SMS Messenger - просте управління SMS + + Простий та швидкий спосіб управління повідомленнями SMS та MMS без реклами. + + SMS/MMS-повідомлення — це чудовий спосіб підтримувати зв\'язок із близькими. Додаток також правильно опрацьовує групові повідомлення та блокування номера на Android 7+. + + Підтримується багато форматів часу, щоб вам було зручно користуватися додатком. Також ви можете вибирати між 12- та 24-годинним форматом часу. + + Наш додаток має малий розмір в порівнянні з додатками конкурентів, що робить його завантаження таким швидким. + + За замовчуванням використовується матеріальний дизайн та темна тема, що забезпечую зручне використання. Відсутність доступу в інтернет забезпечую вам більшу приватність порівняно з іншими додатками. + + Цей додаток не буде показувати рекламу, потрібні лише найнеобхідніші дозволи. Додаток має повністю відкритий програмний код, кольори оформлення можна легко налаштувати. + + Ознакомьтеся з повним набором інструментів Simple тут: + https://www.simplemobiletools.com + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 00000000..36f0de29 --- /dev/null +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,82 @@ + + 简易短信 + 短信 + 输入一个消息… + Message not sent + Not sent. Touch to retry. + Your message to \'%s\' has not been sent + 添加人 + 附件 + 未找到保存的对话 + 开始一个对话 + 回复 + Show a character counter at writing messages + Loading messages… + Sender doesn\'t support replies + Draft + Sending… + Export messages + Import messages + + + 新的对话 + 添加联系人或者号码… + 建议 + + + 接收到的短信 + 新消息 + 标记为已读 + 标记为未读 + + + 您确定您想要删除这个对话的所有消息? + + + + %d 个对话 + %d 个对话 + + + + + %d 个消息 + %d 个消息 + + + + 为什么该应用需要访问互联网? + 很遗憾这对于发送彩信附件是必须的。如果不能发送彩信的话这相比其他应用会是一个巨大的劣势,所以我们决定这么采取现在的方式。 + 但是和其他应用一样,不包含广告、追踪或者任意的分析工具, 互联网访问仅用于发送彩信。 + + + + 简易短信 - 轻松管理消息 + + 无广告的管理短信和彩信的轻松和简便方式。 + + 发送短信和彩信是一个和您亲友保持联系的绝佳方式。这个应用也可以可以正确处理群消息,在 Android 7 以上版本亦可阻止号码。 + + 它提供多个日期格式以便选择,符合您使用习惯。您也可以在 12 和 24 时制格式之间选择。 + + 它相对于其他竞品有着极小的大小,使得其下载非常快。 + + 它遵循质感设计且默认应用黑暗模式,提供便于使用的极佳的用户体验。 + + 不包含广告及非必要的权限,而且完全开放源代码,并提供自定义颜色。 + + 于此查看简易工具系列全套: + https://www.simplemobiletools.com + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml new file mode 100644 index 00000000..24f6140e --- /dev/null +++ b/app/src/main/res/values/donottranslate.xml @@ -0,0 +1,5 @@ + + + com.simplemobiletools.smsmessenger + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 81ac90b0..5cf58c57 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2,12 +2,21 @@ Simple SMS Messenger SMS Messenger Type a message… - Message has not been sent. + Message not sent + Not sent. Touch to retry. + Your message to \'%s\' has not been sent Add Person Attachment No stored conversations have been found Start a conversation Reply + Show a character counter at writing messages + Loading messages… + Sender doesn\'t support replies + Draft + Sending… + Export messages + Import messages New conversation diff --git a/app/src/main/res/xml/searchable.xml b/app/src/main/res/xml/searchable.xml new file mode 100644 index 00000000..892fa25a --- /dev/null +++ b/app/src/main/res/xml/searchable.xml @@ -0,0 +1,5 @@ + + diff --git a/build.gradle b/build.gradle index 52b817f4..29189fd5 100644 --- a/build.gradle +++ b/build.gradle @@ -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() diff --git a/fastlane/metadata/android/en-US/changelogs/17.txt b/fastlane/metadata/android/en-US/changelogs/17.txt new file mode 100644 index 00000000..20f8c7b5 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/17.txt @@ -0,0 +1 @@ + * Fixed some smaller glitches + translation improvements diff --git a/fastlane/metadata/android/en-US/changelogs/18.txt b/fastlane/metadata/android/en-US/changelogs/18.txt new file mode 100644 index 00000000..986a4467 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/18.txt @@ -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 diff --git a/fastlane/metadata/android/en-US/changelogs/19.txt b/fastlane/metadata/android/en-US/changelogs/19.txt new file mode 100644 index 00000000..c0c72c8a --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/19.txt @@ -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 diff --git a/fastlane/metadata/android/en-US/changelogs/20.txt b/fastlane/metadata/android/en-US/changelogs/20.txt new file mode 100644 index 00000000..23576702 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/20.txt @@ -0,0 +1,3 @@ + * Cache messages for better performance + * Add a scrollbar on the main screen + * Adding some stability and translation improvements diff --git a/fastlane/metadata/android/en-US/changelogs/21.txt b/fastlane/metadata/android/en-US/changelogs/21.txt new file mode 100644 index 00000000..e5175d29 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/21.txt @@ -0,0 +1 @@ + * Fixing a crash at devices with multiple SIM cards diff --git a/fastlane/metadata/android/en-US/changelogs/22.txt b/fastlane/metadata/android/en-US/changelogs/22.txt new file mode 100644 index 00000000..42a9f1dd --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/22.txt @@ -0,0 +1 @@ +Fixed messages not being sent in some cases diff --git a/fastlane/metadata/android/en-US/changelogs/23.txt b/fastlane/metadata/android/en-US/changelogs/23.txt new file mode 100644 index 00000000..e2b18ed9 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/23.txt @@ -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 diff --git a/fastlane/metadata/android/en-US/changelogs/25.txt b/fastlane/metadata/android/en-US/changelogs/25.txt new file mode 100644 index 00000000..dcb4ce35 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/25.txt @@ -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 diff --git a/fastlane/metadata/android/en-US/changelogs/26.txt b/fastlane/metadata/android/en-US/changelogs/26.txt new file mode 100644 index 00000000..914590da --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/26.txt @@ -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 diff --git a/fastlane/metadata/android/en-US/changelogs/27.txt b/fastlane/metadata/android/en-US/changelogs/27.txt new file mode 100644 index 00000000..10157d25 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/27.txt @@ -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 diff --git a/fastlane/metadata/android/en-US/changelogs/28.txt b/fastlane/metadata/android/en-US/changelogs/28.txt new file mode 100644 index 00000000..6a73d43f --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/28.txt @@ -0,0 +1 @@ + * Adding some stability and translation improvements diff --git a/fastlane/metadata/android/en-US/changelogs/29.txt b/fastlane/metadata/android/en-US/changelogs/29.txt new file mode 100644 index 00000000..09a9182e --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/29.txt @@ -0,0 +1,3 @@ + * Added Search + * Added a White theme with special handling + * Some stability and translation improvements diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/app_1.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/app_1.jpg deleted file mode 100644 index 6e9ebf4e..00000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/app_1.jpg and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/app_2.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/app_2.jpg deleted file mode 100644 index b66c01c5..00000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/app_2.jpg and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/app_3.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/app_3.jpg deleted file mode 100644 index 6fa74117..00000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/app_3.jpg and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/english/1.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/english/1.jpg new file mode 100644 index 00000000..2c6f62d1 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/english/1.jpg differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/english/2.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/english/2.jpg new file mode 100644 index 00000000..ea78e479 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/english/2.jpg differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/english/3.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/english/3.jpg new file mode 100644 index 00000000..dc5ac2cc Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/english/3.jpg differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/english/4.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/english/4.jpg new file mode 100644 index 00000000..e1ff8478 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/english/4.jpg differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2c011e0d..eed1450f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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