Rename package to org.fossify.messages
This commit is contained in:
parent
d71db351ca
commit
e2f83f49da
106 changed files with 417 additions and 418 deletions
|
|
@ -0,0 +1,177 @@
|
|||
package org.fossify.messages.activities
|
||||
|
||||
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.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
|
||||
import org.fossify.messages.databinding.ActivityArchivedConversationsBinding
|
||||
import org.fossify.messages.extensions.config
|
||||
import org.fossify.messages.extensions.conversationsDB
|
||||
import org.fossify.messages.extensions.removeAllArchivedConversations
|
||||
import org.fossify.messages.helpers.THREAD_ID
|
||||
import org.fossify.messages.helpers.THREAD_TITLE
|
||||
import org.fossify.messages.models.Conversation
|
||||
import org.fossify.messages.models.Events
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
|
||||
class ArchivedConversationsActivity : SimpleActivity() {
|
||||
private var bus: EventBus? = null
|
||||
private val binding by viewBinding(ActivityArchivedConversationsBinding::inflate)
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
isMaterialActivity = true
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(binding.root)
|
||||
setupOptionsMenu()
|
||||
|
||||
updateMaterialActivityViews(
|
||||
mainCoordinatorLayout = binding.archiveCoordinator,
|
||||
nestedView = binding.conversationsList,
|
||||
useTransparentNavigation = true,
|
||||
useTopSearchMenu = false
|
||||
)
|
||||
setupMaterialScrollListener(scrollingView = binding.conversationsList, toolbar = binding.archiveToolbar)
|
||||
|
||||
loadArchivedConversations()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
setupToolbar(binding.archiveToolbar, NavigationIcon.Arrow)
|
||||
updateMenuColors()
|
||||
|
||||
loadArchivedConversations()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
bus?.unregister(this)
|
||||
}
|
||||
|
||||
private fun setupOptionsMenu() {
|
||||
binding.archiveToolbar.inflateMenu(R.menu.archive_menu)
|
||||
binding.archiveToolbar.setOnMenuItemClickListener { menuItem ->
|
||||
when (menuItem.itemId) {
|
||||
R.id.empty_archive -> removeAll()
|
||||
else -> return@setOnMenuItemClickListener false
|
||||
}
|
||||
return@setOnMenuItemClickListener true
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateOptionsMenu(conversations: ArrayList<Conversation>) {
|
||||
binding.archiveToolbar.menu.apply {
|
||||
findItem(R.id.empty_archive).isVisible = conversations.isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateMenuColors() {
|
||||
updateStatusbarColor(getProperBackgroundColor())
|
||||
}
|
||||
|
||||
private fun loadArchivedConversations() {
|
||||
ensureBackgroundThread {
|
||||
val conversations = try {
|
||||
conversationsDB.getAllArchived().toMutableList() as ArrayList<Conversation>
|
||||
} catch (e: Exception) {
|
||||
ArrayList()
|
||||
}
|
||||
|
||||
runOnUiThread {
|
||||
setupConversations(conversations)
|
||||
}
|
||||
}
|
||||
|
||||
bus = EventBus.getDefault()
|
||||
try {
|
||||
bus!!.register(this)
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeAll() {
|
||||
ConfirmationDialog(
|
||||
activity = this,
|
||||
message = "",
|
||||
messageId = R.string.empty_archive_confirmation,
|
||||
positive = org.fossify.commons.R.string.yes,
|
||||
negative = org.fossify.commons.R.string.no
|
||||
) {
|
||||
removeAllArchivedConversations {
|
||||
loadArchivedConversations()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getOrCreateConversationsAdapter(): ArchivedConversationsAdapter {
|
||||
var currAdapter = binding.conversationsList.adapter
|
||||
if (currAdapter == null) {
|
||||
hideKeyboard()
|
||||
currAdapter = ArchivedConversationsAdapter(
|
||||
activity = this,
|
||||
recyclerView = binding.conversationsList,
|
||||
onRefresh = { notifyDatasetChanged() },
|
||||
itemClick = { handleConversationClick(it) }
|
||||
)
|
||||
|
||||
binding.conversationsList.adapter = currAdapter
|
||||
if (areSystemAnimationsEnabled) {
|
||||
binding.conversationsList.scheduleLayoutAnimation()
|
||||
}
|
||||
}
|
||||
return currAdapter as ArchivedConversationsAdapter
|
||||
}
|
||||
|
||||
private fun setupConversations(conversations: ArrayList<Conversation>) {
|
||||
val sortedConversations = conversations.sortedWith(
|
||||
compareByDescending<Conversation> { config.pinnedConversations.contains(it.threadId.toString()) }
|
||||
.thenByDescending { it.date }
|
||||
).toMutableList() as ArrayList<Conversation>
|
||||
|
||||
showOrHidePlaceholder(conversations.isEmpty())
|
||||
updateOptionsMenu(conversations)
|
||||
|
||||
try {
|
||||
getOrCreateConversationsAdapter().apply {
|
||||
updateConversations(sortedConversations)
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun showOrHidePlaceholder(show: Boolean) {
|
||||
binding.conversationsFastscroller.beGoneIf(show)
|
||||
binding.noConversationsPlaceholder.beVisibleIf(show)
|
||||
binding.noConversationsPlaceholder.setTextColor(getProperTextColor())
|
||||
binding.noConversationsPlaceholder.text = getString(R.string.no_archived_conversations)
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun notifyDatasetChanged() {
|
||||
getOrCreateConversationsAdapter().notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private fun handleConversationClick(any: Any) {
|
||||
Intent(this, ThreadActivity::class.java).apply {
|
||||
val conversation = any as Conversation
|
||||
putExtra(THREAD_ID, conversation.threadId)
|
||||
putExtra(THREAD_TITLE, conversation.title)
|
||||
putExtra(WAS_PROTECTION_HANDLED, true)
|
||||
startActivity(this)
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun refreshMessages(event: Events.RefreshMessages) {
|
||||
loadArchivedConversations()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
package org.fossify.messages.activities
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Intent
|
||||
import android.media.AudioAttributes
|
||||
import android.media.AudioManager
|
||||
import android.media.RingtoneManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import org.fossify.commons.extensions.*
|
||||
import org.fossify.commons.helpers.NavigationIcon
|
||||
import org.fossify.commons.helpers.ensureBackgroundThread
|
||||
import org.fossify.commons.helpers.isOreoPlus
|
||||
import org.fossify.commons.models.SimpleContact
|
||||
import org.fossify.messages.adapters.ContactsAdapter
|
||||
import org.fossify.messages.databinding.ActivityConversationDetailsBinding
|
||||
import org.fossify.messages.dialogs.RenameConversationDialog
|
||||
import org.fossify.messages.extensions.*
|
||||
import org.fossify.messages.helpers.THREAD_ID
|
||||
import org.fossify.messages.models.Conversation
|
||||
|
||||
class ConversationDetailsActivity : SimpleActivity() {
|
||||
|
||||
private var threadId: Long = 0L
|
||||
private var conversation: Conversation? = null
|
||||
private lateinit var participants: ArrayList<SimpleContact>
|
||||
|
||||
private val binding by viewBinding(ActivityConversationDetailsBinding::inflate)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
isMaterialActivity = true
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(binding.root)
|
||||
|
||||
updateMaterialActivityViews(
|
||||
mainCoordinatorLayout = binding.conversationDetailsCoordinator,
|
||||
nestedView = binding.participantsRecyclerview,
|
||||
useTransparentNavigation = true,
|
||||
useTopSearchMenu = false
|
||||
)
|
||||
setupMaterialScrollListener(scrollingView = binding.participantsRecyclerview, toolbar = binding.conversationDetailsToolbar)
|
||||
|
||||
threadId = intent.getLongExtra(THREAD_ID, 0L)
|
||||
ensureBackgroundThread {
|
||||
conversation = conversationsDB.getConversationWithThreadId(threadId)
|
||||
participants = if (conversation != null && conversation!!.isScheduled) {
|
||||
val message = messagesDB.getThreadMessages(conversation!!.threadId).firstOrNull()
|
||||
message?.participants ?: arrayListOf()
|
||||
} else {
|
||||
getThreadParticipants(threadId, null)
|
||||
}
|
||||
runOnUiThread {
|
||||
setupTextViews()
|
||||
setupParticipants()
|
||||
if (isOreoPlus()) {
|
||||
setupCustomNotifications()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
setupToolbar(binding.conversationDetailsToolbar, NavigationIcon.Arrow)
|
||||
updateTextColors(binding.conversationDetailsHolder)
|
||||
|
||||
val primaryColor = getProperPrimaryColor()
|
||||
binding.conversationNameHeading.setTextColor(primaryColor)
|
||||
binding.membersHeading.setTextColor(primaryColor)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun setupCustomNotifications() {
|
||||
binding.apply {
|
||||
notificationsHeading.beVisible()
|
||||
customNotificationsHolder.beVisible()
|
||||
customNotifications.isChecked = config.customNotifications.contains(threadId.toString())
|
||||
customNotificationsButton.beVisibleIf(customNotifications.isChecked)
|
||||
|
||||
customNotificationsHolder.setOnClickListener {
|
||||
customNotifications.toggle()
|
||||
if (customNotifications.isChecked) {
|
||||
customNotificationsButton.beVisible()
|
||||
config.addCustomNotificationsByThreadId(threadId)
|
||||
createNotificationChannel()
|
||||
} else {
|
||||
customNotificationsButton.beGone()
|
||||
config.removeCustomNotificationsByThreadId(threadId)
|
||||
removeNotificationChannel()
|
||||
}
|
||||
}
|
||||
|
||||
customNotificationsButton.setOnClickListener {
|
||||
Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply {
|
||||
putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
|
||||
putExtra(Settings.EXTRA_CHANNEL_ID, threadId.toString())
|
||||
startActivity(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun createNotificationChannel() {
|
||||
val name = conversation?.title
|
||||
val audioAttributes = AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||
.setLegacyStreamType(AudioManager.STREAM_NOTIFICATION)
|
||||
.build()
|
||||
|
||||
NotificationChannel(threadId.toString(), name, NotificationManager.IMPORTANCE_HIGH).apply {
|
||||
setBypassDnd(false)
|
||||
enableLights(true)
|
||||
setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), audioAttributes)
|
||||
enableVibration(true)
|
||||
notificationManager.createNotificationChannel(this)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun removeNotificationChannel() {
|
||||
notificationManager.deleteNotificationChannel(threadId.toString())
|
||||
}
|
||||
|
||||
private fun setupTextViews() {
|
||||
binding.conversationName.apply {
|
||||
ResourcesCompat.getDrawable(resources, org.fossify.commons.R.drawable.ic_edit_vector, theme)?.apply {
|
||||
applyColorFilter(getProperTextColor())
|
||||
setCompoundDrawablesWithIntrinsicBounds(null, null, this, null)
|
||||
}
|
||||
|
||||
text = conversation?.title
|
||||
setOnClickListener {
|
||||
RenameConversationDialog(this@ConversationDetailsActivity, conversation!!) { title ->
|
||||
text = title
|
||||
ensureBackgroundThread {
|
||||
conversation = renameConversation(conversation!!, newTitle = title)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupParticipants() {
|
||||
val adapter = ContactsAdapter(this, participants, binding.participantsRecyclerview) {
|
||||
val contact = it as SimpleContact
|
||||
val address = contact.phoneNumbers.first().normalizedNumber
|
||||
getContactFromAddress(address) { simpleContact ->
|
||||
if (simpleContact != null) {
|
||||
startContactDetailsIntent(simpleContact)
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.participantsRecyclerview.adapter = adapter
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,607 @@
|
|||
package org.fossify.messages.activities
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.role.RoleManager
|
||||
import android.content.Intent
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.content.pm.ShortcutManager
|
||||
import android.graphics.drawable.Icon
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.os.Bundle
|
||||
import android.provider.Telephony
|
||||
import android.text.TextUtils
|
||||
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.models.FAQItem
|
||||
import org.fossify.commons.models.Release
|
||||
import org.fossify.messages.BuildConfig
|
||||
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.helpers.SEARCHED_MESSAGE_ID
|
||||
import org.fossify.messages.helpers.THREAD_ID
|
||||
import org.fossify.messages.helpers.THREAD_TITLE
|
||||
import org.fossify.messages.models.Conversation
|
||||
import org.fossify.messages.models.Events
|
||||
import org.fossify.messages.models.Message
|
||||
import org.fossify.messages.models.SearchResult
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
|
||||
class MainActivity : SimpleActivity() {
|
||||
private val MAKE_DEFAULT_APP_REQUEST = 1
|
||||
|
||||
private var storedTextColor = 0
|
||||
private var storedFontSize = 0
|
||||
private var lastSearchedText = ""
|
||||
private var bus: EventBus? = null
|
||||
private var wasProtectionHandled = false
|
||||
|
||||
private val binding by viewBinding(ActivityMainBinding::inflate)
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
isMaterialActivity = true
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(binding.root)
|
||||
appLaunched(BuildConfig.APPLICATION_ID)
|
||||
setupOptionsMenu()
|
||||
refreshMenuItems()
|
||||
|
||||
updateMaterialActivityViews(
|
||||
mainCoordinatorLayout = binding.mainCoordinator,
|
||||
nestedView = binding.conversationsList,
|
||||
useTransparentNavigation = true,
|
||||
useTopSearchMenu = true
|
||||
)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
checkAndDeleteOldRecycleBinMessages()
|
||||
handleAppPasswordProtection {
|
||||
wasProtectionHandled = it
|
||||
if (it) {
|
||||
clearAllMessagesIfNeeded {
|
||||
loadMessages()
|
||||
}
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (checkAppSideloading()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
updateMenuColors()
|
||||
refreshMenuItems()
|
||||
|
||||
getOrCreateConversationsAdapter().apply {
|
||||
if (storedTextColor != getProperTextColor()) {
|
||||
updateTextColor(getProperTextColor())
|
||||
}
|
||||
|
||||
if (storedFontSize != config.fontSize) {
|
||||
updateFontSize()
|
||||
}
|
||||
|
||||
updateDrafts()
|
||||
}
|
||||
|
||||
updateTextColors(binding.mainCoordinator)
|
||||
binding.searchHolder.setBackgroundColor(getProperBackgroundColor())
|
||||
|
||||
val properPrimaryColor = getProperPrimaryColor()
|
||||
binding.noConversationsPlaceholder2.setTextColor(properPrimaryColor)
|
||||
binding.noConversationsPlaceholder2.underlineText()
|
||||
binding.conversationsFastscroller.updateColors(properPrimaryColor)
|
||||
binding.conversationsProgressBar.setIndicatorColor(properPrimaryColor)
|
||||
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()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
storeStateVariables()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
bus?.unregister(this)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (binding.mainMenu.isSearchOpen) {
|
||||
binding.mainMenu.closeSearch()
|
||||
} else {
|
||||
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)
|
||||
binding.mainMenu.setupMenu()
|
||||
|
||||
binding.mainMenu.onSearchClosedListener = {
|
||||
fadeOutSearch()
|
||||
}
|
||||
|
||||
binding.mainMenu.onSearchTextChangedListener = { text ->
|
||||
if (text.isNotEmpty()) {
|
||||
if (binding.searchHolder.alpha < 1f) {
|
||||
binding.searchHolder.fadeIn()
|
||||
}
|
||||
} else {
|
||||
fadeOutSearch()
|
||||
}
|
||||
searchTextChanged(text)
|
||||
}
|
||||
|
||||
binding.mainMenu.getToolbar().setOnMenuItemClickListener { menuItem ->
|
||||
when (menuItem.itemId) {
|
||||
R.id.more_apps_from_us -> launchMoreAppsFromUsIntent()
|
||||
R.id.show_recycle_bin -> launchRecycleBin()
|
||||
R.id.show_archived -> launchArchivedConversations()
|
||||
R.id.settings -> launchSettings()
|
||||
R.id.about -> launchAbout()
|
||||
else -> return@setOnMenuItemClickListener false
|
||||
}
|
||||
return@setOnMenuItemClickListener true
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshMenuItems() {
|
||||
binding.mainMenu.getToolbar().menu.apply {
|
||||
findItem(R.id.more_apps_from_us).isVisible = !resources.getBoolean(org.fossify.commons.R.bool.hide_google_relations)
|
||||
findItem(R.id.show_recycle_bin).isVisible = config.useRecycleBin
|
||||
findItem(R.id.show_archived).isVisible = config.isArchiveAvailable
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, resultData)
|
||||
if (requestCode == MAKE_DEFAULT_APP_REQUEST) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
askPermissions()
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun storeStateVariables() {
|
||||
storedTextColor = getProperTextColor()
|
||||
storedFontSize = config.fontSize
|
||||
}
|
||||
|
||||
private fun updateMenuColors() {
|
||||
updateStatusbarColor(getProperBackgroundColor())
|
||||
binding.mainMenu.updateColors()
|
||||
}
|
||||
|
||||
private fun loadMessages() {
|
||||
if (isQPlus()) {
|
||||
val roleManager = getSystemService(RoleManager::class.java)
|
||||
if (roleManager!!.isRoleAvailable(RoleManager.ROLE_SMS)) {
|
||||
if (roleManager.isRoleHeld(RoleManager.ROLE_SMS)) {
|
||||
askPermissions()
|
||||
} else {
|
||||
val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS)
|
||||
startActivityForResult(intent, MAKE_DEFAULT_APP_REQUEST)
|
||||
}
|
||||
} else {
|
||||
toast(org.fossify.commons.R.string.unknown_error_occurred)
|
||||
finish()
|
||||
}
|
||||
} else {
|
||||
if (Telephony.Sms.getDefaultSmsPackage(this) == packageName) {
|
||||
askPermissions()
|
||||
} else {
|
||||
val intent = Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT)
|
||||
intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, packageName)
|
||||
startActivityForResult(intent, MAKE_DEFAULT_APP_REQUEST)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// while SEND_SMS and READ_SMS permissions are mandatory, READ_CONTACTS is optional. If we don't have it, we just won't be able to show the contact name in some cases
|
||||
private fun askPermissions() {
|
||||
handlePermission(PERMISSION_READ_SMS) {
|
||||
if (it) {
|
||||
handlePermission(PERMISSION_SEND_SMS) {
|
||||
if (it) {
|
||||
handlePermission(PERMISSION_READ_CONTACTS) {
|
||||
handleNotificationPermission { granted ->
|
||||
if (!granted) {
|
||||
PermissionRequiredDialog(
|
||||
activity = this,
|
||||
textId = org.fossify.commons.R.string.allow_notifications_incoming_messages,
|
||||
positiveActionCallback = { openNotificationSettings() })
|
||||
}
|
||||
}
|
||||
|
||||
initMessenger()
|
||||
bus = EventBus.getDefault()
|
||||
try {
|
||||
bus!!.register(this)
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initMessenger() {
|
||||
checkWhatsNewDialog()
|
||||
storeStateVariables()
|
||||
getCachedConversations()
|
||||
|
||||
binding.noConversationsPlaceholder2.setOnClickListener {
|
||||
launchNewConversation()
|
||||
}
|
||||
|
||||
binding.conversationsFab.setOnClickListener {
|
||||
launchNewConversation()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCachedConversations() {
|
||||
ensureBackgroundThread {
|
||||
val conversations = try {
|
||||
conversationsDB.getNonArchived().toMutableList() as ArrayList<Conversation>
|
||||
} catch (e: Exception) {
|
||||
ArrayList()
|
||||
}
|
||||
|
||||
val archived = try {
|
||||
conversationsDB.getAllArchived()
|
||||
} catch (e: Exception) {
|
||||
listOf()
|
||||
}
|
||||
|
||||
updateUnreadCountBadge(conversations)
|
||||
runOnUiThread {
|
||||
setupConversations(conversations, cached = true)
|
||||
getNewConversations((conversations + archived).toMutableList() as ArrayList<Conversation>)
|
||||
}
|
||||
conversations.forEach {
|
||||
clearExpiredScheduledMessages(it.threadId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNewConversations(cachedConversations: ArrayList<Conversation>) {
|
||||
val privateCursor = getMyContactsCursor(favoritesOnly = false, withPhoneNumbersOnly = true)
|
||||
ensureBackgroundThread {
|
||||
val privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor)
|
||||
val conversations = getConversations(privateContacts = privateContacts)
|
||||
|
||||
conversations.forEach { clonedConversation ->
|
||||
val threadIds = cachedConversations.map { it.threadId }
|
||||
if (!threadIds.contains(clonedConversation.threadId)) {
|
||||
conversationsDB.insertOrUpdate(clonedConversation)
|
||||
cachedConversations.add(clonedConversation)
|
||||
}
|
||||
}
|
||||
|
||||
cachedConversations.forEach { cachedConversation ->
|
||||
val threadId = cachedConversation.threadId
|
||||
|
||||
val isTemporaryThread = cachedConversation.isScheduled
|
||||
val isConversationDeleted = !conversations.map { it.threadId }.contains(threadId)
|
||||
if (isConversationDeleted && !isTemporaryThread) {
|
||||
conversationsDB.deleteThreadId(threadId)
|
||||
}
|
||||
|
||||
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)
|
||||
messagesDB.getScheduledThreadMessages(threadId)
|
||||
.forEach { message ->
|
||||
messagesDB.insertOrUpdate(message.copy(threadId = newConversation.threadId))
|
||||
}
|
||||
insertOrUpdateConversation(newConversation, cachedConversation)
|
||||
}
|
||||
}
|
||||
|
||||
cachedConversations.forEach { cachedConv ->
|
||||
val conv = conversations.find {
|
||||
it.threadId == cachedConv.threadId && !Conversation.areContentsTheSame(cachedConv, it)
|
||||
}
|
||||
if (conv != null) {
|
||||
val lastModified = maxOf(cachedConv.date, conv.date)
|
||||
val conversation = conv.copy(date = lastModified)
|
||||
insertOrUpdateConversation(conversation)
|
||||
}
|
||||
}
|
||||
|
||||
val allConversations = conversationsDB.getNonArchived() as ArrayList<Conversation>
|
||||
runOnUiThread {
|
||||
setupConversations(allConversations)
|
||||
}
|
||||
|
||||
if (config.appRunCount == 1) {
|
||||
conversations.map { it.threadId }.forEach { threadId ->
|
||||
val messages = getMessages(threadId, getImageResolutions = false, includeScheduledMessages = false)
|
||||
messages.chunked(30).forEach { currentMessages ->
|
||||
messagesDB.insertMessages(*currentMessages.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getOrCreateConversationsAdapter(): ConversationsAdapter {
|
||||
var currAdapter = binding.conversationsList.adapter
|
||||
if (currAdapter == null) {
|
||||
hideKeyboard()
|
||||
currAdapter = ConversationsAdapter(
|
||||
activity = this,
|
||||
recyclerView = binding.conversationsList,
|
||||
onRefresh = { notifyDatasetChanged() },
|
||||
itemClick = { handleConversationClick(it) }
|
||||
)
|
||||
|
||||
binding.conversationsList.adapter = currAdapter
|
||||
if (areSystemAnimationsEnabled) {
|
||||
binding.conversationsList.scheduleLayoutAnimation()
|
||||
}
|
||||
}
|
||||
return currAdapter as ConversationsAdapter
|
||||
}
|
||||
|
||||
private fun setupConversations(conversations: ArrayList<Conversation>, cached: Boolean = false) {
|
||||
val sortedConversations = conversations.sortedWith(
|
||||
compareByDescending<Conversation> { config.pinnedConversations.contains(it.threadId.toString()) }
|
||||
.thenByDescending { it.date }
|
||||
).toMutableList() as ArrayList<Conversation>
|
||||
|
||||
if (cached && config.appRunCount == 1) {
|
||||
// there are no cached conversations on the first run so we show the loading placeholder and progress until we are done loading from telephony
|
||||
showOrHideProgress(conversations.isEmpty())
|
||||
} else {
|
||||
showOrHideProgress(false)
|
||||
showOrHidePlaceholder(conversations.isEmpty())
|
||||
}
|
||||
|
||||
try {
|
||||
getOrCreateConversationsAdapter().apply {
|
||||
updateConversations(sortedConversations) {
|
||||
if (!cached) {
|
||||
showOrHidePlaceholder(currentList.isEmpty())
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun showOrHideProgress(show: Boolean) {
|
||||
if (show) {
|
||||
binding.conversationsProgressBar.show()
|
||||
binding.noConversationsPlaceholder.beVisible()
|
||||
binding.noConversationsPlaceholder.text = getString(R.string.loading_messages)
|
||||
} else {
|
||||
binding.conversationsProgressBar.hide()
|
||||
binding.noConversationsPlaceholder.beGone()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showOrHidePlaceholder(show: Boolean) {
|
||||
binding.conversationsFastscroller.beGoneIf(show)
|
||||
binding.noConversationsPlaceholder.beVisibleIf(show)
|
||||
binding.noConversationsPlaceholder.text = getString(R.string.no_conversations_found)
|
||||
binding.noConversationsPlaceholder2.beVisibleIf(show)
|
||||
}
|
||||
|
||||
private fun fadeOutSearch() {
|
||||
binding.searchHolder.animate().alpha(0f).setDuration(SHORT_ANIMATION_DURATION).withEndAction {
|
||||
binding.searchHolder.beGone()
|
||||
searchTextChanged("", true)
|
||||
}.start()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun notifyDatasetChanged() {
|
||||
getOrCreateConversationsAdapter().notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private fun handleConversationClick(any: Any) {
|
||||
Intent(this, ThreadActivity::class.java).apply {
|
||||
val conversation = any as Conversation
|
||||
putExtra(THREAD_ID, conversation.threadId)
|
||||
putExtra(THREAD_TITLE, conversation.title)
|
||||
putExtra(WAS_PROTECTION_HANDLED, wasProtectionHandled)
|
||||
startActivity(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchNewConversation() {
|
||||
hideKeyboard()
|
||||
Intent(this, NewConversationActivity::class.java).apply {
|
||||
startActivity(this)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
private fun checkShortcut() {
|
||||
val appIconColor = config.appIconColor
|
||||
if (isNougatMR1Plus() && config.lastHandledShortcutColor != appIconColor) {
|
||||
val newConversation = getCreateNewContactShortcut(appIconColor)
|
||||
|
||||
val manager = getSystemService(ShortcutManager::class.java)
|
||||
try {
|
||||
manager.dynamicShortcuts = listOf(newConversation)
|
||||
config.lastHandledShortcutColor = appIconColor
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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 bmp = drawable.convertToBitmap()
|
||||
|
||||
val intent = Intent(this, NewConversationActivity::class.java)
|
||||
intent.action = Intent.ACTION_VIEW
|
||||
return ShortcutInfo.Builder(this, "new_conversation")
|
||||
.setShortLabel(newEvent)
|
||||
.setLongLabel(newEvent)
|
||||
.setIcon(Icon.createWithBitmap(bmp))
|
||||
.setIntent(intent)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun searchTextChanged(text: String, forceUpdate: Boolean = false) {
|
||||
if (!binding.mainMenu.isSearchOpen && !forceUpdate) {
|
||||
return
|
||||
}
|
||||
|
||||
lastSearchedText = text
|
||||
binding.searchPlaceholder2.beGoneIf(text.length >= 2)
|
||||
if (text.length >= 2) {
|
||||
ensureBackgroundThread {
|
||||
val searchQuery = "%$text%"
|
||||
val messages = messagesDB.getMessagesWithText(searchQuery)
|
||||
val conversations = conversationsDB.getConversationsWithText(searchQuery)
|
||||
if (text == lastSearchedText) {
|
||||
showSearchResults(messages, conversations, text)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.searchPlaceholder.beVisible()
|
||||
binding.searchResultsList.beGone()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSearchResults(messages: List<Message>, conversations: List<Conversation>, searchedText: String) {
|
||||
val searchResults = ArrayList<SearchResult>()
|
||||
conversations.forEach { conversation ->
|
||||
val date = conversation.date.formatDateOrTime(this, true, true)
|
||||
val searchResult = SearchResult(-1, conversation.title, conversation.phoneNumber, date, conversation.threadId, conversation.photoUri)
|
||||
searchResults.add(searchResult)
|
||||
}
|
||||
|
||||
messages.sortedByDescending { it.id }.forEach { message ->
|
||||
var recipient = message.senderName
|
||||
if (recipient.isEmpty() && message.participants.isNotEmpty()) {
|
||||
val participantNames = message.participants.map { it.name }
|
||||
recipient = TextUtils.join(", ", participantNames)
|
||||
}
|
||||
|
||||
val date = message.date.formatDateOrTime(this, true, true)
|
||||
val searchResult = SearchResult(message.id, recipient, message.body, date, message.threadId, message.senderPhotoUri)
|
||||
searchResults.add(searchResult)
|
||||
}
|
||||
|
||||
runOnUiThread {
|
||||
binding.searchResultsList.beVisibleIf(searchResults.isNotEmpty())
|
||||
binding.searchPlaceholder.beVisibleIf(searchResults.isEmpty())
|
||||
|
||||
val currAdapter = binding.searchResultsList.adapter
|
||||
if (currAdapter == null) {
|
||||
SearchResultsAdapter(this, searchResults, binding.searchResultsList, searchedText) {
|
||||
hideKeyboard()
|
||||
Intent(this, ThreadActivity::class.java).apply {
|
||||
putExtra(THREAD_ID, (it as SearchResult).threadId)
|
||||
putExtra(THREAD_TITLE, it.title)
|
||||
putExtra(SEARCHED_MESSAGE_ID, it.messageId)
|
||||
startActivity(this)
|
||||
}
|
||||
}.apply {
|
||||
binding.searchResultsList.adapter = this
|
||||
}
|
||||
} else {
|
||||
(currAdapter as SearchResultsAdapter).updateItems(searchResults, searchedText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchRecycleBin() {
|
||||
hideKeyboard()
|
||||
startActivity(Intent(applicationContext, RecycleBinConversationsActivity::class.java))
|
||||
}
|
||||
|
||||
private fun launchArchivedConversations() {
|
||||
hideKeyboard()
|
||||
startActivity(Intent(applicationContext, ArchivedConversationsActivity::class.java))
|
||||
}
|
||||
|
||||
private fun launchSettings() {
|
||||
hideKeyboard()
|
||||
startActivity(Intent(applicationContext, SettingsActivity::class.java))
|
||||
}
|
||||
|
||||
private fun launchAbout() {
|
||||
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)
|
||||
)
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true)
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun refreshMessages(event: Events.RefreshMessages) {
|
||||
initMessenger()
|
||||
}
|
||||
|
||||
private fun checkWhatsNewDialog() {
|
||||
arrayListOf<Release>().apply {
|
||||
checkWhatsNew(this, BuildConfig.VERSION_CODE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package org.fossify.messages.activities
|
||||
|
||||
import android.os.Bundle
|
||||
import org.fossify.commons.activities.BaseSimpleActivity
|
||||
import org.fossify.commons.extensions.*
|
||||
import org.fossify.commons.helpers.APP_ICON_IDS
|
||||
import org.fossify.commons.helpers.APP_LAUNCHER_NAME
|
||||
import org.fossify.commons.helpers.NavigationIcon
|
||||
import org.fossify.commons.helpers.ensureBackgroundThread
|
||||
import org.fossify.commons.interfaces.RefreshRecyclerViewListener
|
||||
import org.fossify.messages.R
|
||||
import org.fossify.messages.databinding.ActivityManageBlockedKeywordsBinding
|
||||
import org.fossify.messages.dialogs.AddBlockedKeywordDialog
|
||||
import org.fossify.messages.dialogs.ManageBlockedKeywordsAdapter
|
||||
import org.fossify.messages.extensions.config
|
||||
import org.fossify.messages.extensions.toArrayList
|
||||
|
||||
class ManageBlockedKeywordsActivity : BaseSimpleActivity(), RefreshRecyclerViewListener {
|
||||
override fun getAppIconIDs() = intent.getIntegerArrayListExtra(APP_ICON_IDS) ?: ArrayList()
|
||||
|
||||
override fun getAppLauncherName() = intent.getStringExtra(APP_LAUNCHER_NAME) ?: ""
|
||||
|
||||
private val binding by viewBinding(ActivityManageBlockedKeywordsBinding::inflate)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
isMaterialActivity = true
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(binding.root)
|
||||
updateBlockedKeywords()
|
||||
setupOptionsMenu()
|
||||
|
||||
updateMaterialActivityViews(
|
||||
mainCoordinatorLayout = binding.blockKeywordsCoordinator,
|
||||
nestedView = binding.manageBlockedKeywordsList,
|
||||
useTransparentNavigation = true,
|
||||
useTopSearchMenu = false
|
||||
)
|
||||
setupMaterialScrollListener(scrollingView = binding.manageBlockedKeywordsList, toolbar = binding.blockKeywordsToolbar)
|
||||
updateTextColors(binding.manageBlockedKeywordsWrapper)
|
||||
|
||||
binding.manageBlockedKeywordsPlaceholder2.apply {
|
||||
underlineText()
|
||||
setTextColor(getProperPrimaryColor())
|
||||
setOnClickListener {
|
||||
addOrEditBlockedKeyword()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
setupToolbar(binding.blockKeywordsToolbar, NavigationIcon.Arrow)
|
||||
}
|
||||
|
||||
private fun setupOptionsMenu() {
|
||||
binding.blockKeywordsToolbar.setOnMenuItemClickListener { menuItem ->
|
||||
when (menuItem.itemId) {
|
||||
R.id.add_blocked_keyword -> {
|
||||
addOrEditBlockedKeyword()
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun refreshItems() {
|
||||
updateBlockedKeywords()
|
||||
}
|
||||
|
||||
private fun updateBlockedKeywords() {
|
||||
ensureBackgroundThread {
|
||||
val blockedKeywords = config.blockedKeywords
|
||||
runOnUiThread {
|
||||
ManageBlockedKeywordsAdapter(this, blockedKeywords.toArrayList(), this, binding.manageBlockedKeywordsList) {
|
||||
addOrEditBlockedKeyword(it as String)
|
||||
}.apply {
|
||||
binding.manageBlockedKeywordsList.adapter = this
|
||||
}
|
||||
|
||||
binding.manageBlockedKeywordsPlaceholder.beVisibleIf(blockedKeywords.isEmpty())
|
||||
binding.manageBlockedKeywordsPlaceholder2.beVisibleIf(blockedKeywords.isEmpty())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addOrEditBlockedKeyword(keyword: String? = null) {
|
||||
AddBlockedKeywordDialog(this, keyword) {
|
||||
updateBlockedKeywords()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
package org.fossify.messages.activities
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import com.google.gson.Gson
|
||||
import com.reddit.indicatorfastscroll.FastScrollItemIndicator
|
||||
import org.fossify.commons.dialogs.RadioGroupDialog
|
||||
import org.fossify.commons.extensions.*
|
||||
import org.fossify.commons.helpers.*
|
||||
import org.fossify.commons.models.RadioItem
|
||||
import org.fossify.commons.models.SimpleContact
|
||||
import org.fossify.messages.R
|
||||
import org.fossify.messages.adapters.ContactsAdapter
|
||||
import org.fossify.messages.databinding.ActivityNewConversationBinding
|
||||
import org.fossify.messages.databinding.ItemSuggestedContactBinding
|
||||
import org.fossify.messages.extensions.getSuggestedContacts
|
||||
import org.fossify.messages.extensions.getThreadId
|
||||
import org.fossify.messages.helpers.*
|
||||
import org.fossify.messages.messaging.isShortCodeWithLetters
|
||||
import java.net.URLDecoder
|
||||
import java.util.Locale
|
||||
|
||||
class NewConversationActivity : SimpleActivity() {
|
||||
private var allContacts = ArrayList<SimpleContact>()
|
||||
private var privateContacts = ArrayList<SimpleContact>()
|
||||
|
||||
private val binding by viewBinding(ActivityNewConversationBinding::inflate)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
isMaterialActivity = true
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(binding.root)
|
||||
title = getString(R.string.new_conversation)
|
||||
updateTextColors(binding.newConversationHolder)
|
||||
|
||||
updateMaterialActivityViews(
|
||||
mainCoordinatorLayout = binding.newConversationCoordinator,
|
||||
nestedView = binding.contactsList,
|
||||
useTransparentNavigation = true,
|
||||
useTopSearchMenu = false
|
||||
)
|
||||
setupMaterialScrollListener(scrollingView = binding.contactsList, toolbar = binding.newConversationToolbar)
|
||||
|
||||
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
||||
binding.newConversationAddress.requestFocus()
|
||||
|
||||
// READ_CONTACTS permission is not mandatory, but without it we won't be able to show any suggestions during typing
|
||||
handlePermission(PERMISSION_READ_CONTACTS) {
|
||||
initContacts()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
setupToolbar(binding.newConversationToolbar, NavigationIcon.Arrow)
|
||||
binding.noContactsPlaceholder2.setTextColor(getProperPrimaryColor())
|
||||
binding.noContactsPlaceholder2.underlineText()
|
||||
binding.suggestionsLabel.setTextColor(getProperPrimaryColor())
|
||||
}
|
||||
|
||||
private fun initContacts() {
|
||||
if (isThirdPartyIntent()) {
|
||||
return
|
||||
}
|
||||
|
||||
fetchContacts()
|
||||
binding.newConversationAddress.onTextChangeListener { searchString ->
|
||||
val filteredContacts = ArrayList<SimpleContact>()
|
||||
allContacts.forEach { contact ->
|
||||
if (contact.phoneNumbers.any { it.normalizedNumber.contains(searchString, true) } ||
|
||||
contact.name.contains(searchString, true) ||
|
||||
contact.name.contains(searchString.normalizeString(), true) ||
|
||||
contact.name.normalizeString().contains(searchString, true)) {
|
||||
filteredContacts.add(contact)
|
||||
}
|
||||
}
|
||||
|
||||
filteredContacts.sortWith(compareBy { !it.name.startsWith(searchString, true) })
|
||||
setupAdapter(filteredContacts)
|
||||
|
||||
binding.newConversationConfirm.beVisibleIf(searchString.length > 2)
|
||||
}
|
||||
|
||||
binding.newConversationConfirm.applyColorFilter(getProperTextColor())
|
||||
binding.newConversationConfirm.setOnClickListener {
|
||||
val number = binding.newConversationAddress.value
|
||||
if (isShortCodeWithLetters(number)) {
|
||||
binding.newConversationAddress.setText("")
|
||||
toast(R.string.invalid_short_code, length = Toast.LENGTH_LONG)
|
||||
return@setOnClickListener
|
||||
}
|
||||
launchThreadActivity(number, number)
|
||||
}
|
||||
|
||||
binding.noContactsPlaceholder2.setOnClickListener {
|
||||
handlePermission(PERMISSION_READ_CONTACTS) {
|
||||
if (it) {
|
||||
fetchContacts()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val properPrimaryColor = getProperPrimaryColor()
|
||||
binding.contactsLetterFastscroller.textColor = getProperTextColor().getColorStateList()
|
||||
binding.contactsLetterFastscroller.pressedTextColor = properPrimaryColor
|
||||
binding.contactsLetterFastscrollerThumb.setupWithFastScroller(binding.contactsLetterFastscroller)
|
||||
binding.contactsLetterFastscrollerThumb.textColor = properPrimaryColor.getContrastColor()
|
||||
binding.contactsLetterFastscrollerThumb.thumbColor = properPrimaryColor.getColorStateList()
|
||||
}
|
||||
|
||||
private fun isThirdPartyIntent(): Boolean {
|
||||
if ((intent.action == Intent.ACTION_SENDTO || intent.action == Intent.ACTION_SEND || intent.action == Intent.ACTION_VIEW) && intent.dataString != null) {
|
||||
val number = intent.dataString!!.removePrefix("sms:").removePrefix("smsto:").removePrefix("mms").removePrefix("mmsto:").replace("+", "%2b").trim()
|
||||
launchThreadActivity(URLDecoder.decode(number), "")
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun fetchContacts() {
|
||||
fillSuggestedContacts {
|
||||
SimpleContactsHelper(this).getAvailableContacts(false) {
|
||||
allContacts = it
|
||||
|
||||
if (privateContacts.isNotEmpty()) {
|
||||
allContacts.addAll(privateContacts)
|
||||
allContacts.sort()
|
||||
}
|
||||
|
||||
runOnUiThread {
|
||||
setupAdapter(allContacts)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAdapter(contacts: ArrayList<SimpleContact>) {
|
||||
val hasContacts = contacts.isNotEmpty()
|
||||
binding.contactsList.beVisibleIf(hasContacts)
|
||||
binding.noContactsPlaceholder.beVisibleIf(!hasContacts)
|
||||
binding.noContactsPlaceholder2.beVisibleIf(!hasContacts && !hasPermission(PERMISSION_READ_CONTACTS))
|
||||
|
||||
if (!hasContacts) {
|
||||
val placeholderText = if (hasPermission(PERMISSION_READ_CONTACTS)) {
|
||||
org.fossify.commons.R.string.no_contacts_found
|
||||
} else {
|
||||
org.fossify.commons.R.string.no_access_to_contacts
|
||||
}
|
||||
|
||||
binding.noContactsPlaceholder.text = getString(placeholderText)
|
||||
}
|
||||
|
||||
val currAdapter = binding.contactsList.adapter
|
||||
if (currAdapter == null) {
|
||||
ContactsAdapter(this, contacts, binding.contactsList) {
|
||||
hideKeyboard()
|
||||
val contact = it as SimpleContact
|
||||
val phoneNumbers = contact.phoneNumbers
|
||||
if (phoneNumbers.size > 1) {
|
||||
val primaryNumber = contact.phoneNumbers.find { it.isPrimary }
|
||||
if (primaryNumber != null) {
|
||||
launchThreadActivity(primaryNumber.value, contact.name)
|
||||
} else {
|
||||
val items = ArrayList<RadioItem>()
|
||||
phoneNumbers.forEachIndexed { index, phoneNumber ->
|
||||
val type = getPhoneNumberTypeText(phoneNumber.type, phoneNumber.label)
|
||||
items.add(RadioItem(index, "${phoneNumber.normalizedNumber} ($type)", phoneNumber.normalizedNumber))
|
||||
}
|
||||
|
||||
RadioGroupDialog(this, items) {
|
||||
launchThreadActivity(it as String, contact.name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
launchThreadActivity(phoneNumbers.first().normalizedNumber, contact.name)
|
||||
}
|
||||
}.apply {
|
||||
binding.contactsList.adapter = this
|
||||
}
|
||||
|
||||
if (areSystemAnimationsEnabled) {
|
||||
binding.contactsList.scheduleLayoutAnimation()
|
||||
}
|
||||
} else {
|
||||
(currAdapter as ContactsAdapter).updateContacts(contacts)
|
||||
}
|
||||
|
||||
setupLetterFastscroller(contacts)
|
||||
}
|
||||
|
||||
private fun fillSuggestedContacts(callback: () -> Unit) {
|
||||
val privateCursor = getMyContactsCursor(false, true)
|
||||
ensureBackgroundThread {
|
||||
privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor)
|
||||
val suggestions = getSuggestedContacts(privateContacts)
|
||||
runOnUiThread {
|
||||
binding.suggestionsHolder.removeAllViews()
|
||||
if (suggestions.isEmpty()) {
|
||||
binding.suggestionsLabel.beGone()
|
||||
binding.suggestionsScrollview.beGone()
|
||||
} else {
|
||||
binding.suggestionsLabel.beVisible()
|
||||
binding.suggestionsScrollview.beVisible()
|
||||
suggestions.forEach {
|
||||
val contact = it
|
||||
ItemSuggestedContactBinding.inflate(layoutInflater).apply {
|
||||
suggestedContactName.text = contact.name
|
||||
suggestedContactName.setTextColor(getProperTextColor())
|
||||
|
||||
if (!isDestroyed) {
|
||||
SimpleContactsHelper(this@NewConversationActivity).loadContactImage(contact.photoUri, suggestedContactImage, contact.name)
|
||||
binding.suggestionsHolder.addView(root)
|
||||
root.setOnClickListener {
|
||||
launchThreadActivity(contact.phoneNumbers.first().normalizedNumber, contact.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupLetterFastscroller(contacts: ArrayList<SimpleContact>) {
|
||||
binding.contactsLetterFastscroller.setupWithRecyclerView(binding.contactsList, { position ->
|
||||
try {
|
||||
val name = contacts[position].name
|
||||
val character = if (name.isNotEmpty()) name.substring(0, 1) else ""
|
||||
FastScrollItemIndicator.Text(character.uppercase(Locale.getDefault()).normalizeString())
|
||||
} catch (e: Exception) {
|
||||
FastScrollItemIndicator.Text("")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun launchThreadActivity(phoneNumber: String, name: String) {
|
||||
hideKeyboard()
|
||||
val text = intent.getStringExtra(Intent.EXTRA_TEXT) ?: intent.getStringExtra("sms_body") ?: ""
|
||||
val numbers = phoneNumber.split(";").toSet()
|
||||
val number = if (numbers.size == 1) phoneNumber else Gson().toJson(numbers)
|
||||
Intent(this, ThreadActivity::class.java).apply {
|
||||
putExtra(THREAD_ID, getThreadId(numbers))
|
||||
putExtra(THREAD_TITLE, name)
|
||||
putExtra(THREAD_TEXT, text)
|
||||
putExtra(THREAD_NUMBER, number)
|
||||
|
||||
if (intent.action == Intent.ACTION_SEND && intent.extras?.containsKey(Intent.EXTRA_STREAM) == true) {
|
||||
val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
||||
putExtra(THREAD_ATTACHMENT_URI, uri?.toString())
|
||||
} else if (intent.action == Intent.ACTION_SEND_MULTIPLE && intent.extras?.containsKey(Intent.EXTRA_STREAM) == true) {
|
||||
val uris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
|
||||
putExtra(THREAD_ATTACHMENT_URIS, uris)
|
||||
}
|
||||
|
||||
startActivity(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
package org.fossify.messages.activities
|
||||
|
||||
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.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
|
||||
import org.fossify.messages.databinding.ActivityRecycleBinConversationsBinding
|
||||
import org.fossify.messages.extensions.config
|
||||
import org.fossify.messages.extensions.conversationsDB
|
||||
import org.fossify.messages.extensions.emptyMessagesRecycleBin
|
||||
import org.fossify.messages.helpers.IS_RECYCLE_BIN
|
||||
import org.fossify.messages.helpers.THREAD_ID
|
||||
import org.fossify.messages.helpers.THREAD_TITLE
|
||||
import org.fossify.messages.models.Conversation
|
||||
import org.fossify.messages.models.Events
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
|
||||
class RecycleBinConversationsActivity : SimpleActivity() {
|
||||
private var bus: EventBus? = null
|
||||
private val binding by viewBinding(ActivityRecycleBinConversationsBinding::inflate)
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
isMaterialActivity = true
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(binding.root)
|
||||
setupOptionsMenu()
|
||||
|
||||
updateMaterialActivityViews(
|
||||
mainCoordinatorLayout = binding.recycleBinCoordinator,
|
||||
nestedView = binding.conversationsList,
|
||||
useTransparentNavigation = true,
|
||||
useTopSearchMenu = false
|
||||
)
|
||||
setupMaterialScrollListener(scrollingView = binding.conversationsList, toolbar = binding.recycleBinToolbar)
|
||||
|
||||
loadRecycleBinConversations()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
setupToolbar(binding.recycleBinToolbar, NavigationIcon.Arrow)
|
||||
updateMenuColors()
|
||||
|
||||
loadRecycleBinConversations()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
bus?.unregister(this)
|
||||
}
|
||||
|
||||
private fun setupOptionsMenu() {
|
||||
binding.recycleBinToolbar.inflateMenu(R.menu.recycle_bin_menu)
|
||||
binding.recycleBinToolbar.setOnMenuItemClickListener { menuItem ->
|
||||
when (menuItem.itemId) {
|
||||
R.id.empty_recycle_bin -> removeAll()
|
||||
else -> return@setOnMenuItemClickListener false
|
||||
}
|
||||
return@setOnMenuItemClickListener true
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateOptionsMenu(conversations: ArrayList<Conversation>) {
|
||||
binding.recycleBinToolbar.menu.apply {
|
||||
findItem(R.id.empty_recycle_bin).isVisible = conversations.isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateMenuColors() {
|
||||
updateStatusbarColor(getProperBackgroundColor())
|
||||
}
|
||||
|
||||
private fun loadRecycleBinConversations() {
|
||||
ensureBackgroundThread {
|
||||
val conversations = try {
|
||||
conversationsDB.getAllWithMessagesInRecycleBin().toMutableList() as ArrayList<Conversation>
|
||||
} catch (e: Exception) {
|
||||
ArrayList()
|
||||
}
|
||||
|
||||
runOnUiThread {
|
||||
setupConversations(conversations)
|
||||
}
|
||||
}
|
||||
|
||||
bus = EventBus.getDefault()
|
||||
try {
|
||||
bus!!.register(this)
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeAll() {
|
||||
ConfirmationDialog(
|
||||
activity = this,
|
||||
message = "",
|
||||
messageId = R.string.empty_recycle_bin_messages_confirmation,
|
||||
positive = org.fossify.commons.R.string.yes,
|
||||
negative = org.fossify.commons.R.string.no
|
||||
) {
|
||||
ensureBackgroundThread {
|
||||
emptyMessagesRecycleBin()
|
||||
loadRecycleBinConversations()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getOrCreateConversationsAdapter(): RecycleBinConversationsAdapter {
|
||||
var currAdapter = binding.conversationsList.adapter
|
||||
if (currAdapter == null) {
|
||||
hideKeyboard()
|
||||
currAdapter = RecycleBinConversationsAdapter(
|
||||
activity = this,
|
||||
recyclerView = binding.conversationsList,
|
||||
onRefresh = { notifyDatasetChanged() },
|
||||
itemClick = { handleConversationClick(it) }
|
||||
)
|
||||
|
||||
binding.conversationsList.adapter = currAdapter
|
||||
if (areSystemAnimationsEnabled) {
|
||||
binding.conversationsList.scheduleLayoutAnimation()
|
||||
}
|
||||
}
|
||||
return currAdapter as RecycleBinConversationsAdapter
|
||||
}
|
||||
|
||||
private fun setupConversations(conversations: ArrayList<Conversation>) {
|
||||
val sortedConversations = conversations.sortedWith(
|
||||
compareByDescending<Conversation> { config.pinnedConversations.contains(it.threadId.toString()) }
|
||||
.thenByDescending { it.date }
|
||||
).toMutableList() as ArrayList<Conversation>
|
||||
|
||||
showOrHidePlaceholder(conversations.isEmpty())
|
||||
updateOptionsMenu(conversations)
|
||||
|
||||
try {
|
||||
getOrCreateConversationsAdapter().apply {
|
||||
updateConversations(sortedConversations)
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun showOrHidePlaceholder(show: Boolean) {
|
||||
binding.conversationsFastscroller.beGoneIf(show)
|
||||
binding.noConversationsPlaceholder.beVisibleIf(show)
|
||||
binding.noConversationsPlaceholder.text = getString(R.string.no_conversations_found)
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun notifyDatasetChanged() {
|
||||
getOrCreateConversationsAdapter().notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private fun handleConversationClick(any: Any) {
|
||||
Intent(this, ThreadActivity::class.java).apply {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun refreshMessages(event: Events.RefreshMessages) {
|
||||
loadRecycleBinConversations()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,418 @@
|
|||
package org.fossify.messages.activities
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.fossify.commons.activities.ManageBlockedNumbersActivity
|
||||
import org.fossify.commons.dialogs.*
|
||||
import org.fossify.commons.extensions.*
|
||||
import org.fossify.commons.helpers.*
|
||||
import org.fossify.commons.models.RadioItem
|
||||
import org.fossify.messages.R
|
||||
import org.fossify.messages.databinding.ActivitySettingsBinding
|
||||
import org.fossify.messages.dialogs.ExportMessagesDialog
|
||||
import org.fossify.messages.extensions.config
|
||||
import org.fossify.messages.extensions.emptyMessagesRecycleBin
|
||||
import org.fossify.messages.extensions.messagesDB
|
||||
import org.fossify.messages.helpers.*
|
||||
import java.util.Locale
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class SettingsActivity : SimpleActivity() {
|
||||
private var blockedNumbersAtPause = -1
|
||||
private var recycleBinMessages = 0
|
||||
private val messagesFileType = "application/json"
|
||||
private val messageImportFileTypes = listOf("application/json", "application/xml", "text/xml")
|
||||
|
||||
private val binding by viewBinding(ActivitySettingsBinding::inflate)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
isMaterialActivity = true
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(binding.root)
|
||||
|
||||
updateMaterialActivityViews(
|
||||
mainCoordinatorLayout = binding.settingsCoordinator,
|
||||
nestedView = binding.settingsHolder,
|
||||
useTransparentNavigation = true,
|
||||
useTopSearchMenu = false
|
||||
)
|
||||
setupMaterialScrollListener(scrollingView = binding.settingsNestedScrollview, toolbar = binding.settingsToolbar)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
setupToolbar(binding.settingsToolbar, NavigationIcon.Arrow)
|
||||
|
||||
setupPurchaseThankYou()
|
||||
setupCustomizeColors()
|
||||
setupCustomizeNotifications()
|
||||
setupUseEnglish()
|
||||
setupLanguage()
|
||||
setupManageBlockedNumbers()
|
||||
setupManageBlockedKeywords()
|
||||
setupChangeDateTimeFormat()
|
||||
setupFontSize()
|
||||
setupShowCharacterCounter()
|
||||
setupUseSimpleCharacters()
|
||||
setupSendOnEnter()
|
||||
setupEnableDeliveryReports()
|
||||
setupSendLongMessageAsMMS()
|
||||
setupGroupMessageAsMMS()
|
||||
setupLockScreenVisibility()
|
||||
setupMMSFileSizeLimit()
|
||||
setupUseRecycleBin()
|
||||
setupEmptyRecycleBin()
|
||||
setupAppPasswordProtection()
|
||||
setupMessagesExport()
|
||||
setupMessagesImport()
|
||||
updateTextColors(binding.settingsNestedScrollview)
|
||||
|
||||
if (blockedNumbersAtPause != -1 && blockedNumbersAtPause != getBlockedNumbers().hashCode()) {
|
||||
refreshMessages()
|
||||
}
|
||||
|
||||
arrayOf(
|
||||
binding.settingsColorCustomizationSectionLabel,
|
||||
binding.settingsGeneralSettingsLabel,
|
||||
binding.settingsOutgoingMessagesLabel,
|
||||
binding.settingsNotificationsLabel,
|
||||
binding.settingsRecycleBinLabel,
|
||||
binding.settingsSecurityLabel,
|
||||
binding.settingsMigratingLabel
|
||||
).forEach {
|
||||
it.setTextColor(getProperPrimaryColor())
|
||||
}
|
||||
}
|
||||
|
||||
private val getContent = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
|
||||
if (uri != null) {
|
||||
MessagesImporter(this).importMessages(uri)
|
||||
}
|
||||
}
|
||||
|
||||
private val saveDocument = registerForActivityResult(ActivityResultContracts.CreateDocument(messagesFileType)) { uri ->
|
||||
if (uri != null) {
|
||||
toast(org.fossify.commons.R.string.exporting)
|
||||
exportMessages(uri)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupMessagesExport() {
|
||||
binding.settingsExportMessagesHolder.setOnClickListener {
|
||||
ExportMessagesDialog(this) { fileName ->
|
||||
saveDocument.launch(fileName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupMessagesImport() {
|
||||
binding.settingsImportMessagesHolder.setOnClickListener {
|
||||
getContent.launch(messageImportFileTypes.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportMessages(uri: Uri) {
|
||||
ensureBackgroundThread {
|
||||
try {
|
||||
MessagesReader(this).getMessagesToExport(config.exportSms, config.exportMms) { messagesToExport ->
|
||||
if (messagesToExport.isEmpty()) {
|
||||
toast(org.fossify.commons.R.string.no_entries_for_exporting)
|
||||
return@getMessagesToExport
|
||||
}
|
||||
val json = Json { encodeDefaults = true }
|
||||
val jsonString = json.encodeToString(messagesToExport)
|
||||
val outputStream = contentResolver.openOutputStream(uri)!!
|
||||
|
||||
outputStream.use {
|
||||
it.write(jsonString.toByteArray())
|
||||
}
|
||||
toast(org.fossify.commons.R.string.exporting_successful)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
showErrorToast(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
blockedNumbersAtPause = getBlockedNumbers().hashCode()
|
||||
}
|
||||
|
||||
private fun setupPurchaseThankYou() = binding.apply {
|
||||
settingsPurchaseThankYouHolder.beGoneIf(isOrWasThankYouInstalled())
|
||||
settingsPurchaseThankYouHolder.setOnClickListener {
|
||||
launchPurchaseThankYouIntent()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCustomizeColors() = binding.apply {
|
||||
settingsColorCustomizationLabel.text = getCustomizeColorsString()
|
||||
settingsColorCustomizationHolder.setOnClickListener {
|
||||
handleCustomizeColorsClick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCustomizeNotifications() = binding.apply {
|
||||
settingsCustomizeNotificationsHolder.beVisibleIf(isOreoPlus())
|
||||
settingsCustomizeNotificationsHolder.setOnClickListener {
|
||||
launchCustomizeNotificationsIntent()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupUseEnglish() = binding.apply {
|
||||
settingsUseEnglishHolder.beVisibleIf((config.wasUseEnglishToggled || Locale.getDefault().language != "en") && !isTiramisuPlus())
|
||||
settingsUseEnglish.isChecked = config.useEnglish
|
||||
settingsUseEnglishHolder.setOnClickListener {
|
||||
settingsUseEnglish.toggle()
|
||||
config.useEnglish = settingsUseEnglish.isChecked
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupLanguage() = binding.apply {
|
||||
settingsLanguage.text = Locale.getDefault().displayLanguage
|
||||
settingsLanguageHolder.beVisibleIf(isTiramisuPlus())
|
||||
settingsLanguageHolder.setOnClickListener {
|
||||
launchChangeAppLanguageIntent()
|
||||
}
|
||||
}
|
||||
|
||||
// support for device-wise blocking came on Android 7, rely only on that
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
private fun setupManageBlockedNumbers() = binding.apply {
|
||||
settingsManageBlockedNumbers.text = addLockedLabelIfNeeded(org.fossify.commons.R.string.manage_blocked_numbers)
|
||||
settingsManageBlockedNumbersHolder.beVisibleIf(isNougatPlus())
|
||||
|
||||
settingsManageBlockedNumbersHolder.setOnClickListener {
|
||||
if (isOrWasThankYouInstalled()) {
|
||||
Intent(this@SettingsActivity, ManageBlockedNumbersActivity::class.java).apply {
|
||||
startActivity(this)
|
||||
}
|
||||
} else {
|
||||
FeatureLockedDialog(this@SettingsActivity) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupManageBlockedKeywords() = binding.apply {
|
||||
settingsManageBlockedKeywords.text = addLockedLabelIfNeeded(R.string.manage_blocked_keywords)
|
||||
|
||||
settingsManageBlockedKeywordsHolder.setOnClickListener {
|
||||
if (isOrWasThankYouInstalled()) {
|
||||
Intent(this@SettingsActivity, ManageBlockedKeywordsActivity::class.java).apply {
|
||||
startActivity(this)
|
||||
}
|
||||
} else {
|
||||
FeatureLockedDialog(this@SettingsActivity) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupChangeDateTimeFormat() = binding.apply {
|
||||
settingsChangeDateTimeFormatHolder.setOnClickListener {
|
||||
ChangeDateTimeFormatDialog(this@SettingsActivity) {
|
||||
refreshMessages()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupFontSize() = binding.apply {
|
||||
settingsFontSize.text = getFontSizeText()
|
||||
settingsFontSizeHolder.setOnClickListener {
|
||||
val items = arrayListOf(
|
||||
RadioItem(FONT_SIZE_SMALL, getString(org.fossify.commons.R.string.small)),
|
||||
RadioItem(FONT_SIZE_MEDIUM, getString(org.fossify.commons.R.string.medium)),
|
||||
RadioItem(FONT_SIZE_LARGE, getString(org.fossify.commons.R.string.large)),
|
||||
RadioItem(FONT_SIZE_EXTRA_LARGE, getString(org.fossify.commons.R.string.extra_large))
|
||||
)
|
||||
|
||||
RadioGroupDialog(this@SettingsActivity, items, config.fontSize) {
|
||||
config.fontSize = it as Int
|
||||
settingsFontSize.text = getFontSizeText()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupShowCharacterCounter() = binding.apply {
|
||||
settingsShowCharacterCounter.isChecked = config.showCharacterCounter
|
||||
settingsShowCharacterCounterHolder.setOnClickListener {
|
||||
settingsShowCharacterCounter.toggle()
|
||||
config.showCharacterCounter = settingsShowCharacterCounter.isChecked
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupUseSimpleCharacters() = binding.apply {
|
||||
settingsUseSimpleCharacters.isChecked = config.useSimpleCharacters
|
||||
settingsUseSimpleCharactersHolder.setOnClickListener {
|
||||
settingsUseSimpleCharacters.toggle()
|
||||
config.useSimpleCharacters = settingsUseSimpleCharacters.isChecked
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSendOnEnter() = binding.apply {
|
||||
settingsSendOnEnter.isChecked = config.sendOnEnter
|
||||
settingsSendOnEnterHolder.setOnClickListener {
|
||||
settingsSendOnEnter.toggle()
|
||||
config.sendOnEnter = settingsSendOnEnter.isChecked
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupEnableDeliveryReports() = binding.apply {
|
||||
settingsEnableDeliveryReports.isChecked = config.enableDeliveryReports
|
||||
settingsEnableDeliveryReportsHolder.setOnClickListener {
|
||||
settingsEnableDeliveryReports.toggle()
|
||||
config.enableDeliveryReports = settingsEnableDeliveryReports.isChecked
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSendLongMessageAsMMS() = binding.apply {
|
||||
settingsSendLongMessageMms.isChecked = config.sendLongMessageMMS
|
||||
settingsSendLongMessageMmsHolder.setOnClickListener {
|
||||
settingsSendLongMessageMms.toggle()
|
||||
config.sendLongMessageMMS = settingsSendLongMessageMms.isChecked
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupGroupMessageAsMMS() = binding.apply {
|
||||
settingsSendGroupMessageMms.isChecked = config.sendGroupMessageMMS
|
||||
settingsSendGroupMessageMmsHolder.setOnClickListener {
|
||||
settingsSendGroupMessageMms.toggle()
|
||||
config.sendGroupMessageMMS = settingsSendGroupMessageMms.isChecked
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupLockScreenVisibility() = binding.apply {
|
||||
settingsLockScreenVisibility.text = getLockScreenVisibilityText()
|
||||
settingsLockScreenVisibilityHolder.setOnClickListener {
|
||||
val items = arrayListOf(
|
||||
RadioItem(LOCK_SCREEN_SENDER_MESSAGE, getString(R.string.sender_and_message)),
|
||||
RadioItem(LOCK_SCREEN_SENDER, getString(R.string.sender_only)),
|
||||
RadioItem(LOCK_SCREEN_NOTHING, getString(org.fossify.commons.R.string.nothing)),
|
||||
)
|
||||
|
||||
RadioGroupDialog(this@SettingsActivity, items, config.lockScreenVisibilitySetting) {
|
||||
config.lockScreenVisibilitySetting = it as Int
|
||||
settingsLockScreenVisibility.text = getLockScreenVisibilityText()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLockScreenVisibilityText() = getString(
|
||||
when (config.lockScreenVisibilitySetting) {
|
||||
LOCK_SCREEN_SENDER_MESSAGE -> R.string.sender_and_message
|
||||
LOCK_SCREEN_SENDER -> R.string.sender_only
|
||||
else -> org.fossify.commons.R.string.nothing
|
||||
}
|
||||
)
|
||||
|
||||
private fun setupMMSFileSizeLimit() = binding.apply {
|
||||
settingsMmsFileSizeLimit.text = getMMSFileLimitText()
|
||||
settingsMmsFileSizeLimitHolder.setOnClickListener {
|
||||
val items = arrayListOf(
|
||||
RadioItem(7, getString(R.string.mms_file_size_limit_none), FILE_SIZE_NONE),
|
||||
RadioItem(6, getString(R.string.mms_file_size_limit_2mb), FILE_SIZE_2_MB),
|
||||
RadioItem(5, getString(R.string.mms_file_size_limit_1mb), FILE_SIZE_1_MB),
|
||||
RadioItem(4, getString(R.string.mms_file_size_limit_600kb), FILE_SIZE_600_KB),
|
||||
RadioItem(3, getString(R.string.mms_file_size_limit_300kb), FILE_SIZE_300_KB),
|
||||
RadioItem(2, getString(R.string.mms_file_size_limit_200kb), FILE_SIZE_200_KB),
|
||||
RadioItem(1, getString(R.string.mms_file_size_limit_100kb), FILE_SIZE_100_KB),
|
||||
)
|
||||
|
||||
val checkedItemId = items.find { it.value == config.mmsFileSizeLimit }?.id ?: 7
|
||||
RadioGroupDialog(this@SettingsActivity, items, checkedItemId) {
|
||||
config.mmsFileSizeLimit = it as Long
|
||||
settingsMmsFileSizeLimit.text = getMMSFileLimitText()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupUseRecycleBin() = binding.apply {
|
||||
updateRecycleBinButtons()
|
||||
settingsUseRecycleBin.isChecked = config.useRecycleBin
|
||||
settingsUseRecycleBinHolder.setOnClickListener {
|
||||
settingsUseRecycleBin.toggle()
|
||||
config.useRecycleBin = settingsUseRecycleBin.isChecked
|
||||
updateRecycleBinButtons()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateRecycleBinButtons() = binding.apply {
|
||||
settingsEmptyRecycleBinHolder.beVisibleIf(config.useRecycleBin)
|
||||
}
|
||||
|
||||
private fun setupEmptyRecycleBin() = binding.apply {
|
||||
ensureBackgroundThread {
|
||||
recycleBinMessages = messagesDB.getArchivedCount()
|
||||
runOnUiThread {
|
||||
settingsEmptyRecycleBinSize.text =
|
||||
resources.getQuantityString(R.plurals.delete_messages, recycleBinMessages, recycleBinMessages)
|
||||
}
|
||||
}
|
||||
|
||||
settingsEmptyRecycleBinHolder.setOnClickListener {
|
||||
if (recycleBinMessages == 0) {
|
||||
toast(org.fossify.commons.R.string.recycle_bin_empty)
|
||||
} else {
|
||||
ConfirmationDialog(
|
||||
activity = this@SettingsActivity,
|
||||
message = "",
|
||||
messageId = R.string.empty_recycle_bin_messages_confirmation,
|
||||
positive = org.fossify.commons.R.string.yes,
|
||||
negative = org.fossify.commons.R.string.no
|
||||
) {
|
||||
ensureBackgroundThread {
|
||||
emptyMessagesRecycleBin()
|
||||
}
|
||||
recycleBinMessages = 0
|
||||
settingsEmptyRecycleBinSize.text =
|
||||
resources.getQuantityString(R.plurals.delete_messages, recycleBinMessages, recycleBinMessages)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAppPasswordProtection() = binding.apply {
|
||||
settingsAppPasswordProtection.isChecked = config.isAppPasswordProtectionOn
|
||||
settingsAppPasswordProtectionHolder.setOnClickListener {
|
||||
val tabToShow = if (config.isAppPasswordProtectionOn) config.appProtectionType else SHOW_ALL_TABS
|
||||
SecurityDialog(this@SettingsActivity, config.appPasswordHash, tabToShow) { hash, type, success ->
|
||||
if (success) {
|
||||
val hasPasswordProtection = config.isAppPasswordProtectionOn
|
||||
settingsAppPasswordProtection.isChecked = !hasPasswordProtection
|
||||
config.isAppPasswordProtectionOn = !hasPasswordProtection
|
||||
config.appPasswordHash = if (hasPasswordProtection) "" else hash
|
||||
config.appProtectionType = type
|
||||
|
||||
if (config.isAppPasswordProtectionOn) {
|
||||
val confirmationTextId = if (config.appProtectionType == PROTECTION_FINGERPRINT) {
|
||||
org.fossify.commons.R.string.fingerprint_setup_successfully
|
||||
} else {
|
||||
org.fossify.commons.R.string.protection_setup_successfully
|
||||
}
|
||||
|
||||
ConfirmationDialog(this@SettingsActivity, "", confirmationTextId, org.fossify.commons.R.string.ok, 0) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMMSFileLimitText() = getString(
|
||||
when (config.mmsFileSizeLimit) {
|
||||
FILE_SIZE_100_KB -> R.string.mms_file_size_limit_100kb
|
||||
FILE_SIZE_200_KB -> R.string.mms_file_size_limit_200kb
|
||||
FILE_SIZE_300_KB -> R.string.mms_file_size_limit_300kb
|
||||
FILE_SIZE_600_KB -> R.string.mms_file_size_limit_600kb
|
||||
FILE_SIZE_1_MB -> R.string.mms_file_size_limit_1mb
|
||||
FILE_SIZE_2_MB -> R.string.mms_file_size_limit_2mb
|
||||
else -> R.string.mms_file_size_limit_none
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package org.fossify.messages.activities
|
||||
|
||||
import org.fossify.commons.activities.BaseSimpleActivity
|
||||
import org.fossify.messages.R
|
||||
|
||||
open class SimpleActivity : BaseSimpleActivity() {
|
||||
override fun getAppIconIDs() = arrayListOf(
|
||||
R.mipmap.ic_launcher_red,
|
||||
R.mipmap.ic_launcher_pink,
|
||||
R.mipmap.ic_launcher_purple,
|
||||
R.mipmap.ic_launcher_deep_purple,
|
||||
R.mipmap.ic_launcher_indigo,
|
||||
R.mipmap.ic_launcher_blue,
|
||||
R.mipmap.ic_launcher_light_blue,
|
||||
R.mipmap.ic_launcher_cyan,
|
||||
R.mipmap.ic_launcher_teal,
|
||||
R.mipmap.ic_launcher,
|
||||
R.mipmap.ic_launcher_light_green,
|
||||
R.mipmap.ic_launcher_lime,
|
||||
R.mipmap.ic_launcher_yellow,
|
||||
R.mipmap.ic_launcher_amber,
|
||||
R.mipmap.ic_launcher_orange,
|
||||
R.mipmap.ic_launcher_deep_orange,
|
||||
R.mipmap.ic_launcher_brown,
|
||||
R.mipmap.ic_launcher_blue_grey,
|
||||
R.mipmap.ic_launcher_grey_black
|
||||
)
|
||||
|
||||
override fun getAppLauncherName() = getString(R.string.app_launcher_name)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package org.fossify.messages.activities
|
||||
|
||||
import android.content.Intent
|
||||
import org.fossify.commons.activities.BaseSplashActivity
|
||||
|
||||
class SplashActivity : BaseSplashActivity() {
|
||||
override fun initActivity() {
|
||||
startActivity(Intent(this, MainActivity::class.java))
|
||||
finish()
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,89 @@
|
|||
package org.fossify.messages.activities
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import ezvcard.VCard
|
||||
import ezvcard.property.Email
|
||||
import ezvcard.property.Telephone
|
||||
import org.fossify.commons.extensions.normalizePhoneNumber
|
||||
import org.fossify.commons.extensions.sendEmailIntent
|
||||
import org.fossify.commons.extensions.viewBinding
|
||||
import org.fossify.commons.helpers.NavigationIcon
|
||||
import org.fossify.messages.R
|
||||
import org.fossify.messages.adapters.VCardViewerAdapter
|
||||
import org.fossify.messages.databinding.ActivityVcardViewerBinding
|
||||
import org.fossify.messages.extensions.dialNumber
|
||||
import org.fossify.messages.helpers.EXTRA_VCARD_URI
|
||||
import org.fossify.messages.helpers.parseVCardFromUri
|
||||
import org.fossify.messages.models.VCardPropertyWrapper
|
||||
import org.fossify.messages.models.VCardWrapper
|
||||
|
||||
class VCardViewerActivity : SimpleActivity() {
|
||||
|
||||
private val binding by viewBinding(ActivityVcardViewerBinding::inflate)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
isMaterialActivity = true
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(binding.root)
|
||||
|
||||
updateMaterialActivityViews(binding.vcardViewerCoordinator, binding.contactsList, useTransparentNavigation = true, useTopSearchMenu = false)
|
||||
setupMaterialScrollListener(binding.contactsList, binding.vcardToolbar)
|
||||
|
||||
val vCardUri = intent.getParcelableExtra(EXTRA_VCARD_URI) as? Uri
|
||||
if (vCardUri != null) {
|
||||
setupOptionsMenu(vCardUri)
|
||||
parseVCardFromUri(this, vCardUri) {
|
||||
runOnUiThread {
|
||||
setupContactsList(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
setupToolbar(binding.vcardToolbar, NavigationIcon.Arrow)
|
||||
}
|
||||
|
||||
private fun setupOptionsMenu(vCardUri: Uri) {
|
||||
binding.vcardToolbar.setOnMenuItemClickListener { menuItem ->
|
||||
when (menuItem.itemId) {
|
||||
R.id.add_contact -> {
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
val mimetype = contentResolver.getType(vCardUri)
|
||||
setDataAndType(vCardUri, mimetype?.lowercase())
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
else -> return@setOnMenuItemClickListener false
|
||||
}
|
||||
return@setOnMenuItemClickListener true
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupContactsList(vCards: List<VCard>) {
|
||||
val items = prepareData(vCards)
|
||||
val adapter = VCardViewerAdapter(this, items.toMutableList()) { item ->
|
||||
val property = item as? VCardPropertyWrapper
|
||||
if (property != null) {
|
||||
handleClick(item)
|
||||
}
|
||||
}
|
||||
binding.contactsList.adapter = adapter
|
||||
}
|
||||
|
||||
private fun handleClick(property: VCardPropertyWrapper) {
|
||||
when (property.property) {
|
||||
is Telephone -> dialNumber(property.value.normalizePhoneNumber())
|
||||
is Email -> sendEmailIntent(property.value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareData(vCards: List<VCard>): List<VCardWrapper> {
|
||||
return vCards.map { vCard -> VCardWrapper.from(this, vCard) }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue