Merge branch 'master' into enter-to-send
This commit is contained in:
commit
56ea66634d
99 changed files with 2358 additions and 599 deletions
|
|
@ -0,0 +1,152 @@
|
|||
package com.simplemobiletools.smsmessenger.helpers
|
||||
|
||||
import android.app.Activity
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
|
||||
import com.simplemobiletools.smsmessenger.R
|
||||
import com.simplemobiletools.smsmessenger.extensions.*
|
||||
import kotlinx.android.synthetic.main.item_attachment_document.view.*
|
||||
import kotlinx.android.synthetic.main.item_attachment_vcard.view.*
|
||||
import kotlinx.android.synthetic.main.item_attachment_vcard_preview.view.*
|
||||
import kotlinx.android.synthetic.main.item_remove_attachment_button.view.*
|
||||
|
||||
fun View.setupDocumentPreview(
|
||||
uri: Uri,
|
||||
title: String,
|
||||
mimeType: String,
|
||||
attachment: Boolean = false,
|
||||
onClick: (() -> Unit)? = null,
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
onRemoveButtonClicked: (() -> Unit)? = null
|
||||
) {
|
||||
if (title.isNotEmpty()) {
|
||||
filename.text = title
|
||||
}
|
||||
|
||||
try {
|
||||
val size = context.getFileSizeFromUri(uri)
|
||||
file_size.beVisible()
|
||||
file_size.text = size.formatSize()
|
||||
} catch (e: Exception) {
|
||||
file_size.beGone()
|
||||
}
|
||||
|
||||
val textColor = context.getProperTextColor()
|
||||
val primaryColor = context.getProperPrimaryColor()
|
||||
|
||||
document_attachment_holder.background.applyColorFilter(textColor)
|
||||
filename.setTextColor(textColor)
|
||||
file_size.setTextColor(textColor)
|
||||
|
||||
icon.setImageResource(getIconResourceForMimeType(mimeType))
|
||||
icon.background.setTint(primaryColor)
|
||||
document_attachment_holder.background.applyColorFilter(primaryColor.darkenColor())
|
||||
|
||||
if (attachment) {
|
||||
remove_attachment_button.apply {
|
||||
beVisible()
|
||||
background.applyColorFilter(primaryColor)
|
||||
if (onRemoveButtonClicked != null) {
|
||||
setOnClickListener {
|
||||
onRemoveButtonClicked.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document_attachment_holder.setOnClickListener {
|
||||
onClick?.invoke()
|
||||
}
|
||||
document_attachment_holder.setOnLongClickListener {
|
||||
onLongClick?.invoke()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fun View.setupVCardPreview(
|
||||
activity: Activity,
|
||||
uri: Uri,
|
||||
attachment: Boolean = false,
|
||||
onClick: (() -> Unit)? = null,
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
onRemoveButtonClicked: (() -> Unit)? = null,
|
||||
) {
|
||||
val textColor = activity.getProperTextColor()
|
||||
val primaryColor = activity.getProperPrimaryColor()
|
||||
|
||||
vcard_attachment_holder.background.applyColorFilter(primaryColor.darkenColor())
|
||||
vcard_title.setTextColor(textColor)
|
||||
vcard_subtitle.setTextColor(textColor)
|
||||
|
||||
if (attachment) {
|
||||
vcard_progress.beVisible()
|
||||
}
|
||||
arrayOf(vcard_photo, vcard_title, vcard_subtitle, view_contact_details).forEach {
|
||||
it.beGone()
|
||||
}
|
||||
|
||||
parseVCardFromUri(activity, uri) { vCards ->
|
||||
activity.runOnUiThread {
|
||||
if (vCards.isEmpty()) {
|
||||
vcard_title.beVisible()
|
||||
vcard_title.text = context.getString(R.string.unknown_error_occurred)
|
||||
return@runOnUiThread
|
||||
}
|
||||
val title = vCards.firstOrNull()?.parseNameFromVCard()
|
||||
val imageIcon = if (title != null) {
|
||||
SimpleContactsHelper(activity).getContactLetterIcon(title)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
arrayOf(vcard_photo, vcard_title).forEach {
|
||||
it.beVisible()
|
||||
}
|
||||
|
||||
vcard_photo.setImageBitmap(imageIcon)
|
||||
vcard_title.text = title
|
||||
|
||||
if (vCards.size > 1) {
|
||||
vcard_subtitle.beVisible()
|
||||
val quantity = vCards.size - 1
|
||||
vcard_subtitle.text = resources.getQuantityString(R.plurals.and_other_contacts, quantity, quantity)
|
||||
} else {
|
||||
vcard_subtitle.beGone()
|
||||
}
|
||||
|
||||
if (attachment) {
|
||||
vcard_progress.beGone()
|
||||
remove_attachment_button.apply {
|
||||
beVisible()
|
||||
background.applyColorFilter(primaryColor)
|
||||
if (onRemoveButtonClicked != null) {
|
||||
setOnClickListener {
|
||||
onRemoveButtonClicked.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
view_contact_details.setTextColor(primaryColor)
|
||||
view_contact_details.beVisible()
|
||||
}
|
||||
|
||||
vcard_attachment_holder.setOnClickListener {
|
||||
onClick?.invoke()
|
||||
}
|
||||
vcard_attachment_holder.setOnLongClickListener {
|
||||
onLongClick?.invoke()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIconResourceForMimeType(mimeType: String) = when {
|
||||
mimeType.isAudioMimeType() -> R.drawable.ic_vector_audio_file
|
||||
mimeType.isCalendarMimeType() -> R.drawable.ic_calendar_month_vector
|
||||
mimeType.isPdfMimeType() -> R.drawable.ic_vector_pdf
|
||||
mimeType.isZipMimeType() -> R.drawable.ic_vector_folder_zip
|
||||
else -> R.drawable.ic_document_vector
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.simplemobiletools.smsmessenger.helpers
|
||||
|
||||
import android.util.Xml
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
|
||||
object AttachmentUtils {
|
||||
|
||||
fun parseAttachmentNames(text: String): List<String> {
|
||||
val parser = Xml.newPullParser()
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
||||
parser.setInput(text.reader())
|
||||
parser.nextTag()
|
||||
return readSmil(parser)
|
||||
}
|
||||
|
||||
private fun readSmil(parser: XmlPullParser): List<String> {
|
||||
parser.require(XmlPullParser.START_TAG, null, "smil")
|
||||
while (parser.next() != XmlPullParser.END_TAG) {
|
||||
if (parser.eventType != XmlPullParser.START_TAG) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (parser.name == "body") {
|
||||
return readBody(parser)
|
||||
} else {
|
||||
skip(parser)
|
||||
}
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun readBody(parser: XmlPullParser): List<String> {
|
||||
val names = mutableListOf<String>()
|
||||
parser.require(XmlPullParser.START_TAG, null, "body")
|
||||
while (parser.next() != XmlPullParser.END_TAG) {
|
||||
if (parser.eventType != XmlPullParser.START_TAG) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (parser.name == "par") {
|
||||
parser.require(XmlPullParser.START_TAG, null, "par")
|
||||
while (parser.next() != XmlPullParser.END_TAG) {
|
||||
if (parser.eventType != XmlPullParser.START_TAG) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (parser.name == "ref") {
|
||||
val value = parser.getAttributeValue(null, "src")
|
||||
names.add(value)
|
||||
parser.nextTag()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
skip(parser)
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
private fun skip(parser: XmlPullParser) {
|
||||
if (parser.eventType != XmlPullParser.START_TAG) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
|
||||
var depth = 1
|
||||
while (depth != 0) {
|
||||
when (parser.next()) {
|
||||
XmlPullParser.END_TAG -> depth--
|
||||
XmlPullParser.START_TAG -> depth++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.simplemobiletools.smsmessenger.helpers
|
|||
|
||||
import android.content.Context
|
||||
import com.simplemobiletools.commons.helpers.BaseConfig
|
||||
import com.simplemobiletools.smsmessenger.extensions.getDefaultKeyboardHeight
|
||||
import com.simplemobiletools.smsmessenger.models.Conversation
|
||||
|
||||
class Config(context: Context) : BaseConfig(context) {
|
||||
|
|
@ -90,4 +91,8 @@ class Config(context: Context) : BaseConfig(context) {
|
|||
var wasDbCleared: Boolean
|
||||
get() = prefs.getBoolean(WAS_DB_CLEARED, false)
|
||||
set(wasDbCleared) = prefs.edit().putBoolean(WAS_DB_CLEARED, wasDbCleared).apply()
|
||||
|
||||
var keyboardHeight: Int
|
||||
get() = prefs.getInt(SOFT_KEYBOARD_HEIGHT, context.getDefaultKeyboardHeight())
|
||||
set(keyboardHeight) = prefs.edit().putInt(SOFT_KEYBOARD_HEIGHT, keyboardHeight).apply()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ const val IMPORT_MMS = "import_mms"
|
|||
const val WAS_DB_CLEARED = "was_db_cleared_2"
|
||||
const val EXTRA_VCARD_URI = "vcard"
|
||||
const val SCHEDULED_MESSAGE_ID = "scheduled_message_id"
|
||||
const val SOFT_KEYBOARD_HEIGHT = "soft_keyboard_height"
|
||||
|
||||
private const val PATH = "com.simplemobiletools.smsmessenger.action."
|
||||
const val MARK_AS_READ = PATH + "mark_as_read"
|
||||
|
|
@ -46,6 +47,11 @@ const val THREAD_SENT_MESSAGE_ERROR = 4
|
|||
const val THREAD_SENT_MESSAGE_SENT = 5
|
||||
const val THREAD_SENT_MESSAGE_SENDING = 6
|
||||
|
||||
// view types for attachment list
|
||||
const val ATTACHMENT_DOCUMENT = 7
|
||||
const val ATTACHMENT_MEDIA = 8
|
||||
const val ATTACHMENT_VCARD = 9
|
||||
|
||||
// lock screen visibility constants
|
||||
const val LOCK_SCREEN_SENDER_MESSAGE = 1
|
||||
const val LOCK_SCREEN_SENDER = 2
|
||||
|
|
@ -61,6 +67,16 @@ const val FILE_SIZE_2_MB = 2_097_152L
|
|||
|
||||
const val MESSAGES_LIMIT = 50
|
||||
|
||||
// intent launch request codes
|
||||
const val PICK_PHOTO_INTENT = 42
|
||||
const val PICK_VIDEO_INTENT = 49
|
||||
const val PICK_SAVE_FILE_INTENT = 43
|
||||
const val CAPTURE_PHOTO_INTENT = 44
|
||||
const val CAPTURE_VIDEO_INTENT = 45
|
||||
const val CAPTURE_AUDIO_INTENT = 46
|
||||
const val PICK_DOCUMENT_INTENT = 47
|
||||
const val PICK_CONTACT_INTENT = 48
|
||||
|
||||
fun refreshMessages() {
|
||||
EventBus.getDefault().post(Events.RefreshMessages())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,449 @@
|
|||
package com.simplemobiletools.smsmessenger.helpers
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.ContactsContract
|
||||
import android.provider.ContactsContract.CommonDataKinds.*
|
||||
import android.provider.ContactsContract.Data
|
||||
import android.text.TextUtils
|
||||
import android.util.SparseArray
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.models.PhoneNumber
|
||||
import com.simplemobiletools.commons.models.contacts.*
|
||||
import com.simplemobiletools.commons.models.contacts.Email
|
||||
import com.simplemobiletools.commons.models.contacts.Event
|
||||
import com.simplemobiletools.commons.models.contacts.Organization
|
||||
import com.simplemobiletools.commons.overloads.times
|
||||
|
||||
// based on the ContactsHelper from Simple-Contacts
|
||||
class ContactsHelper(val context: Context) {
|
||||
private var displayContactSources = ArrayList<String>()
|
||||
|
||||
fun getContactFromUri(uri: Uri): Contact? {
|
||||
val key = getLookupKeyFromUri(uri) ?: return null
|
||||
return getContactWithLookupKey(key)
|
||||
}
|
||||
|
||||
private fun getLookupKeyFromUri(lookupUri: Uri): String? {
|
||||
val projection = arrayOf(ContactsContract.Contacts.LOOKUP_KEY)
|
||||
val cursor = context.contentResolver.query(lookupUri, projection, null, null, null)
|
||||
cursor?.use {
|
||||
if (cursor.moveToFirst()) {
|
||||
return cursor.getStringValue(ContactsContract.Contacts.LOOKUP_KEY)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getContactWithLookupKey(key: String): Contact? {
|
||||
val selection = "(${Data.MIMETYPE} = ? OR ${Data.MIMETYPE} = ?) AND ${Data.LOOKUP_KEY} = ?"
|
||||
val selectionArgs = arrayOf(StructuredName.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE, key)
|
||||
return parseContactCursor(selection, selectionArgs)
|
||||
}
|
||||
|
||||
private fun parseContactCursor(selection: String, selectionArgs: Array<String>): Contact? {
|
||||
val storedGroups = getDeviceStoredGroups()
|
||||
val uri = Data.CONTENT_URI
|
||||
val projection = getContactProjection()
|
||||
|
||||
val cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
|
||||
cursor?.use {
|
||||
if (cursor.moveToFirst()) {
|
||||
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
|
||||
|
||||
var prefix = ""
|
||||
var firstName = ""
|
||||
var middleName = ""
|
||||
var surname = ""
|
||||
var suffix = ""
|
||||
val mimetype = cursor.getStringValue(Data.MIMETYPE)
|
||||
|
||||
// ignore names at Organization type contacts
|
||||
if (mimetype == StructuredName.CONTENT_ITEM_TYPE) {
|
||||
prefix = cursor.getStringValue(StructuredName.PREFIX) ?: ""
|
||||
firstName = cursor.getStringValue(StructuredName.GIVEN_NAME) ?: ""
|
||||
middleName = cursor.getStringValue(StructuredName.MIDDLE_NAME) ?: ""
|
||||
surname = cursor.getStringValue(StructuredName.FAMILY_NAME) ?: ""
|
||||
suffix = cursor.getStringValue(StructuredName.SUFFIX) ?: ""
|
||||
}
|
||||
|
||||
val nickname = getNicknames(id)[id] ?: ""
|
||||
val photoUri = cursor.getStringValue(Phone.PHOTO_URI) ?: ""
|
||||
val number = getPhoneNumbers(id)[id] ?: ArrayList()
|
||||
val emails = getEmails(id)[id] ?: ArrayList()
|
||||
val addresses = getAddresses(id)[id] ?: ArrayList()
|
||||
val events = getEvents(id)[id] ?: ArrayList()
|
||||
val notes = getNotes(id)[id] ?: ""
|
||||
val accountName = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_NAME) ?: ""
|
||||
val starred = cursor.getIntValue(StructuredName.STARRED)
|
||||
val ringtone = cursor.getStringValue(StructuredName.CUSTOM_RINGTONE)
|
||||
val contactId = cursor.getIntValue(Data.CONTACT_ID)
|
||||
val groups = getContactGroups(storedGroups, contactId)[contactId] ?: ArrayList()
|
||||
val thumbnailUri = cursor.getStringValue(StructuredName.PHOTO_THUMBNAIL_URI) ?: ""
|
||||
val organization = getOrganizations(id)[id] ?: Organization("", "")
|
||||
val websites = getWebsites(id)[id] ?: ArrayList()
|
||||
val ims = getIMs(id)[id] ?: ArrayList()
|
||||
return Contact(
|
||||
id, prefix, firstName, middleName, surname, suffix, nickname, photoUri, number, emails, addresses, events,
|
||||
accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites, ims, mimetype, ringtone
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getPhoneNumbers(contactId: Int? = null): SparseArray<ArrayList<PhoneNumber>> {
|
||||
val phoneNumbers = SparseArray<ArrayList<PhoneNumber>>()
|
||||
val uri = Phone.CONTENT_URI
|
||||
val projection = arrayOf(
|
||||
Data.RAW_CONTACT_ID,
|
||||
Phone.NUMBER,
|
||||
Phone.NORMALIZED_NUMBER,
|
||||
Phone.TYPE,
|
||||
Phone.LABEL,
|
||||
Phone.IS_PRIMARY
|
||||
)
|
||||
|
||||
val selection = if (contactId == null) getSourcesSelection() else "${Data.RAW_CONTACT_ID} = ?"
|
||||
val selectionArgs = if (contactId == null) getSourcesSelectionArgs() else arrayOf(contactId.toString())
|
||||
|
||||
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
|
||||
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
|
||||
val number = cursor.getStringValue(Phone.NUMBER) ?: return@queryCursor
|
||||
val normalizedNumber = cursor.getStringValue(Phone.NORMALIZED_NUMBER) ?: number.normalizePhoneNumber()
|
||||
val type = cursor.getIntValue(Phone.TYPE)
|
||||
val label = cursor.getStringValue(Phone.LABEL) ?: ""
|
||||
val isPrimary = cursor.getIntValue(Phone.IS_PRIMARY) != 0
|
||||
|
||||
if (phoneNumbers[id] == null) {
|
||||
phoneNumbers.put(id, ArrayList())
|
||||
}
|
||||
|
||||
val phoneNumber = PhoneNumber(number, type, label, normalizedNumber, isPrimary)
|
||||
phoneNumbers[id].add(phoneNumber)
|
||||
}
|
||||
|
||||
return phoneNumbers
|
||||
}
|
||||
|
||||
private fun getNicknames(contactId: Int? = null): SparseArray<String> {
|
||||
val nicknames = SparseArray<String>()
|
||||
val uri = Data.CONTENT_URI
|
||||
val projection = arrayOf(
|
||||
Data.RAW_CONTACT_ID,
|
||||
Nickname.NAME
|
||||
)
|
||||
|
||||
val selection = getSourcesSelection(true, contactId != null)
|
||||
val selectionArgs = getSourcesSelectionArgs(Nickname.CONTENT_ITEM_TYPE, contactId)
|
||||
|
||||
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
|
||||
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
|
||||
val nickname = cursor.getStringValue(Nickname.NAME) ?: return@queryCursor
|
||||
nicknames.put(id, nickname)
|
||||
}
|
||||
|
||||
return nicknames
|
||||
}
|
||||
|
||||
private fun getEmails(contactId: Int? = null): SparseArray<ArrayList<Email>> {
|
||||
val emails = SparseArray<ArrayList<Email>>()
|
||||
val uri = ContactsContract.CommonDataKinds.Email.CONTENT_URI
|
||||
val projection = arrayOf(
|
||||
Data.RAW_CONTACT_ID,
|
||||
ContactsContract.CommonDataKinds.Email.DATA,
|
||||
ContactsContract.CommonDataKinds.Email.TYPE,
|
||||
ContactsContract.CommonDataKinds.Email.LABEL
|
||||
)
|
||||
|
||||
val selection = if (contactId == null) getSourcesSelection() else "${Data.RAW_CONTACT_ID} = ?"
|
||||
val selectionArgs = if (contactId == null) getSourcesSelectionArgs() else arrayOf(contactId.toString())
|
||||
|
||||
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
|
||||
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
|
||||
val email = cursor.getStringValue(ContactsContract.CommonDataKinds.Email.DATA) ?: return@queryCursor
|
||||
val type = cursor.getIntValue(ContactsContract.CommonDataKinds.Email.TYPE)
|
||||
val label = cursor.getStringValue(ContactsContract.CommonDataKinds.Email.LABEL) ?: ""
|
||||
|
||||
if (emails[id] == null) {
|
||||
emails.put(id, ArrayList())
|
||||
}
|
||||
|
||||
emails[id]!!.add(Email(email, type, label))
|
||||
}
|
||||
|
||||
return emails
|
||||
}
|
||||
|
||||
private fun getAddresses(contactId: Int? = null): SparseArray<ArrayList<Address>> {
|
||||
val addresses = SparseArray<ArrayList<Address>>()
|
||||
val uri = StructuredPostal.CONTENT_URI
|
||||
val projection = arrayOf(
|
||||
Data.RAW_CONTACT_ID,
|
||||
StructuredPostal.FORMATTED_ADDRESS,
|
||||
StructuredPostal.TYPE,
|
||||
StructuredPostal.LABEL
|
||||
)
|
||||
|
||||
val selection = if (contactId == null) getSourcesSelection() else "${Data.RAW_CONTACT_ID} = ?"
|
||||
val selectionArgs = if (contactId == null) getSourcesSelectionArgs() else arrayOf(contactId.toString())
|
||||
|
||||
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
|
||||
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
|
||||
val address = cursor.getStringValue(StructuredPostal.FORMATTED_ADDRESS) ?: return@queryCursor
|
||||
val type = cursor.getIntValue(StructuredPostal.TYPE)
|
||||
val label = cursor.getStringValue(StructuredPostal.LABEL) ?: ""
|
||||
|
||||
if (addresses[id] == null) {
|
||||
addresses.put(id, ArrayList())
|
||||
}
|
||||
|
||||
addresses[id]!!.add(Address(address, type, label))
|
||||
}
|
||||
|
||||
return addresses
|
||||
}
|
||||
|
||||
private fun getIMs(contactId: Int? = null): SparseArray<ArrayList<IM>> {
|
||||
val IMs = SparseArray<ArrayList<IM>>()
|
||||
val uri = Data.CONTENT_URI
|
||||
val projection = arrayOf(
|
||||
Data.RAW_CONTACT_ID,
|
||||
Im.DATA,
|
||||
Im.PROTOCOL,
|
||||
Im.CUSTOM_PROTOCOL
|
||||
)
|
||||
|
||||
val selection = getSourcesSelection(true, contactId != null)
|
||||
val selectionArgs = getSourcesSelectionArgs(Im.CONTENT_ITEM_TYPE, contactId)
|
||||
|
||||
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
|
||||
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
|
||||
val IM = cursor.getStringValue(Im.DATA) ?: return@queryCursor
|
||||
val type = cursor.getIntValue(Im.PROTOCOL)
|
||||
val label = cursor.getStringValue(Im.CUSTOM_PROTOCOL) ?: ""
|
||||
|
||||
if (IMs[id] == null) {
|
||||
IMs.put(id, ArrayList())
|
||||
}
|
||||
|
||||
IMs[id]!!.add(IM(IM, type, label))
|
||||
}
|
||||
|
||||
return IMs
|
||||
}
|
||||
|
||||
private fun getEvents(contactId: Int? = null): SparseArray<ArrayList<Event>> {
|
||||
val events = SparseArray<ArrayList<Event>>()
|
||||
val uri = Data.CONTENT_URI
|
||||
val projection = arrayOf(
|
||||
Data.RAW_CONTACT_ID,
|
||||
ContactsContract.CommonDataKinds.Event.START_DATE,
|
||||
ContactsContract.CommonDataKinds.Event.TYPE
|
||||
)
|
||||
|
||||
val selection = getSourcesSelection(true, contactId != null)
|
||||
val selectionArgs = getSourcesSelectionArgs(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE, contactId)
|
||||
|
||||
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
|
||||
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
|
||||
val startDate = cursor.getStringValue(ContactsContract.CommonDataKinds.Event.START_DATE) ?: return@queryCursor
|
||||
val type = cursor.getIntValue(ContactsContract.CommonDataKinds.Event.TYPE)
|
||||
|
||||
if (events[id] == null) {
|
||||
events.put(id, ArrayList())
|
||||
}
|
||||
|
||||
events[id]!!.add(Event(startDate, type))
|
||||
}
|
||||
|
||||
return events
|
||||
}
|
||||
|
||||
private fun getNotes(contactId: Int? = null): SparseArray<String> {
|
||||
val notes = SparseArray<String>()
|
||||
val uri = Data.CONTENT_URI
|
||||
val projection = arrayOf(
|
||||
Data.RAW_CONTACT_ID,
|
||||
Note.NOTE
|
||||
)
|
||||
|
||||
val selection = getSourcesSelection(true, contactId != null)
|
||||
val selectionArgs = getSourcesSelectionArgs(Note.CONTENT_ITEM_TYPE, contactId)
|
||||
|
||||
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
|
||||
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
|
||||
val note = cursor.getStringValue(Note.NOTE) ?: return@queryCursor
|
||||
notes.put(id, note)
|
||||
}
|
||||
|
||||
return notes
|
||||
}
|
||||
|
||||
private fun getOrganizations(contactId: Int? = null): SparseArray<Organization> {
|
||||
val organizations = SparseArray<Organization>()
|
||||
val uri = Data.CONTENT_URI
|
||||
val projection = arrayOf(
|
||||
Data.RAW_CONTACT_ID,
|
||||
ContactsContract.CommonDataKinds.Organization.COMPANY,
|
||||
ContactsContract.CommonDataKinds.Organization.TITLE
|
||||
)
|
||||
|
||||
val selection = getSourcesSelection(true, contactId != null)
|
||||
val selectionArgs = getSourcesSelectionArgs(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE, contactId)
|
||||
|
||||
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
|
||||
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
|
||||
val company = cursor.getStringValue(ContactsContract.CommonDataKinds.Organization.COMPANY) ?: ""
|
||||
val title = cursor.getStringValue(ContactsContract.CommonDataKinds.Organization.TITLE) ?: ""
|
||||
if (company.isEmpty() && title.isEmpty()) {
|
||||
return@queryCursor
|
||||
}
|
||||
|
||||
val organization = Organization(company, title)
|
||||
organizations.put(id, organization)
|
||||
}
|
||||
|
||||
return organizations
|
||||
}
|
||||
|
||||
private fun getWebsites(contactId: Int? = null): SparseArray<ArrayList<String>> {
|
||||
val websites = SparseArray<ArrayList<String>>()
|
||||
val uri = Data.CONTENT_URI
|
||||
val projection = arrayOf(
|
||||
Data.RAW_CONTACT_ID,
|
||||
Website.URL
|
||||
)
|
||||
|
||||
val selection = getSourcesSelection(true, contactId != null)
|
||||
val selectionArgs = getSourcesSelectionArgs(Website.CONTENT_ITEM_TYPE, contactId)
|
||||
|
||||
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
|
||||
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
|
||||
val url = cursor.getStringValue(Website.URL) ?: return@queryCursor
|
||||
|
||||
if (websites[id] == null) {
|
||||
websites.put(id, ArrayList())
|
||||
}
|
||||
|
||||
websites[id]!!.add(url)
|
||||
}
|
||||
|
||||
return websites
|
||||
}
|
||||
|
||||
private fun getDeviceStoredGroups(): java.util.ArrayList<Group> {
|
||||
val groups = java.util.ArrayList<Group>()
|
||||
|
||||
val uri = ContactsContract.Groups.CONTENT_URI
|
||||
val projection = arrayOf(
|
||||
ContactsContract.Groups._ID,
|
||||
ContactsContract.Groups.TITLE,
|
||||
ContactsContract.Groups.SYSTEM_ID
|
||||
)
|
||||
|
||||
val selection = "${ContactsContract.Groups.AUTO_ADD} = ? AND ${ContactsContract.Groups.FAVORITES} = ?"
|
||||
val selectionArgs = arrayOf("0", "0")
|
||||
|
||||
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
|
||||
val id = cursor.getLongValue(ContactsContract.Groups._ID)
|
||||
val title = cursor.getStringValue(ContactsContract.Groups.TITLE) ?: return@queryCursor
|
||||
|
||||
val systemId = cursor.getStringValue(ContactsContract.Groups.SYSTEM_ID)
|
||||
if (groups.map { it.title }.contains(title) && systemId != null) {
|
||||
return@queryCursor
|
||||
}
|
||||
|
||||
groups.add(Group(id, title))
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
private fun getContactGroups(storedGroups: ArrayList<Group>, contactId: Int? = null): SparseArray<ArrayList<Group>> {
|
||||
val groups = SparseArray<ArrayList<Group>>()
|
||||
|
||||
val uri = Data.CONTENT_URI
|
||||
val projection = arrayOf(
|
||||
Data.CONTACT_ID,
|
||||
Data.DATA1
|
||||
)
|
||||
|
||||
val selection = getSourcesSelection(true, contactId != null, false)
|
||||
val selectionArgs = getSourcesSelectionArgs(GroupMembership.CONTENT_ITEM_TYPE, contactId)
|
||||
|
||||
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
|
||||
val id = cursor.getIntValue(Data.CONTACT_ID)
|
||||
val newRowId = cursor.getLongValue(Data.DATA1)
|
||||
|
||||
val groupTitle = storedGroups.firstOrNull { it.id == newRowId }?.title ?: return@queryCursor
|
||||
val group = Group(newRowId, groupTitle)
|
||||
if (groups[id] == null) {
|
||||
groups.put(id, ArrayList())
|
||||
}
|
||||
groups[id]!!.add(group)
|
||||
}
|
||||
|
||||
return groups
|
||||
}
|
||||
|
||||
private fun getQuestionMarks() = ("?," * displayContactSources.filter { it.isNotEmpty() }.size).trimEnd(',')
|
||||
|
||||
private fun getSourcesSelection(addMimeType: Boolean = false, addContactId: Boolean = false, useRawContactId: Boolean = true): String {
|
||||
val strings = ArrayList<String>()
|
||||
if (addMimeType) {
|
||||
strings.add("${Data.MIMETYPE} = ?")
|
||||
}
|
||||
|
||||
if (addContactId) {
|
||||
strings.add("${if (useRawContactId) Data.RAW_CONTACT_ID else Data.CONTACT_ID} = ?")
|
||||
} else {
|
||||
// sometimes local device storage has null account_name, handle it properly
|
||||
val accountNameString = StringBuilder()
|
||||
if (displayContactSources.contains("")) {
|
||||
accountNameString.append("(")
|
||||
}
|
||||
accountNameString.append("${ContactsContract.RawContacts.ACCOUNT_NAME} IN (${getQuestionMarks()})")
|
||||
if (displayContactSources.contains("")) {
|
||||
accountNameString.append(" OR ${ContactsContract.RawContacts.ACCOUNT_NAME} IS NULL)")
|
||||
}
|
||||
strings.add(accountNameString.toString())
|
||||
}
|
||||
|
||||
return TextUtils.join(" AND ", strings)
|
||||
}
|
||||
|
||||
private fun getSourcesSelectionArgs(mimetype: String? = null, contactId: Int? = null): Array<String> {
|
||||
val args = ArrayList<String>()
|
||||
|
||||
if (mimetype != null) {
|
||||
args.add(mimetype)
|
||||
}
|
||||
|
||||
if (contactId != null) {
|
||||
args.add(contactId.toString())
|
||||
} else {
|
||||
args.addAll(displayContactSources.filter { it.isNotEmpty() })
|
||||
}
|
||||
|
||||
return args.toTypedArray()
|
||||
}
|
||||
|
||||
private fun getContactProjection() = arrayOf(
|
||||
Data.MIMETYPE,
|
||||
Data.CONTACT_ID,
|
||||
Data.RAW_CONTACT_ID,
|
||||
StructuredName.PREFIX,
|
||||
StructuredName.GIVEN_NAME,
|
||||
StructuredName.MIDDLE_NAME,
|
||||
StructuredName.FAMILY_NAME,
|
||||
StructuredName.SUFFIX,
|
||||
StructuredName.PHOTO_URI,
|
||||
StructuredName.PHOTO_THUMBNAIL_URI,
|
||||
StructuredName.STARRED,
|
||||
StructuredName.CUSTOM_RINGTONE,
|
||||
ContactsContract.RawContacts.ACCOUNT_NAME,
|
||||
ContactsContract.RawContacts.ACCOUNT_TYPE
|
||||
)
|
||||
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ import android.app.AlarmManager
|
|||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.core.app.AlarmManagerCompat
|
||||
|
|
@ -15,6 +14,8 @@ import com.simplemobiletools.commons.extensions.showErrorToast
|
|||
import com.simplemobiletools.commons.helpers.isMarshmallowPlus
|
||||
import com.simplemobiletools.smsmessenger.R
|
||||
import com.simplemobiletools.smsmessenger.extensions.config
|
||||
import com.simplemobiletools.smsmessenger.extensions.isPlainTextMimeType
|
||||
import com.simplemobiletools.smsmessenger.models.Attachment
|
||||
import com.simplemobiletools.smsmessenger.models.Message
|
||||
import com.simplemobiletools.smsmessenger.receivers.ScheduledMessageReceiver
|
||||
import com.simplemobiletools.smsmessenger.receivers.SmsStatusDeliveredReceiver
|
||||
|
|
@ -34,7 +35,7 @@ fun Context.getSendMessageSettings(): Settings {
|
|||
return settings
|
||||
}
|
||||
|
||||
fun Context.sendMessage(text: String, addresses: List<String>, subscriptionId: Int?, attachments: List<Uri>) {
|
||||
fun Context.sendMessage(text: String, addresses: List<String>, subscriptionId: Int?, attachments: List<Attachment>) {
|
||||
val settings = getSendMessageSettings()
|
||||
if (subscriptionId != null) {
|
||||
settings.subscriptionId = subscriptionId
|
||||
|
|
@ -44,11 +45,19 @@ fun Context.sendMessage(text: String, addresses: List<String>, subscriptionId: I
|
|||
val message = com.klinker.android.send_message.Message(text, addresses.toTypedArray())
|
||||
|
||||
if (attachments.isNotEmpty()) {
|
||||
for (uri in attachments) {
|
||||
for (attachment in attachments) {
|
||||
try {
|
||||
val byteArray = contentResolver.openInputStream(uri)?.readBytes() ?: continue
|
||||
val mimeType = contentResolver.getType(uri) ?: continue
|
||||
message.addMedia(byteArray, mimeType)
|
||||
val uri = attachment.getUri()
|
||||
contentResolver.openInputStream(uri)?.use {
|
||||
val bytes = it.readBytes()
|
||||
val mimeType = if (attachment.mimetype.isPlainTextMimeType()) {
|
||||
"application/txt"
|
||||
} else {
|
||||
attachment.mimetype
|
||||
}
|
||||
val name = attachment.filename
|
||||
message.addMedia(bytes, mimeType, name, name)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
showErrorToast(e)
|
||||
} catch (e: Error) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,12 @@ import ezvcard.VCard
|
|||
|
||||
fun parseVCardFromUri(context: Context, uri: Uri, callback: (vCards: List<VCard>) -> Unit) {
|
||||
ensureBackgroundThread {
|
||||
val inputStream = context.contentResolver.openInputStream(uri)
|
||||
val inputStream = try {
|
||||
context.contentResolver.openInputStream(uri)
|
||||
} catch (e: Exception) {
|
||||
callback(emptyList())
|
||||
return@ensureBackgroundThread
|
||||
}
|
||||
val vCards = Ezvcard.parse(inputStream).all()
|
||||
callback(vCards)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue