Setup full screen app lock

- Updated commons
This commit is contained in:
Naveen Singh 2024-12-26 13:16:31 +05:30
parent 0df10f18f0
commit 3f17c2d94c
No known key found for this signature in database
GPG key ID: AF5D43C216778C0B
10 changed files with 553 additions and 227 deletions

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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,16 +107,9 @@ class MainActivity : SimpleActivity() {
if (savedInstanceState == null) {
checkAndDeleteOldRecycleBinMessages()
handleAppPasswordProtection {
wasProtectionHandled = it
if (it) {
clearAllMessagesIfNeeded {
loadMessages()
}
} else {
finish()
}
}
}
if (checkAppSideloading()) {
@ -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<Conversation>, cached: Boolean = false) {
private fun setupConversations(
conversations: ArrayList<Conversation>,
cached: Boolean = false
) {
val sortedConversations = conversations.sortedWith(
compareByDescending<Conversation> { config.pinnedConversations.contains(it.threadId.toString()) }
.thenByDescending { it.date }
@ -435,7 +461,10 @@ class MainActivity : SimpleActivity() {
}
private fun fadeOutSearch() {
binding.searchHolder.animate().alpha(0f).setDuration(SHORT_ANIMATION_DURATION).withEndAction {
binding.searchHolder.animate()
.alpha(0f)
.setDuration(SHORT_ANIMATION_DURATION)
.withEndAction {
binding.searchHolder.beGone()
searchTextChanged("", true)
}.start()
@ -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<Message>, conversations: List<Conversation>, searchedText: String) {
private fun showSearchResults(
messages: List<Message>,
conversations: List<Conversation>,
searchedText: String
) {
val searchResults = ArrayList<SearchResult>()
conversations.forEach { conversation ->
val date = conversation.date.formatDateOrTime(this, true, true)
val searchResult = SearchResult(-1, conversation.title, conversation.phoneNumber, date, conversation.threadId, conversation.photoUri)
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)

View file

@ -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) {

View file

@ -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<Conversation>
conversationsDB.getAllWithMessagesInRecycleBin()
.toMutableList() as ArrayList<Conversation>
} 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)
}

View file

@ -27,4 +27,6 @@ open class SimpleActivity : BaseSimpleActivity() {
)
override fun getAppLauncherName() = getString(R.string.app_launcher_name)
override fun getRepositoryName() = "Messages"
}

View file

@ -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()
}
}
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,47 +334,30 @@ 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 {
// 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<Message>
val recycledMessages =
messagesDB.getThreadMessagesFromRecycleBin(threadId).map { it.id }
messages = messages.filter { !recycledMessages.contains(it.id) }
.toMutableList() as ArrayList<Message>
}
}
@ -383,8 +483,10 @@ class ThreadActivity : SimpleActivity() {
if (privateContacts.isNotEmpty()) {
val senderNumbersToReplace = HashMap<String, String>()
participants.filter { it.doesHavePhoneNumber(it.name) }.forEach { participant ->
privateContacts.firstOrNull { it.doesHavePhoneNumber(participant.phoneNumbers.first().normalizedNumber) }?.apply {
senderNumbersToReplace[participant.phoneNumbers.first().normalizedNumber] = name
privateContacts.firstOrNull { it.doesHavePhoneNumber(participant.phoneNumbers.first().normalizedNumber) }
?.apply {
senderNumbersToReplace[participant.phoneNumbers.first().normalizedNumber] =
name
participant.name = name
participant.photoUri = photoUri
}
@ -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,11 +544,18 @@ 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 {
binding.threadMessagesList.endlessScrollListener =
object : MyRecyclerView.EndlessScrollListener {
override fun updateBottom() {}
override fun updateTop() {
@ -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<Message>, toRecycleBin: Boolean, fromRecycleBin: Boolean) {
private fun deleteMessages(
messagesToRemove: List<Message>,
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<ThreadItem>, scrollPosition = 0)
updateMessages(
newMessages = newList as ArrayList<ThreadItem>,
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<SubscriptionInfo>, numbers: List<String>): Int {
private fun getProperSimIndex(
availableSIMs: MutableList<SubscriptionInfo>,
numbers: List<String>
): 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<View>()
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<View>) {
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<SimpleContact>
participants =
participants.filter { it.rawId != id }.toMutableList() as ArrayList<SimpleContact>
showSelectedContacts()
updateMessageType()
}
@ -1502,7 +1702,10 @@ class ThreadActivity : SimpleActivity() {
return numbers
}
private fun fixParticipantNumbers(participants: ArrayList<SimpleContact>, properNumbers: ArrayList<String>): ArrayList<SimpleContact> {
private fun fixParticipantNumbers(
participants: ArrayList<SimpleContact>,
properNumbers: ArrayList<String>
): ArrayList<SimpleContact> {
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,7 +1778,8 @@ class ThreadActivity : SimpleActivity() {
}
}
messages.filter { !it.isScheduled && !it.isReceivedMessage() && it.id > lastMaxId }.forEach { latestMessage ->
messages.filter { !it.isScheduled && !it.isReceivedMessage() && it.id > lastMaxId }
.forEach { latestMessage ->
messagesDB.insertOrIgnore(latestMessage)
}
@ -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,7 +1896,8 @@ class ThreadActivity : SimpleActivity() {
val dateTime = scheduledDateTime
val millis = dateTime.millis
binding.messageHolder.scheduledMessageButton.text = if (dateTime.yearOfCentury().get() > DateTime.now().yearOfCentury().get()) {
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
@ -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,13 +2055,17 @@ class ThreadActivity : SimpleActivity() {
view.onApplyWindowInsets(insets)
}
val callback = object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
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<WindowInsetsAnimationCompat>) = insets
override fun onProgress(
insets: WindowInsetsCompat,
runningAnimations: MutableList<WindowInsetsAnimationCompat>
) = 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()

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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