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 cda2ade2..dd67cdce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,37 @@ Changelog ========== +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)* ---------------------------- diff --git a/app/build.gradle b/app/build.gradle index 420817fd..2d71ade9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { applicationId "com.simplemobiletools.smsmessenger" minSdkVersion 22 targetSdkVersion 30 - versionCode 18 - versionName "5.5.0" + versionCode 23 + versionName "5.7.0" setProperty("archivesBaseName", "sms-messenger") } @@ -56,13 +56,14 @@ android { } dependencies { - implementation 'com.simplemobiletools:commons:5.31.23' + implementation 'com.simplemobiletools:commons:5.32.19' implementation 'org.greenrobot:eventbus:3.2.0' implementation 'com.klinkerapps:android-smsmms:5.2.6' implementation 'com.github.tibbi:IndicatorFastScroll:08f512858a' 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/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt index 3e57dd0e..58550850 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt @@ -18,10 +18,7 @@ import com.simplemobiletools.commons.models.FAQItem import com.simplemobiletools.smsmessenger.BuildConfig import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.adapters.ConversationsAdapter -import com.simplemobiletools.smsmessenger.extensions.config -import com.simplemobiletools.smsmessenger.extensions.conversationsDB -import com.simplemobiletools.smsmessenger.extensions.getConversations -import com.simplemobiletools.smsmessenger.extensions.updateUnreadCountBadge +import com.simplemobiletools.smsmessenger.extensions.* import com.simplemobiletools.smsmessenger.helpers.THREAD_ID import com.simplemobiletools.smsmessenger.helpers.THREAD_TITLE import com.simplemobiletools.smsmessenger.models.Conversation @@ -86,6 +83,8 @@ class MainActivity : SimpleActivity() { updateTextColors(main_coordinator) no_conversations_placeholder_2.setTextColor(getAdjustedPrimaryColor()) no_conversations_placeholder_2.underlineText() + conversations_fastscroller.updatePrimaryColor() + conversations_fastscroller.updateBubbleColors() checkShortcut() } @@ -183,46 +182,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()) + } + } + } } } @@ -232,17 +228,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) 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 68b9c411..ae831c2e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/NewConversationActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/NewConversationActivity.kt @@ -8,10 +8,7 @@ import android.view.WindowManager import com.google.gson.Gson import com.reddit.indicatorfastscroll.FastScrollItemIndicator 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.SimpleContact import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.adapters.ContactsAdapter @@ -144,7 +141,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) @@ -194,7 +191,7 @@ class NewConversationActivity : SimpleActivity() { 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(numbers).toInt()) + putExtra(THREAD_ID, getThreadId(numbers)) putExtra(THREAD_TITLE, name) putExtra(THREAD_TEXT, text) putExtra(THREAD_NUMBER, number) 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 680c3422..c8d6949e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/SettingsActivity.kt @@ -53,15 +53,16 @@ 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() } } 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 220694fc..43e73d62 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -50,7 +50,7 @@ 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 threadItems = ArrayList() @@ -72,7 +72,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 } @@ -81,7 +81,10 @@ class ThreadActivity : SimpleActivity() { bus!!.register(this) handlePermission(PERMISSION_READ_PHONE_STATE) { if (it) { - setupThread() + setupButtons() + setupCachedMessages { + setupThread() + } } else { finish() } @@ -99,15 +102,16 @@ class ThreadActivity : SimpleActivity() { } private fun setupThread() { - val privateCursor = getMyContactsCursor().loadInBackground() + val privateCursor = getMyContactsCursor()?.loadInBackground() ensureBackgroundThread { + val cachedMessagesCode = messages.hashCode() messages = getMessages(threadId) - participants = if (messages.isEmpty()) { - getThreadParticipants(threadId, null) - } else { - messages.first().participants + if (messages.hashCode() == cachedMessagesCode) { + return@ensureBackgroundThread } + setupParticipants() + // check if no participant came from a privately stored contact in Simple Contacts privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor) if (privateContacts.isNotEmpty()) { @@ -140,50 +144,17 @@ class ThreadActivity : SimpleActivity() { 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) { - } - } + messages.chunked(30).forEach { currentMessages -> + messagesDB.insertMessages(*currentMessages.toTypedArray()) } + setupAttachmentSizes() 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() - } - + setupThreadTitle() setupSIMSelector() } } - setupButtons() } override fun onDestroy() { @@ -224,6 +195,25 @@ 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 setupAdapter() { threadItems = getThreadItems() invalidateOptionsMenu() @@ -290,7 +280,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) @@ -315,6 +305,53 @@ 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 @@ -335,6 +372,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) @@ -430,7 +471,7 @@ class ThreadActivity : SimpleActivity() { private fun markAsUnread() { ensureBackgroundThread { - conversationsDB.markUnread(threadId.toLong()) + conversationsDB.markUnread(threadId) markThreadMessagesUnread(threadId) runOnUiThread { finish() @@ -474,7 +515,7 @@ class ThreadActivity : SimpleActivity() { 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) { @@ -568,6 +609,7 @@ class ThreadActivity : SimpleActivity() { val settings = Settings() settings.useSystemSending = true + settings.deliveryReports = true val SIMId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId if (SIMId != null) { @@ -588,12 +630,14 @@ 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()) + transaction.sendNewMessage(message, threadId) thread_type_message.setText("") attachmentUris.clear() @@ -601,6 +645,8 @@ class ThreadActivity : SimpleActivity() { thread_attachments_wrapper.removeAllViews() } catch (e: Exception) { showErrorToast(e) + } catch (e: Error) { + toast(e.localizedMessage ?: getString(R.string.unknown_error_occurred)) } } @@ -661,10 +707,16 @@ class ThreadActivity : SimpleActivity() { @Subscribe(threadMode = ThreadMode.ASYNC) fun refreshMessages(event: Events.RefreshMessages) { 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 { + messagesDB.insertOrIgnore(it) + } + setupAdapter() } } 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 9bf1df22..83e5814f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt @@ -63,9 +63,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() {} @@ -153,11 +153,11 @@ 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 { @@ -193,7 +193,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) @@ -218,7 +218,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 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 dfef567b..fe750b20 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt @@ -81,9 +81,9 @@ class ThreadAdapter(activity: SimpleActivity, var messages: ArrayList + val messagesToRemove = getSelectedItems() val positions = getSelectedItemPositions() messagesToRemove.forEach { activity.deleteMessage((it as Message).id, it.isMMS) @@ -179,7 +186,7 @@ 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 @@ -192,7 +199,7 @@ class ThreadAdapter(activity: SimpleActivity, var messages: 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 d9590a91..0cd08cd6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt @@ -27,7 +27,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 +44,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,15 +85,15 @@ 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), ArrayList(), ArrayList()) val isMMS = false @@ -100,7 +109,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 +134,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 +152,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 +174,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 +192,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 +215,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 +233,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 +248,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 +266,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 +293,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 +320,7 @@ fun Context.getThreadSnippet(threadId: Int): String { return snippet } -fun Context.getThreadParticipants(threadId: Int, contactsMap: HashMap?): ArrayList { +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,8 +342,8 @@ 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 +417,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()) { @@ -427,7 +444,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 +471,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,10 +485,10 @@ 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()) @@ -484,17 +501,23 @@ fun Context.deleteConversation(threadId: Int) { 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) @@ -503,9 +526,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) @@ -515,9 +539,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) @@ -564,23 +589,23 @@ 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 ?: "" + var sender = getNameAndPhotoFromPhoneNumber(address).name if (address == sender) { val privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor) sender = privateContacts.firstOrNull { it.doesContainPhoneNumber(address) }?.name ?: address } Handler(Looper.getMainLooper()).post { - showMessageNotification(address, body, threadID, bitmap, sender) + showMessageNotification(address, body, threadId, bitmap, 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()) { @@ -602,14 +627,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) @@ -622,11 +647,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() @@ -652,5 +677,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/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..8980593d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/ConversationsDao.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/ConversationsDao.kt @@ -23,9 +23,6 @@ interface ConversationsDao { @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..629c3dfa --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/interfaces/MessagesDao.kt @@ -0,0 +1,37 @@ +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("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("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..a9580099 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") val 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/ThreadError.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadError.kt index 69df786f..9b0f003e 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) : 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 a6e7e6d0..2a613685 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,25 +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) - } - - if (!context.isNumberBlocked(address)) { - ensureBackgroundThread { - context.insertNewSMS(address, subject, body, date, read, threadId, type, subscriptionId) - - val conversation = context.getConversations(threadId).firstOrNull() ?: return@ensureBackgroundThread - context.conversationsDB.insertOrUpdate(conversation) - context.updateUnreadCountBadge(context.conversationsDB.getUnreadConversations()) + ensureBackgroundThread { + messages.forEach { + address = it.originatingAddress ?: "" + subject = it.pseudoSubject + body += it.messageBody + date = Math.min(it.timestampMillis, System.currentTimeMillis()) + threadId = context.getThreadId(address) } - 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) + + 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/res/menu/cab_conversations.xml b/app/src/main/res/menu/cab_conversations.xml index 3d73190f..113b5be4 100644 --- a/app/src/main/res/menu/cab_conversations.xml +++ b/app/src/main/res/menu/cab_conversations.xml @@ -1,6 +1,11 @@ + - diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index fc05292e..520c5be2 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1,7 +1,7 @@ Schlichter SMS Messenger SMS Messenger - schreibe eine Nachricht… + Schreibe eine Nachricht… Nachricht wurde nicht gesendet. Person hinzufügen Anhang @@ -9,6 +9,10 @@ Neuen Chat beginnen Antworten Show a character counter at writing messages + Loading messages… + Sender doesn\'t support replies + Draft + Sending… Neuer Chat diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 7b4d27a0..1132b47a 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -8,7 +8,11 @@ Δεν βρέθηκαν αποθηκευμένες συνομιλίες Έναρξη συνομιλίας Απάντηση - Show a character counter at writing messages + Εμφάνιση μετρητή χαρακτήρων κατά την πληκτρολόγηση μηνυμάτων + Φόρτωση μηνυμάτων… + Sender doesn\'t support replies + Draft + Sending… Νέα συνομιλία diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 0cb67e3e..3066aa3e 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -8,7 +8,11 @@ No se han encontrado conversaciones Inicia una conversación Responder - Show a character counter at writing messages + Mostrar un contador de caracteres al escribir mensajes + Loading messages… + Sender doesn\'t support replies + Draft + Sending… Nueva conversación diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c5316090..552ff722 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1,7 +1,7 @@ Simple SMS Messenger SMS Messenger - Écrivez un message... + Écrivez un message… Le message n\'a pas été envoyé. Ajouter une personne Pièce jointe @@ -9,10 +9,14 @@ Commencer une conversation Répondre Show a character counter at writing messages + Loading messages… + Sender doesn\'t support replies + Draft + Sending… Nouvelle conversation - Ajouter un contact ou un numéro... + Ajouter un contact ou un numéro… Suggestions 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..b7e12b7c --- /dev/null +++ b/app/src/main/res/values-id/strings.xml @@ -0,0 +1,78 @@ + + Simple SMS Messenger + SMS Messenger + Ketik pesan… + Pesan belum terkirim. + 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… + + + 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-ja/strings.xml b/app/src/main/res/values-ja/strings.xml new file mode 100644 index 00000000..df1b2f23 --- /dev/null +++ b/app/src/main/res/values-ja/strings.xml @@ -0,0 +1,76 @@ + + Simple ショートメール + ショートメール + メッセージを入力してください… + 送信されませんでした。 + 宛先を追加 + 添付 + 保存された会話はありません + 会話を始める + 返信 + メッセージ入力中に文字カウントを表示する + Loading messages… + Sender doesn\'t support replies + Draft + Sending… + + + 新しい会話 + 連絡先や電話番号を追加する… + おすすめ + + + 受信したショートメール + 新しいメッセージ + 既読にする + 未読にする + + + 本当にこの会話の全てのメッセージを削除しますか? + + + + %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 45372922..c02ca2a3 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -9,6 +9,10 @@ Pradėtipokalbį Reply Show a character counter at writing messages + Loading messages… + Sender doesn\'t support replies + Draft + Sending… Naujas pokalbis diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index c6b7dffc..5f4eb165 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -9,6 +9,10 @@ ഒരു സംഭാഷണം ആരംഭിക്കുക ഒരു സംഭാഷണം ആരംഭിക്കുക Show a character counter at writing messages + Loading messages… + Sender doesn\'t support replies + Draft + Sending… പുതിയ സംഭാഷണം diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 87c66bf6..f5e7e0a9 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -8,7 +8,11 @@ Geen opgeslagen berichten gevonden Een gesprek starten Beantwoorden - Show a character counter at writing messages + Teller voor het aantal tekens weergeven + Berichten laden… + Sender doesn\'t support replies + Draft + Sending… Nieuw gesprek diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index e91e9a06..c263baf6 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -8,7 +8,11 @@ Não foram encontradas conversas Iniciar uma conversa Responder - Show a character counter at writing messages + Mostrar número de caracteres ao escrever a mensagem + Loading messages… + Sender doesn\'t support replies + Draft + Sending… Nova conversa diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index fa26fc6c..d82b42f4 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -9,6 +9,10 @@ Начать переписку Ответ Показывать счётчик символов при написании сообщений + Loading messages… + Sender doesn\'t support replies + Draft + Sending… Новая переписка diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 48028ba9..89cc373b 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -9,6 +9,10 @@ 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… 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..9e42b548 --- /dev/null +++ b/app/src/main/res/values-tr/strings.xml @@ -0,0 +1,78 @@ + + Basit SMS Messenger + SMS Messenger + Bir mesaj yazın… + Mesaj gönderilmedi. + 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… + + + 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..3b23c2c4 --- /dev/null +++ b/app/src/main/res/values-uk/strings.xml @@ -0,0 +1,78 @@ + + Simple SMS Messenger + Повідомлення + Введіть повідомлення… + Повідомлення не надіслано. + Додати учасник а + Вкладення + Немає збережених листувань + Почати листування + Відповідь + Показувати кількість символів + Loading messages… + Sender doesn\'t support replies + Draft + Sending… + + + Нове листування + Додати контакт аба номер… + Пропозиція + + + Отримано повідомлення + Нове повідомлення + Прочитано + Не прочитано + + + Видалити усі повідомлення у цьому листуванні? + + + + %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 index 947cef63..50d5077c 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -9,6 +9,10 @@ 开始一个对话 回复 Show a character counter at writing messages + Loading messages… + Sender doesn\'t support replies + Draft + Sending… 新的对话 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ce68413e..444dd7f8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,6 +9,10 @@ Start a conversation Reply Show a character counter at writing messages + Loading messages… + Sender doesn\'t support replies + Draft + Sending… New conversation diff --git a/build.gradle b/build.gradle index b55955d2..b604e4eb 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,14 @@ // 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.21' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:4.1.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong 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