diff --git a/app/src/main/kotlin/org/fossify/messages/App.kt b/app/src/main/kotlin/org/fossify/messages/App.kt index f9b3ebae..1ebacea2 100644 --- a/app/src/main/kotlin/org/fossify/messages/App.kt +++ b/app/src/main/kotlin/org/fossify/messages/App.kt @@ -1,11 +1,7 @@ package org.fossify.messages -import android.app.Application -import org.fossify.commons.extensions.checkUseEnglish +import org.fossify.commons.FossifyApp -class App : Application() { - override fun onCreate() { - super.onCreate() - checkUseEnglish() - } +class App : FossifyApp() { + override val isAppLockFeatureAvailable = true } diff --git a/app/src/main/kotlin/org/fossify/messages/activities/ArchivedConversationsActivity.kt b/app/src/main/kotlin/org/fossify/messages/activities/ArchivedConversationsActivity.kt index 6cfb9705..bab58a15 100644 --- a/app/src/main/kotlin/org/fossify/messages/activities/ArchivedConversationsActivity.kt +++ b/app/src/main/kotlin/org/fossify/messages/activities/ArchivedConversationsActivity.kt @@ -6,7 +6,6 @@ import android.os.Bundle import org.fossify.commons.dialogs.ConfirmationDialog import org.fossify.commons.extensions.* import org.fossify.commons.helpers.NavigationIcon -import org.fossify.commons.helpers.WAS_PROTECTION_HANDLED import org.fossify.commons.helpers.ensureBackgroundThread import org.fossify.messages.R import org.fossify.messages.adapters.ArchivedConversationsAdapter @@ -165,7 +164,6 @@ class ArchivedConversationsActivity : SimpleActivity() { val conversation = any as Conversation putExtra(THREAD_ID, conversation.threadId) putExtra(THREAD_TITLE, conversation.title) - putExtra(WAS_PROTECTION_HANDLED, true) startActivity(this) } } diff --git a/app/src/main/kotlin/org/fossify/messages/activities/MainActivity.kt b/app/src/main/kotlin/org/fossify/messages/activities/MainActivity.kt index 1c3c39c0..4e2c2061 100644 --- a/app/src/main/kotlin/org/fossify/messages/activities/MainActivity.kt +++ b/app/src/main/kotlin/org/fossify/messages/activities/MainActivity.kt @@ -11,10 +11,46 @@ import android.graphics.drawable.LayerDrawable import android.os.Bundle import android.provider.Telephony import android.text.TextUtils +import androidx.appcompat.content.res.AppCompatResources import androidx.coordinatorlayout.widget.CoordinatorLayout import org.fossify.commons.dialogs.PermissionRequiredDialog -import org.fossify.commons.extensions.* -import org.fossify.commons.helpers.* +import org.fossify.commons.extensions.adjustAlpha +import org.fossify.commons.extensions.appLaunched +import org.fossify.commons.extensions.appLockManager +import org.fossify.commons.extensions.applyColorFilter +import org.fossify.commons.extensions.areSystemAnimationsEnabled +import org.fossify.commons.extensions.beGone +import org.fossify.commons.extensions.beGoneIf +import org.fossify.commons.extensions.beVisible +import org.fossify.commons.extensions.beVisibleIf +import org.fossify.commons.extensions.checkAppSideloading +import org.fossify.commons.extensions.checkWhatsNew +import org.fossify.commons.extensions.convertToBitmap +import org.fossify.commons.extensions.fadeIn +import org.fossify.commons.extensions.formatDateOrTime +import org.fossify.commons.extensions.getMyContactsCursor +import org.fossify.commons.extensions.getProperBackgroundColor +import org.fossify.commons.extensions.getProperPrimaryColor +import org.fossify.commons.extensions.getProperTextColor +import org.fossify.commons.extensions.hideKeyboard +import org.fossify.commons.extensions.navigationBarHeight +import org.fossify.commons.extensions.openNotificationSettings +import org.fossify.commons.extensions.toast +import org.fossify.commons.extensions.underlineText +import org.fossify.commons.extensions.updateTextColors +import org.fossify.commons.extensions.viewBinding +import org.fossify.commons.helpers.LICENSE_EVENT_BUS +import org.fossify.commons.helpers.LICENSE_INDICATOR_FAST_SCROLL +import org.fossify.commons.helpers.LICENSE_SMS_MMS +import org.fossify.commons.helpers.LOWER_ALPHA +import org.fossify.commons.helpers.MyContactsContentProvider +import org.fossify.commons.helpers.PERMISSION_READ_CONTACTS +import org.fossify.commons.helpers.PERMISSION_READ_SMS +import org.fossify.commons.helpers.PERMISSION_SEND_SMS +import org.fossify.commons.helpers.SHORT_ANIMATION_DURATION +import org.fossify.commons.helpers.ensureBackgroundThread +import org.fossify.commons.helpers.isNougatMR1Plus +import org.fossify.commons.helpers.isQPlus import org.fossify.commons.models.FAQItem import org.fossify.commons.models.Release import org.fossify.messages.BuildConfig @@ -22,7 +58,16 @@ import org.fossify.messages.R import org.fossify.messages.adapters.ConversationsAdapter import org.fossify.messages.adapters.SearchResultsAdapter import org.fossify.messages.databinding.ActivityMainBinding -import org.fossify.messages.extensions.* +import org.fossify.messages.extensions.checkAndDeleteOldRecycleBinMessages +import org.fossify.messages.extensions.clearAllMessagesIfNeeded +import org.fossify.messages.extensions.clearExpiredScheduledMessages +import org.fossify.messages.extensions.config +import org.fossify.messages.extensions.conversationsDB +import org.fossify.messages.extensions.getConversations +import org.fossify.messages.extensions.getMessages +import org.fossify.messages.extensions.insertOrUpdateConversation +import org.fossify.messages.extensions.messagesDB +import org.fossify.messages.extensions.updateUnreadCountBadge import org.fossify.messages.helpers.SEARCHED_MESSAGE_ID import org.fossify.messages.helpers.THREAD_ID import org.fossify.messages.helpers.THREAD_TITLE @@ -41,7 +86,6 @@ class MainActivity : SimpleActivity() { private var storedFontSize = 0 private var lastSearchedText = "" private var bus: EventBus? = null - private var wasProtectionHandled = false private val binding by viewBinding(ActivityMainBinding::inflate) @@ -63,15 +107,8 @@ class MainActivity : SimpleActivity() { if (savedInstanceState == null) { checkAndDeleteOldRecycleBinMessages() - handleAppPasswordProtection { - wasProtectionHandled = it - if (it) { - clearAllMessagesIfNeeded { - loadMessages() - } - } else { - finish() - } + clearAllMessagesIfNeeded { + loadMessages() } } @@ -108,7 +145,8 @@ class MainActivity : SimpleActivity() { binding.conversationsProgressBar.trackColor = properPrimaryColor.adjustAlpha(LOWER_ALPHA) checkShortcut() (binding.conversationsFab.layoutParams as? CoordinatorLayout.LayoutParams)?.bottomMargin = - navigationBarHeight + resources.getDimension(org.fossify.commons.R.dimen.activity_margin).toInt() + navigationBarHeight + resources.getDimension(org.fossify.commons.R.dimen.activity_margin) + .toInt() } override fun onPause() { @@ -125,33 +163,11 @@ class MainActivity : SimpleActivity() { if (binding.mainMenu.isSearchOpen) { binding.mainMenu.closeSearch() } else { + appLockManager.lock() super.onBackPressed() } } - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putBoolean(WAS_PROTECTION_HANDLED, wasProtectionHandled) - } - - override fun onRestoreInstanceState(savedInstanceState: Bundle) { - super.onRestoreInstanceState(savedInstanceState) - wasProtectionHandled = savedInstanceState.getBoolean(WAS_PROTECTION_HANDLED, false) - - if (!wasProtectionHandled) { - handleAppPasswordProtection { - wasProtectionHandled = it - if (it) { - loadMessages() - } else { - finish() - } - } - } else { - loadMessages() - } - } - private fun setupOptionsMenu() { binding.mainMenu.getToolbar().inflateMenu(R.menu.menu_main) binding.mainMenu.toggleHideOnScroll(true) @@ -332,7 +348,8 @@ class MainActivity : SimpleActivity() { conversationsDB.deleteThreadId(threadId) } - val newConversation = conversations.find { it.phoneNumber == cachedConversation.phoneNumber } + val newConversation = + conversations.find { it.phoneNumber == cachedConversation.phoneNumber } if (isTemporaryThread && newConversation != null) { // delete the original temporary thread and move any scheduled messages to the new thread conversationsDB.deleteThreadId(threadId) @@ -346,7 +363,9 @@ class MainActivity : SimpleActivity() { cachedConversations.forEach { cachedConv -> val conv = conversations.find { - it.threadId == cachedConv.threadId && !Conversation.areContentsTheSame(cachedConv, it) + it.threadId == cachedConv.threadId && !Conversation.areContentsTheSame( + old = cachedConv, new = it + ) } if (conv != null) { val lastModified = maxOf(cachedConv.date, conv.date) @@ -362,7 +381,11 @@ class MainActivity : SimpleActivity() { if (config.appRunCount == 1) { conversations.map { it.threadId }.forEach { threadId -> - val messages = getMessages(threadId, getImageResolutions = false, includeScheduledMessages = false) + val messages = getMessages( + threadId = threadId, + getImageResolutions = false, + includeScheduledMessages = false + ) messages.chunked(30).forEach { currentMessages -> messagesDB.insertMessages(*currentMessages.toTypedArray()) } @@ -390,7 +413,10 @@ class MainActivity : SimpleActivity() { return currAdapter as ConversationsAdapter } - private fun setupConversations(conversations: ArrayList, cached: Boolean = false) { + private fun setupConversations( + conversations: ArrayList, + cached: Boolean = false + ) { val sortedConversations = conversations.sortedWith( compareByDescending { config.pinnedConversations.contains(it.threadId.toString()) } .thenByDescending { it.date } @@ -435,10 +461,13 @@ class MainActivity : SimpleActivity() { } private fun fadeOutSearch() { - binding.searchHolder.animate().alpha(0f).setDuration(SHORT_ANIMATION_DURATION).withEndAction { - binding.searchHolder.beGone() - searchTextChanged("", true) - }.start() + binding.searchHolder.animate() + .alpha(0f) + .setDuration(SHORT_ANIMATION_DURATION) + .withEndAction { + binding.searchHolder.beGone() + searchTextChanged("", true) + }.start() } @SuppressLint("NotifyDataSetChanged") @@ -451,7 +480,6 @@ class MainActivity : SimpleActivity() { val conversation = any as Conversation putExtra(THREAD_ID, conversation.threadId) putExtra(THREAD_TITLE, conversation.title) - putExtra(WAS_PROTECTION_HANDLED, wasProtectionHandled) startActivity(this) } } @@ -481,8 +509,13 @@ class MainActivity : SimpleActivity() { @SuppressLint("NewApi") private fun getCreateNewContactShortcut(appIconColor: Int): ShortcutInfo { val newEvent = getString(R.string.new_conversation) - val drawable = resources.getDrawable(org.fossify.commons.R.drawable.shortcut_plus) - (drawable as LayerDrawable).findDrawableByLayerId(org.fossify.commons.R.id.shortcut_plus_background).applyColorFilter(appIconColor) + val drawable = + AppCompatResources.getDrawable(this, org.fossify.commons.R.drawable.shortcut_plus) + + (drawable as LayerDrawable).findDrawableByLayerId( + org.fossify.commons.R.id.shortcut_plus_background + ).applyColorFilter(appIconColor) + val bmp = drawable.convertToBitmap() val intent = Intent(this, NewConversationActivity::class.java) @@ -517,11 +550,27 @@ class MainActivity : SimpleActivity() { } } - private fun showSearchResults(messages: List, conversations: List, searchedText: String) { + 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) + val date = (conversation.date * 1000L).formatDateOrTime( + context = this, + hideTimeOnOtherDays = true, + showCurrentYear = true + ) + + val searchResult = SearchResult( + messageId = -1, + title = conversation.title, + snippet = conversation.phoneNumber, + date = date, + threadId = conversation.threadId, + photoUri = conversation.photoUri + ) searchResults.add(searchResult) } @@ -532,8 +581,20 @@ class MainActivity : SimpleActivity() { 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) + val date = (message.date * 1000L).formatDateOrTime( + context = this, + hideTimeOnOtherDays = true, + showCurrentYear = true + ) + + val searchResult = SearchResult( + messageId = message.id, + title = recipient, + snippet = message.body, + date = date, + threadId = message.threadId, + photoUri = message.senderPhotoUri + ) searchResults.add(searchResult) } @@ -579,17 +640,36 @@ class MainActivity : SimpleActivity() { val licenses = LICENSE_EVENT_BUS or LICENSE_SMS_MMS or LICENSE_INDICATOR_FAST_SCROLL val faqItems = arrayListOf( - FAQItem(R.string.faq_2_title, R.string.faq_2_text), - FAQItem(R.string.faq_3_title, R.string.faq_3_text), - FAQItem(org.fossify.commons.R.string.faq_9_title_commons, org.fossify.commons.R.string.faq_9_text_commons) + FAQItem(title = R.string.faq_2_title, text = R.string.faq_2_text), + FAQItem(title = R.string.faq_3_title, text = R.string.faq_3_text), + FAQItem( + title = org.fossify.commons.R.string.faq_9_title_commons, + text = org.fossify.commons.R.string.faq_9_text_commons + ) ) if (!resources.getBoolean(org.fossify.commons.R.bool.hide_google_relations)) { - faqItems.add(FAQItem(org.fossify.commons.R.string.faq_2_title_commons, org.fossify.commons.R.string.faq_2_text_commons)) - faqItems.add(FAQItem(org.fossify.commons.R.string.faq_6_title_commons, org.fossify.commons.R.string.faq_6_text_commons)) + faqItems.add( + FAQItem( + title = org.fossify.commons.R.string.faq_2_title_commons, + text = org.fossify.commons.R.string.faq_2_text_commons + ) + ) + faqItems.add( + FAQItem( + title = org.fossify.commons.R.string.faq_6_title_commons, + text = org.fossify.commons.R.string.faq_6_text_commons + ) + ) } - startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true) + startAboutActivity( + appNameId = R.string.app_name, + licenseMask = licenses, + versionName = BuildConfig.VERSION_NAME, + faqItems = faqItems, + showFAQBeforeMail = true + ) } @Subscribe(threadMode = ThreadMode.MAIN) diff --git a/app/src/main/kotlin/org/fossify/messages/activities/ManageBlockedKeywordsActivity.kt b/app/src/main/kotlin/org/fossify/messages/activities/ManageBlockedKeywordsActivity.kt index b698e126..29c79cae 100644 --- a/app/src/main/kotlin/org/fossify/messages/activities/ManageBlockedKeywordsActivity.kt +++ b/app/src/main/kotlin/org/fossify/messages/activities/ManageBlockedKeywordsActivity.kt @@ -6,7 +6,6 @@ import android.net.Uri import android.os.Bundle import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts -import org.fossify.commons.activities.BaseSimpleActivity import org.fossify.commons.dialogs.ExportBlockedNumbersDialog import org.fossify.commons.dialogs.FilePickerDialog import org.fossify.commons.extensions.beVisibleIf @@ -19,8 +18,6 @@ import org.fossify.commons.extensions.toast import org.fossify.commons.extensions.underlineText import org.fossify.commons.extensions.updateTextColors import org.fossify.commons.extensions.viewBinding -import org.fossify.commons.helpers.APP_ICON_IDS -import org.fossify.commons.helpers.APP_LAUNCHER_NAME import org.fossify.commons.helpers.ExportResult import org.fossify.commons.helpers.NavigationIcon import org.fossify.commons.helpers.PERMISSION_READ_STORAGE @@ -40,11 +37,7 @@ import org.fossify.messages.helpers.BlockedKeywordsImporter import java.io.FileOutputStream import java.io.OutputStream -class ManageBlockedKeywordsActivity : BaseSimpleActivity(), RefreshRecyclerViewListener { - - override fun getAppIconIDs() = intent.getIntegerArrayListExtra(APP_ICON_IDS) ?: ArrayList() - - override fun getAppLauncherName() = intent.getStringExtra(APP_LAUNCHER_NAME) ?: "" +class ManageBlockedKeywordsActivity : SimpleActivity(), RefreshRecyclerViewListener { private val binding by viewBinding(ActivityManageBlockedKeywordsBinding::inflate) @@ -131,10 +124,11 @@ class ManageBlockedKeywordsActivity : BaseSimpleActivity(), RefreshRecyclerViewL if (isQPlus()) { Intent(Intent.ACTION_GET_CONTENT).apply { addCategory(Intent.CATEGORY_OPENABLE) - type = "text/plain" + val mimeType = "text/plain" + type = mimeType try { - importActivityResultLauncher.launch(type) + importActivityResultLauncher.launch(mimeType) } catch (e: ActivityNotFoundException) { toast(org.fossify.commons.R.string.system_service_disabled, Toast.LENGTH_LONG) } catch (e: Exception) { diff --git a/app/src/main/kotlin/org/fossify/messages/activities/RecycleBinConversationsActivity.kt b/app/src/main/kotlin/org/fossify/messages/activities/RecycleBinConversationsActivity.kt index bb9f751a..78bf71fa 100644 --- a/app/src/main/kotlin/org/fossify/messages/activities/RecycleBinConversationsActivity.kt +++ b/app/src/main/kotlin/org/fossify/messages/activities/RecycleBinConversationsActivity.kt @@ -4,9 +4,13 @@ import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle import org.fossify.commons.dialogs.ConfirmationDialog -import org.fossify.commons.extensions.* +import org.fossify.commons.extensions.areSystemAnimationsEnabled +import org.fossify.commons.extensions.beGoneIf +import org.fossify.commons.extensions.beVisibleIf +import org.fossify.commons.extensions.getProperBackgroundColor +import org.fossify.commons.extensions.hideKeyboard +import org.fossify.commons.extensions.viewBinding import org.fossify.commons.helpers.NavigationIcon -import org.fossify.commons.helpers.WAS_PROTECTION_HANDLED import org.fossify.commons.helpers.ensureBackgroundThread import org.fossify.messages.R import org.fossify.messages.adapters.RecycleBinConversationsAdapter @@ -40,7 +44,10 @@ class RecycleBinConversationsActivity : SimpleActivity() { useTransparentNavigation = true, useTopSearchMenu = false ) - setupMaterialScrollListener(scrollingView = binding.conversationsList, toolbar = binding.recycleBinToolbar) + setupMaterialScrollListener( + scrollingView = binding.conversationsList, + toolbar = binding.recycleBinToolbar + ) loadRecycleBinConversations() } @@ -82,7 +89,8 @@ class RecycleBinConversationsActivity : SimpleActivity() { private fun loadRecycleBinConversations() { ensureBackgroundThread { val conversations = try { - conversationsDB.getAllWithMessagesInRecycleBin().toMutableList() as ArrayList + conversationsDB.getAllWithMessagesInRecycleBin() + .toMutableList() as ArrayList } catch (e: Exception) { ArrayList() } @@ -166,7 +174,6 @@ class RecycleBinConversationsActivity : SimpleActivity() { val conversation = any as Conversation putExtra(THREAD_ID, conversation.threadId) putExtra(THREAD_TITLE, conversation.title) - putExtra(WAS_PROTECTION_HANDLED, true) putExtra(IS_RECYCLE_BIN, true) startActivity(this) } diff --git a/app/src/main/kotlin/org/fossify/messages/activities/SimpleActivity.kt b/app/src/main/kotlin/org/fossify/messages/activities/SimpleActivity.kt index 56e12c13..ae2f7c75 100644 --- a/app/src/main/kotlin/org/fossify/messages/activities/SimpleActivity.kt +++ b/app/src/main/kotlin/org/fossify/messages/activities/SimpleActivity.kt @@ -27,4 +27,6 @@ open class SimpleActivity : BaseSimpleActivity() { ) override fun getAppLauncherName() = getString(R.string.app_launcher_name) + + override fun getRepositoryName() = "Messages" } diff --git a/app/src/main/kotlin/org/fossify/messages/activities/ThreadActivity.kt b/app/src/main/kotlin/org/fossify/messages/activities/ThreadActivity.kt index 4c41e1c5..6d5f91f9 100644 --- a/app/src/main/kotlin/org/fossify/messages/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/org/fossify/messages/activities/ThreadActivity.kt @@ -36,9 +36,14 @@ import android.widget.LinearLayout.LayoutParams import android.widget.RelativeLayout import android.widget.Toast import androidx.annotation.StringRes +import androidx.appcompat.content.res.AppCompatResources import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.res.ResourcesCompat -import androidx.core.view.* +import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsAnimationCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updateLayoutParams import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.gson.Gson @@ -47,8 +52,52 @@ import org.fossify.commons.dialogs.ConfirmationDialog import org.fossify.commons.dialogs.FeatureLockedDialog import org.fossify.commons.dialogs.PermissionRequiredDialog import org.fossify.commons.dialogs.RadioGroupDialog -import org.fossify.commons.extensions.* -import org.fossify.commons.helpers.* +import org.fossify.commons.extensions.addBlockedNumber +import org.fossify.commons.extensions.addLockedLabelIfNeeded +import org.fossify.commons.extensions.applyColorFilter +import org.fossify.commons.extensions.beGone +import org.fossify.commons.extensions.beVisible +import org.fossify.commons.extensions.beVisibleIf +import org.fossify.commons.extensions.darkenColor +import org.fossify.commons.extensions.formatDate +import org.fossify.commons.extensions.getBottomNavigationBackgroundColor +import org.fossify.commons.extensions.getContrastColor +import org.fossify.commons.extensions.getFilenameFromUri +import org.fossify.commons.extensions.getMyContactsCursor +import org.fossify.commons.extensions.getMyFileUri +import org.fossify.commons.extensions.getProperBackgroundColor +import org.fossify.commons.extensions.getProperPrimaryColor +import org.fossify.commons.extensions.getProperTextColor +import org.fossify.commons.extensions.getTextSize +import org.fossify.commons.extensions.hideKeyboard +import org.fossify.commons.extensions.isDynamicTheme +import org.fossify.commons.extensions.isOrWasThankYouInstalled +import org.fossify.commons.extensions.isVisible +import org.fossify.commons.extensions.launchActivityIntent +import org.fossify.commons.extensions.normalizeString +import org.fossify.commons.extensions.notificationManager +import org.fossify.commons.extensions.onTextChangeListener +import org.fossify.commons.extensions.openRequestExactAlarmSettings +import org.fossify.commons.extensions.realScreenSize +import org.fossify.commons.extensions.showErrorToast +import org.fossify.commons.extensions.showKeyboard +import org.fossify.commons.extensions.toInt +import org.fossify.commons.extensions.toast +import org.fossify.commons.extensions.updateTextColors +import org.fossify.commons.extensions.value +import org.fossify.commons.extensions.viewBinding +import org.fossify.commons.helpers.ContactsHelper +import org.fossify.commons.helpers.ExportResult +import org.fossify.commons.helpers.KEY_PHONE +import org.fossify.commons.helpers.MyContactsContentProvider +import org.fossify.commons.helpers.NavigationIcon +import org.fossify.commons.helpers.PERMISSION_READ_PHONE_STATE +import org.fossify.commons.helpers.SimpleContactsHelper +import org.fossify.commons.helpers.VcfExporter +import org.fossify.commons.helpers.ensureBackgroundThread +import org.fossify.commons.helpers.isNougatPlus +import org.fossify.commons.helpers.isOreoPlus +import org.fossify.commons.helpers.isSPlus import org.fossify.commons.models.PhoneNumber import org.fossify.commons.models.RadioItem import org.fossify.commons.models.SimpleContact @@ -63,11 +112,80 @@ import org.fossify.messages.databinding.ItemSelectedContactBinding import org.fossify.messages.dialogs.InvalidNumberDialog import org.fossify.messages.dialogs.RenameConversationDialog import org.fossify.messages.dialogs.ScheduleMessageDialog -import org.fossify.messages.extensions.* -import org.fossify.messages.helpers.* -import org.fossify.messages.messaging.* -import org.fossify.messages.models.* -import org.fossify.messages.models.ThreadItem.* +import org.fossify.messages.extensions.clearExpiredScheduledMessages +import org.fossify.messages.extensions.config +import org.fossify.messages.extensions.conversationsDB +import org.fossify.messages.extensions.createTemporaryThread +import org.fossify.messages.extensions.deleteConversation +import org.fossify.messages.extensions.deleteMessage +import org.fossify.messages.extensions.deleteScheduledMessage +import org.fossify.messages.extensions.deleteSmsDraft +import org.fossify.messages.extensions.dialNumber +import org.fossify.messages.extensions.emptyMessagesRecycleBinForConversation +import org.fossify.messages.extensions.getAddresses +import org.fossify.messages.extensions.getDefaultKeyboardHeight +import org.fossify.messages.extensions.getFileSizeFromUri +import org.fossify.messages.extensions.getMessages +import org.fossify.messages.extensions.getSmsDraft +import org.fossify.messages.extensions.getThreadId +import org.fossify.messages.extensions.getThreadParticipants +import org.fossify.messages.extensions.getThreadTitle +import org.fossify.messages.extensions.indexOfFirstOrNull +import org.fossify.messages.extensions.isGifMimeType +import org.fossify.messages.extensions.isImageMimeType +import org.fossify.messages.extensions.markMessageRead +import org.fossify.messages.extensions.markThreadMessagesUnread +import org.fossify.messages.extensions.messagesDB +import org.fossify.messages.extensions.moveMessageToRecycleBin +import org.fossify.messages.extensions.removeDiacriticsIfNeeded +import org.fossify.messages.extensions.renameConversation +import org.fossify.messages.extensions.restoreAllMessagesFromRecycleBinForConversation +import org.fossify.messages.extensions.restoreMessageFromRecycleBin +import org.fossify.messages.extensions.saveSmsDraft +import org.fossify.messages.extensions.showWithAnimation +import org.fossify.messages.extensions.subscriptionManagerCompat +import org.fossify.messages.extensions.toArrayList +import org.fossify.messages.extensions.updateConversationArchivedStatus +import org.fossify.messages.extensions.updateLastConversationMessage +import org.fossify.messages.extensions.updateScheduledMessagesThreadId +import org.fossify.messages.helpers.CAPTURE_AUDIO_INTENT +import org.fossify.messages.helpers.CAPTURE_PHOTO_INTENT +import org.fossify.messages.helpers.CAPTURE_VIDEO_INTENT +import org.fossify.messages.helpers.FILE_SIZE_NONE +import org.fossify.messages.helpers.IS_RECYCLE_BIN +import org.fossify.messages.helpers.MESSAGES_LIMIT +import org.fossify.messages.helpers.PICK_CONTACT_INTENT +import org.fossify.messages.helpers.PICK_DOCUMENT_INTENT +import org.fossify.messages.helpers.PICK_PHOTO_INTENT +import org.fossify.messages.helpers.PICK_SAVE_FILE_INTENT +import org.fossify.messages.helpers.PICK_VIDEO_INTENT +import org.fossify.messages.helpers.SEARCHED_MESSAGE_ID +import org.fossify.messages.helpers.THREAD_ATTACHMENT_URI +import org.fossify.messages.helpers.THREAD_ATTACHMENT_URIS +import org.fossify.messages.helpers.THREAD_ID +import org.fossify.messages.helpers.THREAD_NUMBER +import org.fossify.messages.helpers.THREAD_TEXT +import org.fossify.messages.helpers.THREAD_TITLE +import org.fossify.messages.helpers.generateRandomId +import org.fossify.messages.helpers.refreshMessages +import org.fossify.messages.messaging.cancelScheduleSendPendingIntent +import org.fossify.messages.messaging.isLongMmsMessage +import org.fossify.messages.messaging.isShortCodeWithLetters +import org.fossify.messages.messaging.scheduleMessage +import org.fossify.messages.messaging.sendMessageCompat +import org.fossify.messages.models.Attachment +import org.fossify.messages.models.AttachmentSelection +import org.fossify.messages.models.Conversation +import org.fossify.messages.models.Events +import org.fossify.messages.models.Message +import org.fossify.messages.models.MessageAttachment +import org.fossify.messages.models.SIMCard +import org.fossify.messages.models.ThreadItem +import org.fossify.messages.models.ThreadItem.ThreadDateTime +import org.fossify.messages.models.ThreadItem.ThreadError +import org.fossify.messages.models.ThreadItem.ThreadLoading +import org.fossify.messages.models.ThreadItem.ThreadSending +import org.fossify.messages.models.ThreadItem.ThreadSent import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -75,6 +193,7 @@ import org.joda.time.DateTime import java.io.File import java.io.InputStream import java.io.OutputStream +import kotlin.collections.set class ThreadActivity : SimpleActivity() { private val MIN_DATE_TIME_DIFF_SECS = 300 @@ -101,7 +220,6 @@ class ThreadActivity : SimpleActivity() { private var loadingOlderMessages = false private var allMessagesFetched = false private var oldestMessageDate = -1 - private var wasProtectionHandled = false private var isRecycleBin = false private var isScheduledMessage: Boolean = false @@ -113,7 +231,7 @@ class ThreadActivity : SimpleActivity() { private val binding by viewBinding(ActivityThreadBinding::inflate) - override fun onNewIntent(intent: Intent?) { + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) finish() startActivity(intent) @@ -126,7 +244,12 @@ class ThreadActivity : SimpleActivity() { setupOptionsMenu() refreshMenuItems() - updateMaterialActivityViews(binding.threadCoordinator, null, useTransparentNavigation = false, useTopSearchMenu = false) + updateMaterialActivityViews( + mainCoordinatorLayout = binding.threadCoordinator, + nestedView = null, + useTransparentNavigation = false, + useTopSearchMenu = false + ) setupMaterialScrollListener(null, binding.threadToolbar) val extras = intent.extras @@ -141,28 +264,11 @@ class ThreadActivity : SimpleActivity() { binding.threadToolbar.title = it } isRecycleBin = intent.getBooleanExtra(IS_RECYCLE_BIN, false) - wasProtectionHandled = intent.getBooleanExtra(WAS_PROTECTION_HANDLED, false) bus = EventBus.getDefault() bus!!.register(this) - if (savedInstanceState == null) { - if (!wasProtectionHandled) { - handleAppPasswordProtection { - wasProtectionHandled = it - if (it) { - clearAllMessagesIfNeeded { - loadConversation() - } - } else { - finish() - } - } - } else { - loadConversation() - } - } - + loadConversation() setupAttachmentPickerView() setupKeyboardListener() hideAttachmentPicker() @@ -171,7 +277,11 @@ class ThreadActivity : SimpleActivity() { override fun onResume() { super.onResume() - setupToolbar(binding.threadToolbar, NavigationIcon.Arrow, statusBarColor = getProperBackgroundColor()) + setupToolbar( + toolbar = binding.threadToolbar, + toolbarNavigationIcon = NavigationIcon.Arrow, + statusBarColor = getProperBackgroundColor() + ) val smsDraft = getSmsDraft(threadId) if (smsDraft != null) { @@ -224,49 +334,32 @@ class ThreadActivity : SimpleActivity() { bus?.unregister(this) } - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putBoolean(WAS_PROTECTION_HANDLED, wasProtectionHandled) - } - - override fun onRestoreInstanceState(savedInstanceState: Bundle) { - super.onRestoreInstanceState(savedInstanceState) - wasProtectionHandled = savedInstanceState.getBoolean(WAS_PROTECTION_HANDLED, false) - - if (!wasProtectionHandled) { - handleAppPasswordProtection { - wasProtectionHandled = it - if (it) { - loadConversation() - } else { - finish() - } - } - } else { - loadConversation() - } - } - private fun refreshMenuItems() { val firstPhoneNumber = participants.firstOrNull()?.phoneNumbers?.firstOrNull()?.value val archiveAvailable = config.isArchiveAvailable binding.threadToolbar.menu.apply { findItem(R.id.delete).isVisible = threadItems.isNotEmpty() findItem(R.id.restore).isVisible = threadItems.isNotEmpty() && isRecycleBin - findItem(R.id.archive).isVisible = threadItems.isNotEmpty() && conversation?.isArchived == false && !isRecycleBin && archiveAvailable - findItem(R.id.unarchive).isVisible = threadItems.isNotEmpty() && conversation?.isArchived == true && !isRecycleBin && archiveAvailable - findItem(R.id.rename_conversation).isVisible = participants.size > 1 && conversation != null && !isRecycleBin + findItem(R.id.archive).isVisible = + threadItems.isNotEmpty() && conversation?.isArchived == false && !isRecycleBin && archiveAvailable + findItem(R.id.unarchive).isVisible = + threadItems.isNotEmpty() && conversation?.isArchived == true && !isRecycleBin && archiveAvailable + findItem(R.id.rename_conversation).isVisible = + participants.size > 1 && conversation != null && !isRecycleBin findItem(R.id.conversation_details).isVisible = conversation != null && !isRecycleBin - findItem(R.id.block_number).title = addLockedLabelIfNeeded(org.fossify.commons.R.string.block_number) + findItem(R.id.block_number).title = + addLockedLabelIfNeeded(org.fossify.commons.R.string.block_number) findItem(R.id.block_number).isVisible = isNougatPlus() && !isRecycleBin - findItem(R.id.dial_number).isVisible = participants.size == 1 && !isSpecialNumber() && !isRecycleBin + findItem(R.id.dial_number).isVisible = + participants.size == 1 && !isSpecialNumber() && !isRecycleBin findItem(R.id.manage_people).isVisible = !isSpecialNumber() && !isRecycleBin findItem(R.id.mark_as_unread).isVisible = threadItems.isNotEmpty() && !isRecycleBin - // allow saving number in cases when we dont have it stored yet and it is a casual readable number - findItem(R.id.add_number_to_contact).isVisible = participants.size == 1 && participants.first().name == firstPhoneNumber && firstPhoneNumber.any { - it.isDigit() - } && !isRecycleBin + // allow saving number in cases when we don't have it stored yet and it is a casual readable number + findItem(R.id.add_number_to_contact).isVisible = + participants.size == 1 && participants.first().name == firstPhoneNumber && firstPhoneNumber.any { + it.isDigit() + } && !isRecycleBin } } @@ -304,7 +397,12 @@ class ThreadActivity : SimpleActivity() { addAttachment(capturedImageUri!!) } else if (data != null) { when (requestCode) { - CAPTURE_VIDEO_INTENT, PICK_DOCUMENT_INTENT, CAPTURE_AUDIO_INTENT, PICK_PHOTO_INTENT, PICK_VIDEO_INTENT -> addAttachment(data) + CAPTURE_VIDEO_INTENT, + PICK_DOCUMENT_INTENT, + CAPTURE_AUDIO_INTENT, + PICK_PHOTO_INTENT, + PICK_VIDEO_INTENT -> addAttachment(data) + PICK_CONTACT_INTENT -> addContactAttachment(data) PICK_SAVE_FILE_INTENT -> saveAttachment(resultData) } @@ -360,8 +458,10 @@ class ThreadActivity : SimpleActivity() { if (!isRecycleBin) { messages = getMessages(threadId, true) if (config.useRecycleBin) { - val recycledMessages = messagesDB.getThreadMessagesFromRecycleBin(threadId).map { it.id } - messages = messages.filter { !recycledMessages.contains(it.id) }.toMutableList() as ArrayList + val recycledMessages = + messagesDB.getThreadMessagesFromRecycleBin(threadId).map { it.id } + messages = messages.filter { !recycledMessages.contains(it.id) } + .toMutableList() as ArrayList } } @@ -383,11 +483,13 @@ class ThreadActivity : SimpleActivity() { if (privateContacts.isNotEmpty()) { val senderNumbersToReplace = HashMap() participants.filter { it.doesHavePhoneNumber(it.name) }.forEach { participant -> - privateContacts.firstOrNull { it.doesHavePhoneNumber(participant.phoneNumbers.first().normalizedNumber) }?.apply { - senderNumbersToReplace[participant.phoneNumbers.first().normalizedNumber] = name - participant.name = name - participant.photoUri = photoUri - } + privateContacts.firstOrNull { it.doesHavePhoneNumber(participant.phoneNumbers.first().normalizedNumber) } + ?.apply { + senderNumbersToReplace[participant.phoneNumbers.first().normalizedNumber] = + name + participant.name = name + participant.photoUri = photoUri + } } messages.forEach { message -> @@ -407,7 +509,15 @@ class ThreadActivity : SimpleActivity() { } val phoneNumber = PhoneNumber(number, 0, "", number) - val contact = SimpleContact(0, 0, name, "", arrayListOf(phoneNumber), ArrayList(), ArrayList()) + val contact = SimpleContact( + rawId = 0, + contactId = 0, + name = name, + photoUri = "", + phoneNumbers = arrayListOf(phoneNumber), + birthdays = ArrayList(), + anniversaries = ArrayList() + ) participants.add(contact) } @@ -434,17 +544,24 @@ class ThreadActivity : SimpleActivity() { recyclerView = binding.threadMessagesList, itemClick = { handleItemClick(it) }, isRecycleBin = isRecycleBin, - deleteMessages = { messages, toRecycleBin, fromRecycleBin -> deleteMessages(messages, toRecycleBin, fromRecycleBin) } + deleteMessages = { messages, toRecycleBin, fromRecycleBin -> + deleteMessages( + messages, + toRecycleBin, + fromRecycleBin + ) + } ) binding.threadMessagesList.adapter = currAdapter - binding.threadMessagesList.endlessScrollListener = object : MyRecyclerView.EndlessScrollListener { - override fun updateBottom() {} + binding.threadMessagesList.endlessScrollListener = + object : MyRecyclerView.EndlessScrollListener { + override fun updateBottom() {} - override fun updateTop() { - fetchNextMessages() + override fun updateTop() { + fetchNextMessages() + } } - } } return currAdapter as ThreadAdapter } @@ -458,7 +575,8 @@ class ThreadActivity : SimpleActivity() { val layoutManager = binding.threadMessagesList.layoutManager as LinearLayoutManager val lastPosition = itemCount - 1 val lastVisiblePosition = layoutManager.findLastVisibleItemPosition() - val shouldScrollToBottom = currentList.lastOrNull() != threadItems.lastOrNull() && lastPosition - lastVisiblePosition == 1 + val shouldScrollToBottom = + currentList.lastOrNull() != threadItems.lastOrNull() && lastPosition - lastVisiblePosition == 1 updateMessages(threadItems, if (shouldScrollToBottom) lastPosition else -1) } } @@ -470,7 +588,8 @@ class ThreadActivity : SimpleActivity() { binding.addContactOrNumber.setAdapter(adapter) binding.addContactOrNumber.imeOptions = EditorInfo.IME_ACTION_NEXT binding.addContactOrNumber.setOnItemClickListener { _, _, position, _ -> - val currContacts = (binding.addContactOrNumber.adapter as AutoCompleteTextViewAdapter).resultList + val currContacts = + (binding.addContactOrNumber.adapter as AutoCompleteTextViewAdapter).resultList val selectedContact = currContacts[position] addSelectedContact(selectedContact) } @@ -485,7 +604,15 @@ class ThreadActivity : SimpleActivity() { binding.confirmInsertedNumber.setOnClickListener { val number = binding.addContactOrNumber.value val phoneNumber = PhoneNumber(number, 0, "", number) - val contact = SimpleContact(number.hashCode(), number.hashCode(), number, "", arrayListOf(phoneNumber), ArrayList(), ArrayList()) + val contact = SimpleContact( + rawId = number.hashCode(), + contactId = number.hashCode(), + name = number, + photoUri = "", + phoneNumbers = arrayListOf(phoneNumber), + birthdays = ArrayList(), + anniversaries = ArrayList() + ) addSelectedContact(contact) } } @@ -504,7 +631,8 @@ class ThreadActivity : SimpleActivity() { super.onScrolled(recyclerView, dx, dy) val layoutManager = binding.threadMessagesList.layoutManager as LinearLayoutManager val lastVisibleItemPosition = layoutManager.findLastCompletelyVisibleItemPosition() - val isCloseToBottom = lastVisibleItemPosition >= getOrCreateThreadAdapter().itemCount - SCROLL_TO_BOTTOM_FAB_LIMIT + val isCloseToBottom = + lastVisibleItemPosition >= getOrCreateThreadAdapter().itemCount - SCROLL_TO_BOTTOM_FAB_LIMIT if (isCloseToBottom) { binding.scrollToBottomFab.hide() } else { @@ -524,7 +652,11 @@ class ThreadActivity : SimpleActivity() { } } - private fun deleteMessages(messagesToRemove: List, toRecycleBin: Boolean, fromRecycleBin: Boolean) { + private fun deleteMessages( + messagesToRemove: List, + toRecycleBin: Boolean, + fromRecycleBin: Boolean + ) { val deletePosition = threadItems.indexOf(messagesToRemove.first()) messages.removeAll(messagesToRemove.toSet()) threadItems = getThreadItems() @@ -574,7 +706,10 @@ class ThreadActivity : SimpleActivity() { val newList = currentList.toMutableList().apply { removeAll { it is ThreadLoading } } - updateMessages(newMessages = newList as ArrayList, scrollPosition = 0) + updateMessages( + newMessages = newList as ArrayList, + scrollPosition = 0 + ) } } return @@ -615,7 +750,8 @@ class ThreadActivity : SimpleActivity() { 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 } + val index = + threadItems.indexOfFirst { (it as? Message)?.id == searchedMessageId } if (index != -1) { binding.threadMessagesList.smoothScrollToPosition(index) } @@ -677,6 +813,7 @@ class ThreadActivity : SimpleActivity() { it } val messageLength = SmsMessage.calculateLength(messageString, false) + @SuppressLint("SetTextI18n") threadCharacterCounter.text = "${messageLength[2]}/${messageLength[0]}" } @@ -726,11 +863,13 @@ class ThreadActivity : SimpleActivity() { threadAddAttachment.setOnClickListener { if (attachmentPickerHolder.isVisible()) { isAttachmentPickerVisible = false - WindowCompat.getInsetsController(window, threadTypeMessage).show(WindowInsetsCompat.Type.ime()) + WindowCompat.getInsetsController(window, threadTypeMessage) + .show(WindowInsetsCompat.Type.ime()) } else { isAttachmentPickerVisible = true showOrHideAttachmentPicker() - WindowCompat.getInsetsController(window, threadTypeMessage).hide(WindowInsetsCompat.Type.ime()) + WindowCompat.getInsetsController(window, threadTypeMessage) + .hide(WindowInsetsCompat.Type.ime()) } window.decorView.requestApplyInsets() } @@ -779,14 +918,22 @@ class ThreadActivity : SimpleActivity() { if (it.mimetype.startsWith("image/")) { val fileOptions = BitmapFactory.Options() fileOptions.inJustDecodeBounds = true - BitmapFactory.decodeStream(contentResolver.openInputStream(it.getUri()), null, fileOptions) + 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() + it.width = metaRetriever.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH + )!!.toInt() + it.height = metaRetriever.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT + )!!.toInt() } if (it.width < 0) { @@ -885,6 +1032,7 @@ class ThreadActivity : SimpleActivity() { binding.messageHolder.threadSelectSimIcon.setOnClickListener { currentSIMCardIndex = (currentSIMCardIndex + 1) % availableSIMCards.size val currentSIMCard = availableSIMCards[currentSIMCardIndex] + @SuppressLint("SetTextI18n") binding.messageHolder.threadSelectSimNumber.text = currentSIMCard.id.toString() val currentSubscriptionId = currentSIMCard.subscriptionId numbers.forEach { @@ -894,9 +1042,13 @@ class ThreadActivity : SimpleActivity() { } } - binding.messageHolder.threadSelectSimNumber.setTextColor(getProperTextColor().getContrastColor()) + binding.messageHolder.threadSelectSimNumber.setTextColor( + getProperTextColor().getContrastColor() + ) try { - binding.messageHolder.threadSelectSimNumber.text = (availableSIMCards[currentSIMCardIndex].id).toString() + @SuppressLint("SetTextI18n") + binding.messageHolder.threadSelectSimNumber.text = + (availableSIMCards[currentSIMCardIndex].id).toString() } catch (e: Exception) { showErrorToast(e) } @@ -904,9 +1056,13 @@ class ThreadActivity : SimpleActivity() { } @SuppressLint("MissingPermission") - private fun getProperSimIndex(availableSIMs: MutableList, numbers: List): Int { + private fun getProperSimIndex( + availableSIMs: MutableList, + numbers: List + ): Int { val userPreferredSimId = config.getUseSIMIdAtNumber(numbers.first()) - val userPreferredSimIdx = availableSIMs.indexOfFirstOrNull { it.subscriptionId == userPreferredSimId } + val userPreferredSimIdx = + availableSIMs.indexOfFirstOrNull { it.subscriptionId == userPreferredSimId } val lastMessage = messages.lastOrNull() val senderPreferredSimIdx = if (lastMessage?.isReceivedMessage() == true) { @@ -936,7 +1092,10 @@ class ThreadActivity : SimpleActivity() { private fun blockNumber() { val numbers = participants.getAddresses() val numbersString = TextUtils.join(", ", numbers) - val question = String.format(resources.getString(org.fossify.commons.R.string.block_confirmation), numbersString) + val question = String.format( + resources.getString(org.fossify.commons.R.string.block_confirmation), + numbersString + ) ConfirmationDialog(this, question) { ensureBackgroundThread { @@ -1021,8 +1180,13 @@ class ThreadActivity : SimpleActivity() { val views = ArrayList() participants.forEach { contact -> ItemSelectedContactBinding.inflate(layoutInflater).apply { - val selectedContactBg = resources.getDrawable(R.drawable.item_selected_contact_background) - (selectedContactBg as LayerDrawable).findDrawableByLayerId(R.id.selected_contact_bg).applyColorFilter(properPrimaryColor) + val selectedContactBg = + AppCompatResources.getDrawable( + this@ThreadActivity, + R.drawable.item_selected_contact_background + ) + (selectedContactBg as LayerDrawable).findDrawableByLayerId(R.id.selected_contact_bg) + .applyColorFilter(properPrimaryColor) selectedContactHolder.background = selectedContactBg selectedContactName.text = contact.name @@ -1063,7 +1227,8 @@ class ThreadActivity : SimpleActivity() { } private fun addNumberToContact() { - val phoneNumber = participants.firstOrNull()?.phoneNumbers?.firstOrNull()?.normalizedNumber ?: return + val phoneNumber = + participants.firstOrNull()?.phoneNumbers?.firstOrNull()?.normalizedNumber ?: return Intent().apply { action = Intent.ACTION_INSERT_OR_EDIT type = "vnd.android.cursor.item/contact" @@ -1113,7 +1278,8 @@ class ThreadActivity : SimpleActivity() { val message = messages.getOrNull(i) ?: continue // do not show the date/time above every message, only if the difference between the 2 messages is at least MIN_DATE_TIME_DIFF_SECS, // or if the message is sent from a different SIM - val isSentFromDifferentKnownSIM = prevSIMId != -1 && message.subscriptionId != -1 && prevSIMId != message.subscriptionId + val isSentFromDifferentKnownSIM = + prevSIMId != -1 && message.subscriptionId != -1 && prevSIMId != message.subscriptionId if (message.date - prevDateTime > MIN_DATE_TIME_DIFF_SECS || isSentFromDifferentKnownSIM) { val simCardID = subscriptionIdToSimId[message.subscriptionId] ?: "?" items.add(ThreadDateTime(message.date, simCardID)) @@ -1136,7 +1302,12 @@ class ThreadActivity : SimpleActivity() { } if (i == cnt - 1 && (message.type == Telephony.Sms.MESSAGE_TYPE_SENT)) { - items.add(ThreadSent(message.id, delivered = message.status == Telephony.Sms.STATUS_COMPLETE)) + items.add( + ThreadSent( + messageId = message.id, + delivered = message.status == Telephony.Sms.STATUS_COMPLETE + ) + ) } prevSIMId = message.subscriptionId } @@ -1153,7 +1324,11 @@ class ThreadActivity : SimpleActivity() { return items } - private fun launchActivityForResult(intent: Intent, requestCode: Int, @StringRes error: Int = org.fossify.commons.R.string.no_app_found) { + private fun launchActivityForResult( + intent: Intent, + requestCode: Int, + @StringRes error: Int = org.fossify.commons.R.string.no_app_found + ) { hideKeyboard() try { startActivityForResult(intent, requestCode) @@ -1293,13 +1468,18 @@ class ThreadActivity : SimpleActivity() { } private fun saveAttachment(resultData: Intent) { - val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - applicationContext.contentResolver.takePersistableUriPermission(resultData.data!!, takeFlags) + val takeFlags = + Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + applicationContext.contentResolver.takePersistableUriPermission( + resultData.data!!, + takeFlags + ) var inputStream: InputStream? = null var outputStream: OutputStream? = null try { inputStream = contentResolver.openInputStream(Uri.parse(lastAttachmentUri)) - outputStream = contentResolver.openOutputStream(Uri.parse(resultData.dataString!!), "rwt") + outputStream = + contentResolver.openOutputStream(Uri.parse(resultData.dataString!!), "rwt") inputStream!!.copyTo(outputStream!!) outputStream.flush() toast(org.fossify.commons.R.string.file_saved) @@ -1338,7 +1518,8 @@ class ThreadActivity : SimpleActivity() { text = removeDiacriticsIfNeeded(text) - val subscriptionId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId ?: SmsManager.getDefaultSmsSubscriptionId() + val subscriptionId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId + ?: SmsManager.getDefaultSmsSubscriptionId() if (isScheduledMessage) { sendScheduledMessage(text, subscriptionId) @@ -1367,7 +1548,12 @@ class ThreadActivity : SimpleActivity() { val conversation = conversationsDB.getConversationWithThreadId(threadId) if (conversation != null) { val nowSeconds = (System.currentTimeMillis() / 1000).toInt() - conversationsDB.insertOrUpdate(conversation.copy(date = nowSeconds, snippet = message.body)) + conversationsDB.insertOrUpdate( + conversation.copy( + date = nowSeconds, + snippet = message.body + ) + ) } scheduleMessage(message) insertOrUpdateMessage(message) @@ -1379,7 +1565,9 @@ class ThreadActivity : SimpleActivity() { } } } catch (e: Exception) { - showErrorToast(e.localizedMessage ?: getString(org.fossify.commons.R.string.unknown_error_occurred)) + showErrorToast( + e.localizedMessage ?: getString(org.fossify.commons.R.string.unknown_error_occurred) + ) } } @@ -1392,7 +1580,11 @@ class ThreadActivity : SimpleActivity() { sendMessageCompat(text, addresses, subscriptionId, attachments, messageToResend) ensureBackgroundThread { val messageIds = messages.map { it.id } - val messages = getMessages(threadId, getImageResolutions = true, limit = maxOf(1, attachments.size)) + val messages = getMessages( + threadId = threadId, + getImageResolutions = true, + limit = maxOf(1, attachments.size) + ) .filter { it.id !in messageIds } for (message in messages) { insertOrUpdateMessage(message) @@ -1403,7 +1595,9 @@ class ThreadActivity : SimpleActivity() { } catch (e: Exception) { showErrorToast(e) } catch (e: Error) { - showErrorToast(e.localizedMessage ?: getString(org.fossify.commons.R.string.unknown_error_occurred)) + showErrorToast( + e.localizedMessage ?: getString(org.fossify.commons.R.string.unknown_error_occurred) + ) } } @@ -1437,13 +1631,17 @@ class ThreadActivity : SimpleActivity() { private fun showSelectedContact(views: ArrayList) { binding.selectedContacts.removeAllViews() var newLinearLayout = LinearLayout(this) - newLinearLayout.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) + newLinearLayout.layoutParams = + LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) newLinearLayout.orientation = LinearLayout.HORIZONTAL - val sideMargin = (binding.selectedContacts.layoutParams as RelativeLayout.LayoutParams).leftMargin + val sideMargin = + (binding.selectedContacts.layoutParams as RelativeLayout.LayoutParams).leftMargin val mediumMargin = resources.getDimension(org.fossify.commons.R.dimen.medium_margin).toInt() val parentWidth = realScreenSize.x - sideMargin * 2 - val firstRowWidth = parentWidth - resources.getDimension(org.fossify.commons.R.dimen.normal_icon_size).toInt() + sideMargin / 2 + val firstRowWidth = + parentWidth - resources.getDimension(org.fossify.commons.R.dimen.normal_icon_size) + .toInt() + sideMargin / 2 var widthSoFar = 0 var isFirstRow = true @@ -1465,7 +1663,8 @@ class ThreadActivity : SimpleActivity() { isFirstRow = false binding.selectedContacts.addView(newLinearLayout) newLinearLayout = LinearLayout(this) - newLinearLayout.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) + newLinearLayout.layoutParams = + LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) newLinearLayout.orientation = LinearLayout.HORIZONTAL params = LayoutParams(layout.measuredWidth, layout.measuredHeight) params.topMargin = mediumMargin @@ -1482,7 +1681,8 @@ class ThreadActivity : SimpleActivity() { } private fun removeSelectedContact(id: Int) { - participants = participants.filter { it.rawId != id }.toMutableList() as ArrayList + participants = + participants.filter { it.rawId != id }.toMutableList() as ArrayList showSelectedContacts() updateMessageType() } @@ -1502,7 +1702,10 @@ class ThreadActivity : SimpleActivity() { return numbers } - private fun fixParticipantNumbers(participants: ArrayList, properNumbers: ArrayList): ArrayList { + private fun fixParticipantNumbers( + participants: ArrayList, + properNumbers: ArrayList + ): ArrayList { for (number in properNumbers) { for (participant in participants) { participant.phoneNumbers = participant.phoneNumbers.map { @@ -1529,7 +1732,11 @@ class ThreadActivity : SimpleActivity() { type = mimeType addCategory(Intent.CATEGORY_OPENABLE) putExtra(Intent.EXTRA_TITLE, path.split("/").last()) - launchActivityForResult(this, PICK_SAVE_FILE_INTENT, error = org.fossify.commons.R.string.system_service_disabled) + launchActivityForResult( + intent = this, + requestCode = PICK_SAVE_FILE_INTENT, + error = org.fossify.commons.R.string.system_service_disabled + ) } } @@ -1549,12 +1756,16 @@ class ThreadActivity : SimpleActivity() { val lastMaxId = messages.filterNot { it.isScheduled }.maxByOrNull { it.id }?.id ?: 0L val newThreadId = getThreadId(participants.getAddresses().toSet()) - val newMessages = getMessages(newThreadId, getImageResolutions = true, includeScheduledMessages = false) + val newMessages = + getMessages(newThreadId, getImageResolutions = true, includeScheduledMessages = false) if (messages.isNotEmpty() && messages.all { it.isScheduled } && newMessages.isNotEmpty()) { // update scheduled messages with real thread id threadId = newThreadId - updateScheduledMessagesThreadId(messages = messages.filter { it.threadId != threadId }, threadId) + updateScheduledMessagesThreadId( + messages = messages.filter { it.threadId != threadId }, + newThreadId = threadId + ) } messages = newMessages.apply { @@ -1567,9 +1778,10 @@ class ThreadActivity : SimpleActivity() { } } - messages.filter { !it.isScheduled && !it.isReceivedMessage() && it.id > lastMaxId }.forEach { latestMessage -> - messagesDB.insertOrIgnore(latestMessage) - } + messages.filter { !it.isScheduled && !it.isReceivedMessage() && it.id > lastMaxId } + .forEach { latestMessage -> + messagesDB.insertOrIgnore(latestMessage) + } setupAdapter() runOnUiThread { @@ -1599,7 +1811,11 @@ class ThreadActivity : SimpleActivity() { RadioItem(TYPE_SEND, getString(R.string.send_now)), RadioItem(TYPE_DELETE, getString(org.fossify.commons.R.string.delete)) ) - RadioGroupDialog(activity = this, items = items, titleId = R.string.scheduled_message) { any -> + RadioGroupDialog( + activity = this, + items = items, + titleId = R.string.scheduled_message + ) { any -> when (any as Int) { TYPE_DELETE -> cancelScheduledMessageAndRefresh(message.id) TYPE_EDIT -> editScheduledMessage(message) @@ -1680,12 +1896,13 @@ class ThreadActivity : SimpleActivity() { val dateTime = scheduledDateTime val millis = dateTime.millis - binding.messageHolder.scheduledMessageButton.text = if (dateTime.yearOfCentury().get() > DateTime.now().yearOfCentury().get()) { - millis.formatDate(this) - } else { - val flags = FORMAT_SHOW_TIME or FORMAT_SHOW_DATE or FORMAT_NO_YEAR - DateUtils.formatDateTime(this, millis, flags) - } + binding.messageHolder.scheduledMessageButton.text = + if (dateTime.yearOfCentury().get() > DateTime.now().yearOfCentury().get()) { + millis.formatDate(this) + } else { + val flags = FORMAT_SHOW_TIME or FORMAT_SHOW_DATE or FORMAT_NO_YEAR + DateUtils.formatDateTime(this, millis, flags) + } } private fun hideScheduleSendUi() { @@ -1702,7 +1919,9 @@ class ThreadActivity : SimpleActivity() { } ResourcesCompat.getDrawable(resources, drawableResId, theme)?.apply { applyColorFilter(getProperTextColor()) - binding.messageHolder.threadSendMessage.setCompoundDrawablesWithIntrinsicBounds(null, this, null, null) + binding.messageHolder.threadSendMessage.setCompoundDrawablesWithIntrinsicBounds( + null, this, null, null + ) } } @@ -1836,14 +2055,18 @@ class ThreadActivity : SimpleActivity() { view.onApplyWindowInsets(insets) } - val callback = object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) { - override fun onPrepare(animation: WindowInsetsAnimationCompat) { - super.onPrepare(animation) - showOrHideAttachmentPicker() - } + val callback = + object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) { + override fun onPrepare(animation: WindowInsetsAnimationCompat) { + super.onPrepare(animation) + showOrHideAttachmentPicker() + } - override fun onProgress(insets: WindowInsetsCompat, runningAnimations: MutableList) = insets - } + override fun onProgress( + insets: WindowInsetsCompat, + runningAnimations: MutableList + ) = insets + } ViewCompat.setWindowInsetsAnimationCallback(window.decorView, callback) } @@ -1868,7 +2091,7 @@ class ThreadActivity : SimpleActivity() { } } - private fun getBottomBarColor() = if (baseConfig.isUsingSystemTheme) { + private fun getBottomBarColor() = if (isDynamicTheme()) { resources.getColor(org.fossify.commons.R.color.you_bottom_bar_color) } else { getBottomNavigationBackgroundColor() diff --git a/app/src/main/kotlin/org/fossify/messages/dialogs/ScheduleMessageDialog.kt b/app/src/main/kotlin/org/fossify/messages/dialogs/ScheduleMessageDialog.kt index d7c3e327..6634c360 100644 --- a/app/src/main/kotlin/org/fossify/messages/dialogs/ScheduleMessageDialog.kt +++ b/app/src/main/kotlin/org/fossify/messages/dialogs/ScheduleMessageDialog.kt @@ -9,7 +9,14 @@ import androidx.appcompat.app.AlertDialog import com.google.android.material.timepicker.MaterialTimePicker import com.google.android.material.timepicker.TimeFormat import org.fossify.commons.activities.BaseSimpleActivity -import org.fossify.commons.extensions.* +import org.fossify.commons.extensions.applyColorFilter +import org.fossify.commons.extensions.getAlertDialogBuilder +import org.fossify.commons.extensions.getDatePickerDialogTheme +import org.fossify.commons.extensions.getProperTextColor +import org.fossify.commons.extensions.getTimeFormat +import org.fossify.commons.extensions.isDynamicTheme +import org.fossify.commons.extensions.setupDialogStuff +import org.fossify.commons.extensions.toast import org.fossify.messages.R import org.fossify.messages.databinding.ScheduleMessageDialogBinding import org.fossify.messages.extensions.config @@ -94,7 +101,12 @@ class ScheduleMessageDialog( val dateSetListener = OnDateSetListener { _, y, m, d -> dateSet(y, m, d) } DatePickerDialog( - activity, activity.getDatePickerDialogTheme(), dateSetListener, year, monthOfYear, dayOfMonth + activity, + activity.getDatePickerDialogTheme(), + dateSetListener, + year, + monthOfYear, + dayOfMonth ).apply { datePicker.minDate = System.currentTimeMillis() show() @@ -111,7 +123,7 @@ class ScheduleMessageDialog( val hourOfDay = dateTime?.hourOfDay ?: getNextHour() val minute = dateTime?.minuteOfHour ?: getNextMinute() - if (activity.config.isUsingSystemTheme) { + if (activity.isDynamicTheme()) { val timeFormat = if (DateFormat.is24HourFormat(activity)) { TimeFormat.CLOCK_24H } else { @@ -132,7 +144,12 @@ class ScheduleMessageDialog( } else { val timeSetListener = OnTimeSetListener { _, hours, minutes -> timeSet(hours, minutes) } TimePickerDialog( - activity, activity.getDatePickerDialogTheme(), timeSetListener, hourOfDay, minute, DateFormat.is24HourFormat(activity) + activity, + activity.getDatePickerDialogTheme(), + timeSetListener, + hourOfDay, + minute, + DateFormat.is24HourFormat(activity) ).apply { show() getButton(AlertDialog.BUTTON_NEGATIVE).apply { @@ -187,7 +204,14 @@ class ScheduleMessageDialog( } } - private fun getNextHour() = (calendar.get(Calendar.HOUR_OF_DAY) + 1).coerceIn(0, 23) + private fun getNextHour(): Int { + return (calendar.get(Calendar.HOUR_OF_DAY) + 1) + .coerceIn(0, 23) + } - private fun getNextMinute() = (calendar.get(Calendar.MINUTE) + 5).roundToClosestMultipleOf(5).coerceIn(0, 59) + private fun getNextMinute(): Int { + return (calendar.get(Calendar.MINUTE) + 5) + .roundToClosestMultipleOf(5) + .coerceIn(0, 59) + } } diff --git a/app/src/main/kotlin/org/fossify/messages/extensions/Math.kt b/app/src/main/kotlin/org/fossify/messages/extensions/Math.kt index 28c80288..daf4a9f1 100644 --- a/app/src/main/kotlin/org/fossify/messages/extensions/Math.kt +++ b/app/src/main/kotlin/org/fossify/messages/extensions/Math.kt @@ -5,4 +5,6 @@ import kotlin.math.roundToInt /** * Returns the closest number divisible by [multipleOf]. */ -fun Int.roundToClosestMultipleOf(multipleOf: Int = 1) = (toDouble() / multipleOf).roundToInt() * multipleOf +fun Int.roundToClosestMultipleOf(multipleOf: Int = 1): Int { + return (toDouble() / multipleOf).roundToInt() * multipleOf +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c36f6536..8d8cc536 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ eventbus = "3.3.1" #Room room = "2.6.1" #Fossify -commons = "8685963657" +commons = "8038e3277e" android-smsmms = "c3e678befd" indicator-fast-scroll = "4524cd0b61" #Gradle