Add support for sending all types of files

This commit is contained in:
Naveen 2022-11-02 18:03:35 +05:30
parent 8d75d5b133
commit 23643d3198
26 changed files with 1234 additions and 459 deletions

View File

@ -93,17 +93,14 @@
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" /> <data android:mimeType="*/*" />
<data android:mimeType="image/*" />
<data android:mimeType="video/*" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" /> <action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" /> <data android:mimeType="*/*" />
<data android:mimeType="video/*" />
</intent-filter> </intent-filter>
</activity> </activity>

View File

@ -5,7 +5,6 @@ import android.app.Activity
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable import android.graphics.drawable.LayerDrawable
import android.media.MediaMetadataRetriever import android.media.MediaMetadataRetriever
import android.net.Uri import android.net.Uri
@ -27,21 +26,19 @@ import android.util.TypedValue
import android.view.Gravity import android.view.Gravity
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.view.animation.OvershootInterpolator
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.LinearLayout.LayoutParams import android.widget.LinearLayout.LayoutParams
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.annotation.StringRes
import androidx.appcompat.widget.AppCompatButton
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import com.bumptech.glide.Glide import androidx.core.view.ViewCompat
import com.bumptech.glide.load.DataSource import androidx.core.view.WindowInsetsCompat
import com.bumptech.glide.load.engine.DiskCacheStrategy import androidx.core.view.children
import com.bumptech.glide.load.engine.GlideException import androidx.core.view.updateLayoutParams
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.simplemobiletools.commons.dialogs.ConfirmationDialog import com.simplemobiletools.commons.dialogs.ConfirmationDialog
@ -53,14 +50,17 @@ import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.commons.models.SimpleContact import com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.adapters.AttachmentsAdapter
import com.simplemobiletools.smsmessenger.adapters.AutoCompleteTextViewAdapter import com.simplemobiletools.smsmessenger.adapters.AutoCompleteTextViewAdapter
import com.simplemobiletools.smsmessenger.adapters.ThreadAdapter import com.simplemobiletools.smsmessenger.adapters.ThreadAdapter
import com.simplemobiletools.smsmessenger.dialogs.ScheduleMessageDialog import com.simplemobiletools.smsmessenger.dialogs.ScheduleMessageDialog
import com.simplemobiletools.smsmessenger.extensions.* import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.* import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.models.* import com.simplemobiletools.smsmessenger.models.*
import ezvcard.VCard
import ezvcard.property.FormattedName
import ezvcard.property.Telephone
import kotlinx.android.synthetic.main.activity_thread.* import kotlinx.android.synthetic.main.activity_thread.*
import kotlinx.android.synthetic.main.item_attachment.view.*
import kotlinx.android.synthetic.main.item_selected_contact.view.* import kotlinx.android.synthetic.main.item_selected_contact.view.*
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
@ -72,12 +72,6 @@ import java.io.OutputStream
class ThreadActivity : SimpleActivity() { class ThreadActivity : SimpleActivity() {
private val MIN_DATE_TIME_DIFF_SECS = 300 private val MIN_DATE_TIME_DIFF_SECS = 300
private val PICK_ATTACHMENT_INTENT = 1
private val PICK_SAVE_FILE_INTENT = 11
private val TAKE_PHOTO_INTENT = 42
private val TYPE_TAKE_PHOTO = 12
private val TYPE_CHOOSE_PHOTO = 13
private val TYPE_EDIT = 14 private val TYPE_EDIT = 14
private val TYPE_SEND = 15 private val TYPE_SEND = 15
@ -93,8 +87,6 @@ class ThreadActivity : SimpleActivity() {
private var privateContacts = ArrayList<SimpleContact>() private var privateContacts = ArrayList<SimpleContact>()
private var messages = ArrayList<Message>() private var messages = ArrayList<Message>()
private val availableSIMCards = ArrayList<SIMCard>() private val availableSIMCards = ArrayList<SIMCard>()
private var attachmentSelections = mutableMapOf<String, AttachmentSelection>()
private val imageCompressor by lazy { ImageCompressor(this) }
private var lastAttachmentUri: String? = null private var lastAttachmentUri: String? = null
private var capturedImageUri: Uri? = null private var capturedImageUri: Uri? = null
private var loadingOlderMessages = false private var loadingOlderMessages = false
@ -105,6 +97,8 @@ class ThreadActivity : SimpleActivity() {
private var scheduledMessage: Message? = null private var scheduledMessage: Message? = null
private lateinit var scheduledDateTime: DateTime private lateinit var scheduledDateTime: DateTime
private var isAttachmentPickerVisible = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_thread) setContentView(R.layout.activity_thread)
@ -145,6 +139,10 @@ class ThreadActivity : SimpleActivity() {
finish() finish()
} }
} }
setupAttachmentPickerView()
setupKeyboardListener()
hideAttachmentPicker()
} }
override fun onResume() { override fun onResume() {
@ -161,7 +159,7 @@ class ThreadActivity : SimpleActivity() {
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
if (thread_type_message.value != "" && attachmentSelections.isEmpty()) { if (thread_type_message.value != "" && getAttachments().isEmpty()) {
saveSmsDraft(thread_type_message.value, threadId) saveSmsDraft(thread_type_message.value, threadId)
} else { } else {
deleteSmsDraft(threadId) deleteSmsDraft(threadId)
@ -172,6 +170,15 @@ class ThreadActivity : SimpleActivity() {
isActivityVisible = false isActivityVisible = false
} }
override fun onBackPressed() {
isAttachmentPickerVisible = false
if (attachment_picker_holder.isVisible()) {
hideAttachmentPicker()
} else {
super.onBackPressed()
}
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
bus?.unregister(this) bus?.unregister(this)
@ -214,15 +221,30 @@ class ThreadActivity : SimpleActivity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
super.onActivityResult(requestCode, resultCode, resultData) super.onActivityResult(requestCode, resultCode, resultData)
if (resultCode != Activity.RESULT_OK) return if (resultCode != Activity.RESULT_OK) return
val data = resultData?.data
if (requestCode == TAKE_PHOTO_INTENT) { when (requestCode) {
addAttachment(capturedImageUri!!) CAPTURE_PHOTO_INTENT -> addAttachment(capturedImageUri!!)
} else if (requestCode == PICK_ATTACHMENT_INTENT && resultData != null && resultData.data != null) { CAPTURE_VIDEO_INTENT -> if (data != null) {
addAttachment(resultData.data!!) addAttachment(data)
} else if (requestCode == PICK_SAVE_FILE_INTENT && resultData != null && resultData.data != null) { }
PICK_DOCUMENT_INTENT -> if (data != null) {
addAttachment(data)
}
CAPTURE_AUDIO_INTENT -> if (data != null) {
addAttachment(data)
}
PICK_PHOTO_VIDEO_INTENT -> if (data != null) {
addAttachment(data)
}
PICK_CONTACT_INTENT -> if (data != null) {
handleContactAttachment(data)
}
PICK_SAVE_FILE_INTENT -> if (data != null) {
saveAttachment(resultData) saveAttachment(resultData)
} }
} }
}
private fun onHomePressed() { private fun onHomePressed() {
hideKeyboard() hideKeyboard()
@ -487,7 +509,14 @@ class ThreadActivity : SimpleActivity() {
thread_type_message.setText(intent.getStringExtra(THREAD_TEXT)) thread_type_message.setText(intent.getStringExtra(THREAD_TEXT))
thread_add_attachment.setOnClickListener { thread_add_attachment.setOnClickListener {
takeOrPickPhotoVideo() if (attachment_picker_holder.isVisible()) {
isAttachmentPickerVisible = false
showKeyboard(thread_type_message)
} else {
isAttachmentPickerVisible = true
hideKeyboard()
}
window.decorView.requestApplyInsets()
} }
if (intent.extras?.containsKey(THREAD_ATTACHMENT_URI) == true) { if (intent.extras?.containsKey(THREAD_ATTACHMENT_URI) == true) {
@ -780,135 +809,134 @@ class ThreadActivity : SimpleActivity() {
return items return items
} }
private fun takeOrPickPhotoVideo() { private fun launchActivityForResult(intent: Intent, requestCode: Int, @StringRes error: Int = R.string.no_app_found) {
val items = arrayListOf( hideKeyboard()
RadioItem(TYPE_TAKE_PHOTO, getString(R.string.take_photo)),
RadioItem(TYPE_CHOOSE_PHOTO, getString(R.string.choose_photo))
)
RadioGroupDialog(this, items = items) {
val checkedId = it as Int
if (checkedId == TYPE_TAKE_PHOTO) {
launchTakePhotoIntent()
} else if (checkedId == TYPE_CHOOSE_PHOTO) {
launchPickPhotoVideoIntent()
}
}
}
private fun launchTakePhotoIntent() {
val imageFile = createImageFile()
capturedImageUri = getMyFileUri(imageFile)
try { try {
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply { startActivityForResult(intent, requestCode)
putExtra(MediaStore.EXTRA_OUTPUT, capturedImageUri)
}
startActivityForResult(intent, TAKE_PHOTO_INTENT)
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
showErrorToast(getString(R.string.no_app_found)) showErrorToast(getString(error))
} catch (e: Exception) { } catch (e: Exception) {
showErrorToast(e) showErrorToast(e)
} }
} }
private fun createTemporaryFile(extension: String = ".jpg"): File {
val outputDirectory = File(cacheDir, "captured").apply {
if (!exists()) {
mkdirs()
}
}
return File.createTempFile("attachment_", extension, outputDirectory)
}
private fun launchCapturePhotoIntent() {
val imageFile = createTemporaryFile()
capturedImageUri = getMyFileUri(imageFile)
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
putExtra(MediaStore.EXTRA_OUTPUT, capturedImageUri)
}
launchActivityForResult(intent, CAPTURE_PHOTO_INTENT)
}
private fun launchCaptureVideoIntent() {
val intent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
launchActivityForResult(intent, CAPTURE_VIDEO_INTENT)
}
private fun launchCaptureAudioIntent() {
val intent = Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION)
launchActivityForResult(intent, CAPTURE_AUDIO_INTENT)
}
private fun launchPickPhotoVideoIntent() { private fun launchPickPhotoVideoIntent() {
hideKeyboard()
val mimeTypes = arrayOf("image/*", "video/*") val mimeTypes = arrayOf("image/*", "video/*")
Intent(Intent.ACTION_GET_CONTENT).apply { Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*" type = "*/*"
putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes) putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
try { launchActivityForResult(this, PICK_PHOTO_VIDEO_INTENT)
startActivityForResult(this, PICK_ATTACHMENT_INTENT) }
} catch (e: ActivityNotFoundException) { }
showErrorToast(getString(R.string.no_app_found))
} catch (e: Exception) { private fun launchPickDocumentIntent() {
showErrorToast(e) val mimeTypes = arrayOf("*/*")
Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
launchActivityForResult(this, PICK_DOCUMENT_INTENT)
}
}
private fun launchPickContactIntent() {
val intent = Intent(Intent.ACTION_PICK).apply {
type = ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE
}
launchActivityForResult(intent, PICK_CONTACT_INTENT)
}
private fun handleContactAttachment(contactUri: Uri) {
val projection = arrayOf(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER
)
queryCursor(contactUri, projection, null, null, null) { cursor ->
if (cursor.moveToFirst()) {
val nameIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)
val numberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
val name = cursor.getString(nameIndex)
val number = cursor.getString(numberIndex)
// todo: export all properties using VcfExporter
val vCard = VCard()
vCard.addTelephoneNumber(Telephone(number))
vCard.addFormattedName(FormattedName(name))
val file = createTemporaryFile(".vcf")
val outputStream = file.outputStream()
vCard.write(outputStream)
val vCardUri = getMyFileUri(file)
addAttachment(vCardUri)
} }
} }
} }
private fun getAttachmentsAdapter(): AttachmentsAdapter? {
val adapter = thread_attachments_recyclerview.adapter
return adapter as? AttachmentsAdapter
}
private fun getAttachments() = getAttachmentsAdapter()?.attachments ?: emptyList()
private fun addAttachment(uri: Uri) { private fun addAttachment(uri: Uri) {
val originalUriString = uri.toString() if (getAttachments().any { it.uri.toString() == uri.toString() }) {
if (attachmentSelections.containsKey(originalUriString)) {
return return
} }
attachmentSelections[originalUriString] = AttachmentSelection(uri, false) var adapter = getAttachmentsAdapter()
val attachmentView = addAttachmentView(originalUriString, uri) if (adapter == null) {
val mimeType = contentResolver.getType(uri) ?: return adapter = AttachmentsAdapter(
activity = this,
if (mimeType.isImageMimeType() && config.mmsFileSizeLimit != FILE_SIZE_NONE) { onItemClick = {},
val selection = attachmentSelections[originalUriString] onAttachmentsRemoved = {
attachmentSelections[originalUriString] = selection!!.copy(isPending = true) thread_attachments_recyclerview.beGone()
checkSendMessageAvailability() checkSendMessageAvailability()
attachmentView.thread_attachment_progress.beVisible() },
imageCompressor.compressImage(uri, config.mmsFileSizeLimit) { compressedUri -> onReady = { checkSendMessageAvailability() }
runOnUiThread { )
if (compressedUri != null) { thread_attachments_recyclerview.adapter = adapter
attachmentSelections[originalUriString] = AttachmentSelection(compressedUri, false)
loadAttachmentPreview(attachmentView, compressedUri)
} else {
toast(R.string.compress_error)
removeAttachment(attachmentView, originalUriString)
}
checkSendMessageAvailability()
attachmentView.thread_attachment_progress.beGone()
}
}
}
} }
private fun addAttachmentView(originalUri: String, uri: Uri): View { thread_attachments_recyclerview.beVisible()
thread_attachments_holder.beVisible() val mimeType = contentResolver.getType(uri).orEmpty()
val attachmentView = layoutInflater.inflate(R.layout.item_attachment, null).apply { val attachment = AttachmentSelection(
thread_attachments_wrapper.addView(this) uri = uri,
thread_remove_attachment.setOnClickListener { mimetype = mimeType,
removeAttachment(this, originalUri) filename = getFilenameFromUri(uri),
} isPending = mimeType.isImageMimeType()
} )
adapter.addAttachment(attachment)
loadAttachmentPreview(attachmentView, uri)
return attachmentView
}
private fun loadAttachmentPreview(attachmentView: View, uri: Uri) {
if (isDestroyed || isFinishing) {
return
}
val roundedCornersRadius = resources.getDimension(R.dimen.medium_margin).toInt()
val options = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.transform(CenterCrop(), RoundedCorners(roundedCornersRadius))
Glide.with(attachmentView.thread_attachment_preview)
.load(uri)
.transition(DrawableTransitionOptions.withCrossFade())
.apply(options)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
attachmentView.thread_attachment_preview.beGone()
attachmentView.thread_remove_attachment.beGone()
return false
}
override fun onResourceReady(dr: Drawable?, a: Any?, t: Target<Drawable>?, d: DataSource?, i: Boolean): Boolean {
attachmentView.thread_attachment_preview.beVisible()
attachmentView.thread_remove_attachment.beVisible()
checkSendMessageAvailability()
return false
}
})
.into(attachmentView.thread_attachment_preview)
}
private fun removeAttachment(attachmentView: View, originalUri: String) {
thread_attachments_wrapper.removeView(attachmentView)
attachmentSelections.remove(originalUri)
if (attachmentSelections.isEmpty()) {
thread_attachments_holder.beGone()
}
checkSendMessageAvailability() checkSendMessageAvailability()
} }
@ -933,7 +961,7 @@ class ThreadActivity : SimpleActivity() {
} }
private fun checkSendMessageAvailability() { private fun checkSendMessageAvailability() {
if (thread_type_message.text!!.isNotEmpty() || (attachmentSelections.isNotEmpty() && !attachmentSelections.values.any { it.isPending })) { if (thread_type_message.text!!.isNotEmpty() || (getAttachments().isNotEmpty() && !getAttachments().any { it.isPending })) {
thread_send_message.isEnabled = true thread_send_message.isEnabled = true
thread_send_message.isClickable = true thread_send_message.isClickable = true
thread_send_message.alpha = 0.9f thread_send_message.alpha = 0.9f
@ -947,7 +975,7 @@ class ThreadActivity : SimpleActivity() {
private fun sendMessage() { private fun sendMessage() {
var text = thread_type_message.value var text = thread_type_message.value
if (text.isEmpty() && attachmentSelections.isEmpty()) { if (text.isEmpty() && getAttachments().isEmpty()) {
showErrorToast(getString(R.string.unknown_error_occurred)) showErrorToast(getString(R.string.unknown_error_occurred))
return return
} }
@ -1002,8 +1030,7 @@ class ThreadActivity : SimpleActivity() {
private fun sendNormalMessage(text: String, subscriptionId: Int) { private fun sendNormalMessage(text: String, subscriptionId: Int) {
val addresses = participants.getAddresses() val addresses = participants.getAddresses()
val attachments = attachmentSelections.values val attachments = getAttachments().map { it.uri }
.map { it.uri }
try { try {
refreshedSinceSent = false refreshedSinceSent = false
@ -1022,9 +1049,8 @@ class ThreadActivity : SimpleActivity() {
private fun clearCurrentMessage() { private fun clearCurrentMessage() {
thread_type_message.setText("") thread_type_message.setText("")
attachmentSelections.clear() getAttachmentsAdapter()?.clear()
thread_attachments_holder.beGone() checkSendMessageAvailability()
thread_attachments_wrapper.removeAllViews()
} }
// show selected contacts, properly split to new lines when appropriate // show selected contacts, properly split to new lines when appropriate
@ -1150,13 +1176,7 @@ class ThreadActivity : SimpleActivity() {
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
putExtra(Intent.EXTRA_TITLE, path.split("/").last()) putExtra(Intent.EXTRA_TITLE, path.split("/").last())
try { launchActivityForResult(this, PICK_SAVE_FILE_INTENT, error = R.string.system_service_disabled)
startActivityForResult(this, PICK_SAVE_FILE_INTENT)
} catch (e: ActivityNotFoundException) {
showErrorToast(getString(R.string.system_service_disabled))
} catch (e: Exception) {
showErrorToast(e)
}
} }
} }
@ -1204,7 +1224,7 @@ class ThreadActivity : SimpleActivity() {
private fun isMmsMessage(text: String): Boolean { private fun isMmsMessage(text: String): Boolean {
val isGroupMms = participants.size > 1 && config.sendGroupMessageMMS val isGroupMms = participants.size > 1 && config.sendGroupMessageMMS
val isLongMmsMessage = isLongMmsMessage(text) && config.sendLongMessageMMS val isLongMmsMessage = isLongMmsMessage(text) && config.sendLongMessageMMS
return attachmentSelections.isNotEmpty() || isGroupMms || isLongMmsMessage return getAttachments().isNotEmpty() || isGroupMms || isLongMmsMessage
} }
private fun updateMessageType() { private fun updateMessageType() {
@ -1217,15 +1237,6 @@ class ThreadActivity : SimpleActivity() {
thread_send_message.setText(stringId) thread_send_message.setText(stringId)
} }
private fun createImageFile(): File {
val outputDirectory = File(cacheDir, "captured").apply {
if (!exists()) {
mkdirs()
}
}
return File.createTempFile("IMG_", ".jpg", outputDirectory)
}
private fun showScheduledMessageInfo(message: Message) { private fun showScheduledMessageInfo(message: Message) {
val items = arrayListOf( val items = arrayListOf(
RadioItem(TYPE_EDIT, getString(R.string.update_message)), RadioItem(TYPE_EDIT, getString(R.string.update_message)),
@ -1282,7 +1293,7 @@ class ThreadActivity : SimpleActivity() {
private fun setupScheduleSendUi() { private fun setupScheduleSendUi() {
val textColor = getProperTextColor() val textColor = getProperTextColor()
scheduled_message_holder.background.applyColorFilter(getProperBackgroundColor().getContrastColor()) scheduled_message_holder.background.applyColorFilter(getProperPrimaryColor().darkenColor())
scheduled_message_button.apply { scheduled_message_button.apply {
val clockDrawable = ResourcesCompat.getDrawable(resources, R.drawable.ic_clock_vector, theme)?.apply { applyColorFilter(textColor) } val clockDrawable = ResourcesCompat.getDrawable(resources, R.drawable.ic_clock_vector, theme)?.apply { applyColorFilter(textColor) }
setCompoundDrawablesWithIntrinsicBounds(clockDrawable, null, null, null) setCompoundDrawablesWithIntrinsicBounds(clockDrawable, null, null, null)
@ -1358,10 +1369,94 @@ class ThreadActivity : SimpleActivity() {
} }
private fun buildMessageAttachment(text: String, messageId: Long): MessageAttachment { private fun buildMessageAttachment(text: String, messageId: Long): MessageAttachment {
val attachments = attachmentSelections.values val attachments = getAttachments()
.map { Attachment(null, messageId, it.uri.toString(), contentResolver.getType(it.uri) ?: "*/*", 0, 0, "") } .map { Attachment(null, messageId, it.uri.toString(), contentResolver.getType(it.uri) ?: "*/*", 0, 0, "") }
.toArrayList() .toArrayList()
return MessageAttachment(messageId, text, attachments) return MessageAttachment(messageId, text, attachments)
} }
private fun setupAttachmentPickerView() {
val colors = arrayOf(
R.color.md_red_500,
R.color.md_pink_500,
R.color.md_purple_500,
R.color.md_teal_500,
R.color.md_green_500,
R.color.md_light_green_500,
R.color.md_blue_500
)
attachment_picker_holder.children.filterIsInstance<AppCompatButton>().forEachIndexed { index, button ->
button.setTextColor(getProperTextColor())
val color = resources.getColor(colors[index])
button.compoundDrawables.forEach { it?.applyColorFilter(color) }
}
pick_from_gallery.setOnClickListener {
launchPickPhotoVideoIntent()
}
camera.setOnClickListener {
launchCapturePhotoIntent()
}
record_video.setOnClickListener {
launchCaptureVideoIntent()
}
record_audio.setOnClickListener {
launchCaptureAudioIntent()
}
pick_file.setOnClickListener {
launchPickDocumentIntent()
}
pick_contact.setOnClickListener {
launchPickContactIntent()
}
schedule_message.setOnClickListener {
if (isScheduledMessage) {
launchScheduleSendDialog(scheduledDateTime)
} else {
launchScheduleSendDialog()
}
}
}
private fun showAttachmentPicker() {
attachment_picker_divider.showWithAnimation()
attachment_picker_holder.showWithAnimation()
animateAttachmentButton(rotation = -135f)
}
private fun hideAttachmentPicker() {
attachment_picker_divider.beGone()
attachment_picker_holder.apply {
beGone()
updateLayoutParams<ConstraintLayout.LayoutParams> {
height = config.keyboardHeight
}
}
animateAttachmentButton(rotation = 0f)
}
private fun animateAttachmentButton(rotation: Float) {
thread_add_attachment.animate()
.rotation(rotation)
.setDuration(500L)
.setInterpolator(OvershootInterpolator())
.start()
}
private fun setupKeyboardListener() {
val typeMask = WindowInsetsCompat.Type.ime()
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { _, insets ->
if (insets.isVisible(typeMask)) {
config.keyboardHeight = insets.getInsets(typeMask).bottom
hideAttachmentPicker()
} else {
if (isAttachmentPickerVisible) {
showAttachmentPicker()
}
}
insets
}
}
} }

View File

@ -0,0 +1,227 @@
package com.simplemobiletools.smsmessenger.adapters
import android.content.Intent
import android.graphics.drawable.Drawable
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.smsmessenger.activities.VCardViewerActivity
import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.extensions.isImageMimeType
import com.simplemobiletools.smsmessenger.extensions.isVideoMimeType
import com.simplemobiletools.smsmessenger.extensions.launchViewIntent
import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.models.AttachmentSelection
import kotlinx.android.synthetic.main.item_attachment_media_preview.view.*
import kotlinx.android.synthetic.main.item_remove_attachment_button.view.*
class AttachmentsAdapter(
val activity: BaseSimpleActivity,
val onItemClick: (AttachmentSelection) -> Unit,
val onAttachmentsRemoved: () -> Unit,
val onReady: (() -> Unit)
) : ListAdapter<AttachmentSelection, AttachmentsAdapter.ViewHolder>(AttachmentDiffCallback()) {
private val config = activity.config
private val resources = activity.resources
private val primaryColor = activity.getProperPrimaryColor()
private val imageCompressor by lazy { ImageCompressor(activity) }
val attachments = mutableListOf<AttachmentSelection>()
fun clear() {
attachments.clear()
submitList(ArrayList())
onAttachmentsRemoved()
}
fun addAttachment(attachment: AttachmentSelection) {
attachments.removeAll { AttachmentSelection.areItemsTheSame(it, attachment) }
attachments.add(attachment)
submitList(attachments.toList())
}
private fun removeAttachment(attachment: AttachmentSelection) {
attachments.removeAll { AttachmentSelection.areItemsTheSame(it, attachment) }
if (attachments.isEmpty()) {
onAttachmentsRemoved()
} else {
submitList(ArrayList(attachments))
}
}
override fun getItemViewType(position: Int): Int {
return getItem(position).viewType
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutRes = when (viewType) {
ATTACHMENT_DOCUMENT -> com.simplemobiletools.smsmessenger.R.layout.item_attachment_document_preview
ATTACHMENT_VCARD -> com.simplemobiletools.smsmessenger.R.layout.item_attachment_vcard_preview
ATTACHMENT_MEDIA -> com.simplemobiletools.smsmessenger.R.layout.item_attachment_media_preview
else -> throw IllegalArgumentException("Unknown view type: $viewType")
}
val view = activity.layoutInflater.inflate(layoutRes, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val attachment = getItem(position)
holder.bindView(attachment, allowSingleClick = true, allowLongClick = false) { view, position ->
when (attachment.viewType) {
ATTACHMENT_DOCUMENT -> {
view.setupDocumentPreview(
uri = attachment.uri,
title = attachment.filename,
attachment = true,
onClick = { activity.launchViewIntent(attachment.uri, attachment.mimetype, attachment.filename) },
onRemoveButtonClicked = { removeAttachment(attachment) }
)
}
ATTACHMENT_VCARD -> {
view.setupVCardPreview(
activity = activity,
uri = attachment.uri,
attachment = true,
onClick = {
val intent = Intent(activity, VCardViewerActivity::class.java).also {
it.putExtra(EXTRA_VCARD_URI, attachment.uri)
}
activity.startActivity(intent)
},
onRemoveButtonClicked = { removeAttachment(attachment) }
)
}
ATTACHMENT_MEDIA -> setupMediaPreview(view, attachment)
}
}
}
private fun setupMediaPreview(view: View, attachment: AttachmentSelection) {
view.apply {
media_attachment_holder.background.applyColorFilter(primaryColor.darkenColor())
media_attachment_holder.setOnClickListener {
activity.launchViewIntent(attachment.uri, attachment.mimetype, attachment.filename)
}
remove_attachment_button.apply {
beVisible()
background.applyColorFilter(primaryColor)
setOnClickListener {
removeAttachment(attachment)
}
}
if (attachment.mimetype.isImageMimeType() && attachment.isPending && config.mmsFileSizeLimit != FILE_SIZE_NONE) {
thumbnail.beGone()
compression_progress.beVisible()
imageCompressor.compressImage(attachment.uri, config.mmsFileSizeLimit) { compressedUri ->
activity.runOnUiThread {
when (compressedUri) {
attachment.uri -> {
attachments.find { it.uri == attachment.uri }?.isPending = false
loadMediaPreview(view, attachment)
}
null -> {
activity.toast(com.simplemobiletools.smsmessenger.R.string.compress_error)
removeAttachment(attachment)
}
else -> {
attachments.remove(attachment)
addAttachment(attachment.copy(uri = compressedUri, isPending = false))
}
}
onReady()
}
}
} else {
loadMediaPreview(view, attachment)
}
}
}
private fun loadMediaPreview(view: View, attachment: AttachmentSelection) {
val roundedCornersRadius = resources.getDimension(com.simplemobiletools.smsmessenger.R.dimen.activity_margin).toInt()
val size = resources.getDimension(com.simplemobiletools.smsmessenger.R.dimen.attachment_preview_size).toInt()
val options = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.transform(CenterCrop(), RoundedCorners(roundedCornersRadius))
Glide.with(view.thumbnail)
.load(attachment.uri)
.transition(DrawableTransitionOptions.withCrossFade())
.override(size, size)
.apply(options)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
removeAttachment(attachment)
activity.toast(com.simplemobiletools.smsmessenger.R.string.unknown_error_occurred)
return false
}
override fun onResourceReady(dr: Drawable?, a: Any?, t: Target<Drawable>?, d: DataSource?, i: Boolean): Boolean {
view.thumbnail.beVisible()
view.play_icon.beVisibleIf(attachment.mimetype.isVideoMimeType())
view.compression_progress.beGone()
return false
}
})
.into(view.thumbnail)
}
open inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bindView(
any: AttachmentSelection,
allowSingleClick: Boolean,
allowLongClick: Boolean,
callback: (itemView: View, adapterPosition: Int) -> Unit
): View {
return itemView.apply {
callback(this, adapterPosition)
if (allowSingleClick) {
setOnClickListener { viewClicked(any) }
setOnLongClickListener { if (allowLongClick) viewLongClicked() else viewClicked(any); true }
} else {
setOnClickListener(null)
setOnLongClickListener(null)
}
}
}
private fun viewClicked(any: AttachmentSelection) {
onItemClick.invoke(any)
}
private fun viewLongClicked() {
}
}
}
private class AttachmentDiffCallback : DiffUtil.ItemCallback<AttachmentSelection>() {
override fun areItemsTheSame(oldItem: AttachmentSelection, newItem: AttachmentSelection): Boolean {
return AttachmentSelection.areItemsTheSame(oldItem, newItem)
}
override fun areContentsTheSame(oldItem: AttachmentSelection, newItem: AttachmentSelection): Boolean {
return AttachmentSelection.areContentsTheSame(oldItem, newItem)
}
}

View File

@ -1,13 +1,11 @@
package com.simplemobiletools.smsmessenger.adapters package com.simplemobiletools.smsmessenger.adapters
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri
import android.util.TypedValue import android.util.TypedValue
import android.view.Menu import android.view.Menu
import android.view.View import android.view.View
@ -38,7 +36,6 @@ import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.* import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.models.* import com.simplemobiletools.smsmessenger.models.*
import kotlinx.android.synthetic.main.item_attachment_image.view.* import kotlinx.android.synthetic.main.item_attachment_image.view.*
import kotlinx.android.synthetic.main.item_attachment_vcard.view.*
import kotlinx.android.synthetic.main.item_received_message.view.* import kotlinx.android.synthetic.main.item_received_message.view.*
import kotlinx.android.synthetic.main.item_received_message.view.thread_mesage_attachments_holder import kotlinx.android.synthetic.main.item_received_message.view.thread_mesage_attachments_holder
import kotlinx.android.synthetic.main.item_received_message.view.thread_message_body import kotlinx.android.synthetic.main.item_received_message.view.thread_message_body
@ -49,8 +46,6 @@ import kotlinx.android.synthetic.main.item_thread_date_time.view.*
import kotlinx.android.synthetic.main.item_thread_error.view.* import kotlinx.android.synthetic.main.item_thread_error.view.*
import kotlinx.android.synthetic.main.item_thread_sending.view.* import kotlinx.android.synthetic.main.item_thread_sending.view.*
import kotlinx.android.synthetic.main.item_thread_success.view.* import kotlinx.android.synthetic.main.item_thread_success.view.*
import kotlinx.android.synthetic.main.item_unknown_attachment.view.*
import java.util.*
class ThreadAdapter( class ThreadAdapter(
activity: SimpleActivity, var messages: ArrayList<ThreadItem>, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit, val onThreadIdUpdate: (Long) -> Unit activity: SimpleActivity, var messages: ArrayList<ThreadItem>, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit, val onThreadIdUpdate: (Long) -> Unit
@ -386,7 +381,7 @@ class ThreadAdapter(
if (actModeCallback.isSelectable) { if (actModeCallback.isSelectable) {
holder.viewClicked(message) holder.viewClicked(message)
} else { } else {
launchViewIntent(uri, mimetype, attachment.filename) activity.launchViewIntent(uri, mimetype, attachment.filename)
} }
} }
imageView.setOnLongClickListener { imageView.setOnLongClickListener {
@ -400,34 +395,10 @@ class ThreadAdapter(
val uri = attachment.getUri() val uri = attachment.getUri()
parent.apply { parent.apply {
val vCardView = layoutInflater.inflate(R.layout.item_attachment_vcard, null).apply { val vCardView = layoutInflater.inflate(R.layout.item_attachment_vcard, null).apply {
background.applyColorFilter(backgroundColor.getContrastColor()) setupVCardPreview(
vcard_title.setTextColor(textColor) activity = activity,
vcard_subtitle.setTextColor(textColor) uri = uri,
view_contact_details.setTextColor(properPrimaryColor) onClick = {
}
thread_mesage_attachments_holder.addView(vCardView)
parseVCardFromUri(context, uri) { vCards ->
val title = vCards.firstOrNull()?.parseNameFromVCard()
val imageIcon = if (title != null) {
SimpleContactsHelper(context).getContactLetterIcon(title)
} else {
null
}
activity.runOnUiThread {
vCardView.apply {
vcard_title.text = title
vcard_photo.setImageBitmap(imageIcon)
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()
}
setOnClickListener {
if (actModeCallback.isSelectable) { if (actModeCallback.isSelectable) {
holder.viewClicked(message) holder.viewClicked(message)
} else { } else {
@ -436,14 +407,11 @@ class ThreadAdapter(
} }
context.startActivity(intent) context.startActivity(intent)
} }
},
onLongClick = { holder.viewLongClicked() }
)
} }
setOnLongClickListener { thread_mesage_attachments_holder.addView(vCardView)
holder.viewLongClicked()
true
}
}
}
}
} }
} }
@ -451,66 +419,24 @@ class ThreadAdapter(
val mimetype = attachment.mimetype val mimetype = attachment.mimetype
val uri = attachment.getUri() val uri = attachment.getUri()
parent.apply { parent.apply {
val attachmentView = layoutInflater.inflate(R.layout.item_unknown_attachment, null).apply { val attachmentView = layoutInflater.inflate(R.layout.item_attachment_document, null).apply {
if (attachment.filename.isNotEmpty()) { setupDocumentPreview(
filename.text = attachment.filename uri = uri,
} title = attachment.filename,
onClick = {
val size = context.contentResolver
.openInputStream(uri)
?.use { it.readBytes() }
?.size
if (size != null) {
file_size.beVisible()
file_size.text = size.formatSize()
} else {
file_size.beGone()
}
background.applyColorFilter(textColor)
filename.setTextColor(textColor)
file_size.setTextColor(textColor)
icon.background.setTint(properPrimaryColor)
setOnClickListener {
if (actModeCallback.isSelectable) { if (actModeCallback.isSelectable) {
holder.viewClicked(message) holder.viewClicked(message)
} else { } else {
launchViewIntent(uri, mimetype, attachment.filename) activity.launchViewIntent(uri, mimetype, attachment.filename)
}
}
setOnLongClickListener {
holder.viewLongClicked()
true
} }
},
onLongClick = { holder.viewLongClicked() },
)
} }
thread_mesage_attachments_holder.addView(attachmentView) thread_mesage_attachments_holder.addView(attachmentView)
} }
} }
private fun launchViewIntent(uri: Uri, mimetype: String, filename: String) {
Intent().apply {
action = Intent.ACTION_VIEW
setDataAndType(uri, mimetype.lowercase(Locale.getDefault()))
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
try {
activity.hideKeyboard()
activity.startActivity(this)
} catch (e: ActivityNotFoundException) {
val newMimetype = filename.getMimeType()
if (newMimetype.isNotEmpty() && mimetype != newMimetype) {
launchViewIntent(uri, newMimetype, filename)
} else {
activity.toast(R.string.no_app_found)
}
} catch (e: Exception) {
activity.showErrorToast(e)
}
}
}
private fun setupDateTime(view: View, dateTime: ThreadDateTime) { private fun setupDateTime(view: View, dateTime: ThreadDateTime) {
view.apply { view.apply {
thread_date_time.apply { thread_date_time.apply {

View File

@ -4,10 +4,12 @@ import android.app.Activity
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import com.simplemobiletools.commons.extensions.getMimeType
import com.simplemobiletools.commons.extensions.hideKeyboard import com.simplemobiletools.commons.extensions.hideKeyboard
import com.simplemobiletools.commons.extensions.showErrorToast import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.extensions.toast import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.R
import java.util.*
fun Activity.dialNumber(phoneNumber: String, callback: (() -> Unit)? = null) { fun Activity.dialNumber(phoneNumber: String, callback: (() -> Unit)? = null) {
hideKeyboard() hideKeyboard()
@ -24,3 +26,25 @@ fun Activity.dialNumber(phoneNumber: String, callback: (() -> Unit)? = null) {
} }
} }
} }
fun Activity.launchViewIntent(uri: Uri, mimetype: String, filename: String) {
Intent().apply {
action = Intent.ACTION_VIEW
setDataAndType(uri, mimetype.lowercase(Locale.getDefault()))
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
try {
hideKeyboard()
startActivity(this)
} catch (e: ActivityNotFoundException) {
val newMimetype = filename.getMimeType()
if (newMimetype.isNotEmpty() && mimetype != newMimetype) {
launchViewIntent(uri, newMimetype, filename)
} else {
toast(R.string.no_app_found)
}
} catch (e: Exception) {
showErrorToast(e)
}
}
}

View File

@ -335,6 +335,7 @@ fun Context.getMmsAttachment(id: Long, getImageResolutions: Boolean): MessageAtt
val attachment = Attachment(partId, id, Uri.withAppendedPath(uri, partId.toString()).toString(), mimetype, 0, 0, attachmentName) val attachment = Attachment(partId, id, Uri.withAppendedPath(uri, partId.toString()).toString(), mimetype, 0, 0, attachmentName)
messageAttachment.attachments.add(attachment) messageAttachment.attachments.add(attachment)
} else { } else {
// todo: fix filename parsing, xml is shown some sometimes
val text = cursor.getStringValue(Mms.Part.TEXT) val text = cursor.getStringValue(Mms.Part.TEXT)
val cutName = text.substringAfter("ref src=\"").substringBefore("\"") val cutName = text.substringAfter("ref src=\"").substringBefore("\"")
if (cutName.isNotEmpty()) { if (cutName.isNotEmpty()) {

View File

@ -0,0 +1,18 @@
package com.simplemobiletools.smsmessenger.extensions
import android.animation.ObjectAnimator
import android.view.View
import androidx.core.animation.doOnStart
import androidx.core.view.isVisible
fun View.showWithAnimation(duration: Long = 250L) {
if (!isVisible) {
ObjectAnimator.ofFloat(
this, "alpha", 0f, 1f
).apply {
this.duration = duration
doOnStart { visibility = View.VISIBLE }
}.start()
}
}

View File

@ -86,4 +86,8 @@ class Config(context: Context) : BaseConfig(context) {
var wasDbCleared: Boolean var wasDbCleared: Boolean
get() = prefs.getBoolean(WAS_DB_CLEARED, false) get() = prefs.getBoolean(WAS_DB_CLEARED, false)
set(wasDbCleared) = prefs.edit().putBoolean(WAS_DB_CLEARED, wasDbCleared).apply() set(wasDbCleared) = prefs.edit().putBoolean(WAS_DB_CLEARED, wasDbCleared).apply()
var keyboardHeight: Int
get() = prefs.getInt(SOFT_KEYBOARD_HEIGHT, 600)
set(value) = prefs.edit().putInt(SOFT_KEYBOARD_HEIGHT, value).apply()
} }

View File

@ -30,6 +30,7 @@ const val IMPORT_MMS = "import_mms"
const val WAS_DB_CLEARED = "was_db_cleared_2" const val WAS_DB_CLEARED = "was_db_cleared_2"
const val EXTRA_VCARD_URI = "vcard" const val EXTRA_VCARD_URI = "vcard"
const val SCHEDULED_MESSAGE_ID = "scheduled_message_id" const val SCHEDULED_MESSAGE_ID = "scheduled_message_id"
const val SOFT_KEYBOARD_HEIGHT = "soft_keyboard_height"
private const val PATH = "com.simplemobiletools.smsmessenger.action." private const val PATH = "com.simplemobiletools.smsmessenger.action."
const val MARK_AS_READ = PATH + "mark_as_read" const val MARK_AS_READ = PATH + "mark_as_read"
@ -45,6 +46,11 @@ const val THREAD_SENT_MESSAGE_ERROR = 4
const val THREAD_SENT_MESSAGE_SENT = 5 const val THREAD_SENT_MESSAGE_SENT = 5
const val THREAD_SENT_MESSAGE_SENDING = 6 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 // lock screen visibility constants
const val LOCK_SCREEN_SENDER_MESSAGE = 1 const val LOCK_SCREEN_SENDER_MESSAGE = 1
const val LOCK_SCREEN_SENDER = 2 const val LOCK_SCREEN_SENDER = 2
@ -60,6 +66,15 @@ const val FILE_SIZE_2_MB = 2_097_152L
const val MESSAGES_LIMIT = 50 const val MESSAGES_LIMIT = 50
// intent launch request codes
const val PICK_PHOTO_VIDEO_INTENT = 42
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() { fun refreshMessages() {
EventBus.getDefault().post(Events.RefreshMessages()) EventBus.getDefault().post(Events.RefreshMessages())
} }

View File

@ -0,0 +1,55 @@
package com.simplemobiletools.smsmessenger.helpers
import android.net.Uri
import android.view.View
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.smsmessenger.extensions.getFileSizeFromUri
import kotlinx.android.synthetic.main.item_attachment_document.view.*
import kotlinx.android.synthetic.main.item_remove_attachment_button.view.*
fun View.setupDocumentPreview(
uri: Uri,
title: String,
attachment: Boolean = false,
onClick: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null,
onRemoveButtonClicked: (() -> Unit)? = null
) {
if (title.isNotEmpty()) {
filename.text = title
}
val size = context.getFileSizeFromUri(uri)
file_size.beVisible()
file_size.text = size.formatSize()
val textColor = context.getProperTextColor()
val primaryColor = context.getProperPrimaryColor()
document_attachment_holder.background.applyColorFilter(textColor)
filename.setTextColor(textColor)
file_size.setTextColor(textColor)
// todo: set icon drawable based on mime type
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
}
}

View File

@ -0,0 +1,83 @@
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 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.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 ->
val title = vCards.firstOrNull()?.parseNameFromVCard()
val imageIcon = if (title != null) {
SimpleContactsHelper(activity).getContactLetterIcon(title)
} else {
null
}
activity.runOnUiThread {
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
}
}
}
}

View File

@ -1,8 +1,35 @@
package com.simplemobiletools.smsmessenger.models package com.simplemobiletools.smsmessenger.models
import android.net.Uri import android.net.Uri
import com.simplemobiletools.smsmessenger.extensions.isImageMimeType
import com.simplemobiletools.smsmessenger.extensions.isVCardMimeType
import com.simplemobiletools.smsmessenger.extensions.isVideoMimeType
import com.simplemobiletools.smsmessenger.helpers.ATTACHMENT_DOCUMENT
import com.simplemobiletools.smsmessenger.helpers.ATTACHMENT_MEDIA
import com.simplemobiletools.smsmessenger.helpers.ATTACHMENT_VCARD
data class AttachmentSelection( data class AttachmentSelection(
val uri: Uri, val uri: Uri,
val isPending: Boolean, val mimetype: String,
) val filename: String,
var isPending: Boolean,
val viewType: Int = getViewTypeForMimeType(mimetype)
) {
companion object {
fun getViewTypeForMimeType(mimetype: String): Int {
return when {
mimetype.isImageMimeType() || mimetype.isVideoMimeType() -> ATTACHMENT_MEDIA
mimetype.isVCardMimeType() -> ATTACHMENT_VCARD
else -> ATTACHMENT_DOCUMENT
}
}
fun areItemsTheSame(first: AttachmentSelection, second: AttachmentSelection): Boolean {
return first.uri == second.uri
}
fun areContentsTheSame(first: AttachmentSelection, second: AttachmentSelection): Boolean {
return first.uri == second.uri && first.mimetype == second.mimetype && first.filename == second.filename
}
}
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,3v10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55 -2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4V7h4V3h-6z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M10,16.5l6,-4.5 -6,-4.5v9zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M17,10.5V7c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h12c0.55,0 1,-0.45 1,-1v-3.5l4,4v-11l-4,4z" />
</vector>

View File

@ -22,10 +22,10 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<RelativeLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/thread_holder" android:id="@+id/thread_holder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_marginBottom="@dimen/tiny_margin" android:layout_marginBottom="@dimen/tiny_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"> app:layout_behavior="@string/appbar_scrolling_view_behavior">
@ -34,6 +34,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone" android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible"> tools:visibility="visible">
<LinearLayout <LinearLayout
@ -106,9 +107,9 @@
<com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller <com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
android:id="@+id/thread_messages_fastscroller" android:id="@+id/thread_messages_fastscroller"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_above="@+id/message_divider" app:layout_constraintBottom_toTopOf="@id/message_divider"
android:layout_below="@+id/thread_add_contacts" app:layout_constraintTop_toBottomOf="@id/thread_add_contacts"
app:supportSwipeToRefresh="true"> app:supportSwipeToRefresh="true">
<com.simplemobiletools.commons.views.MyRecyclerView <com.simplemobiletools.commons.views.MyRecyclerView
@ -130,43 +131,47 @@
android:id="@+id/message_divider" android:id="@+id/message_divider"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1px" android:layout_height="1px"
android:layout_above="@+id/scheduled_message_holder"
android:background="@color/divider_grey" android:background="@color/divider_grey"
android:importantForAccessibility="no" /> android:importantForAccessibility="no"
app:layout_constraintBottom_toTopOf="@id/scheduled_message_holder"
app:layout_constraintTop_toBottomOf="@id/thread_messages_fastscroller"
tools:layout_height="1dp" />
<ImageView <ImageView
android:id="@+id/thread_add_attachment" android:id="@+id/thread_add_attachment"
android:layout_width="@dimen/normal_icon_size" android:layout_width="@dimen/normal_icon_size"
android:layout_height="@dimen/normal_icon_size" android:layout_height="@dimen/normal_icon_size"
android:layout_alignParentStart="true" android:layout_marginStart="@dimen/small_margin"
android:layout_alignParentBottom="true"
android:layout_marginTop="@dimen/small_margin"
android:layout_marginEnd="@dimen/small_margin"
android:alpha="0.9" android:alpha="0.9"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/attachment" android:contentDescription="@string/attachment"
android:padding="@dimen/normal_margin" android:padding="@dimen/normal_margin"
android:src="@drawable/ic_plus_vector" /> android:src="@drawable/ic_plus_vector"
app:layout_constraintBottom_toTopOf="@id/attachment_picker_divider"
app:layout_constraintStart_toStartOf="parent" />
<RelativeLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/scheduled_message_holder" android:id="@+id/scheduled_message_holder"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_above="@+id/thread_attachments_holder" android:layout_marginStart="@dimen/normal_margin"
android:layout_alignStart="@id/thread_type_message"
android:layout_marginTop="@dimen/medium_margin" android:layout_marginTop="@dimen/medium_margin"
android:layout_marginEnd="@dimen/medium_margin" android:layout_marginEnd="@dimen/medium_margin"
android:layout_marginBottom="@dimen/small_margin"
android:background="@drawable/section_holder_stroke" android:background="@drawable/section_holder_stroke"
android:orientation="horizontal"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/thread_attachments_recyclerview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/message_divider"
app:layout_goneMarginBottom="@dimen/medium_margin"
tools:visibility="visible"> tools:visibility="visible">
<com.simplemobiletools.commons.views.MyTextView <com.simplemobiletools.commons.views.MyTextView
android:id="@+id/scheduled_message_button" android:id="@+id/scheduled_message_button"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentStart="true" android:layout_gravity="start"
android:layout_alignParentEnd="true"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
android:clickable="true" android:clickable="true"
android:drawableStart="@drawable/ic_clock_vector" android:drawableStart="@drawable/ic_clock_vector"
@ -184,57 +189,55 @@
android:id="@+id/discard_scheduled_message" android:id="@+id/discard_scheduled_message"
android:layout_width="@dimen/normal_icon_size" android:layout_width="@dimen/normal_icon_size"
android:layout_height="@dimen/normal_icon_size" android:layout_height="@dimen/normal_icon_size"
android:layout_alignParentEnd="true" android:layout_gravity="end"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/cancel_schedule_send" android:contentDescription="@string/cancel_schedule_send"
android:padding="@dimen/normal_margin" android:padding="@dimen/normal_margin"
android:src="@drawable/ic_cross_vector" /> android:src="@drawable/ic_cross_vector" />
</RelativeLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
<HorizontalScrollView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/thread_attachments_holder" android:id="@+id/thread_attachments_recyclerview"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_above="@+id/thread_type_message" android:layout_marginTop="@dimen/tiny_margin"
android:layout_alignStart="@+id/thread_type_message"
android:layout_marginTop="@dimen/medium_margin"
android:layout_marginBottom="@dimen/small_margin" android:layout_marginBottom="@dimen/small_margin"
android:overScrollMode="never" android:clipToPadding="false"
android:scrollbars="none"
android:visibility="gone">
<LinearLayout
android:id="@+id/thread_attachments_wrapper"
android:layout_width="wrap_content"
android:layout_height="@dimen/attachment_preview_size"
android:divider="@drawable/linear_layout_horizontal_divider"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingStart="@dimen/normal_margin"
android:paddingEnd="@dimen/normal_margin" android:paddingEnd="@dimen/normal_margin"
android:showDividers="middle" /> android:scrollbars="none"
</HorizontalScrollView> android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toTopOf="@id/thread_type_message"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/scheduled_message_holder"
app:layout_goneMarginTop="@dimen/medium_margin"
tools:itemCount="2"
tools:listitem="@layout/item_attachment_document_preview"
tools:visibility="visible" />
<com.simplemobiletools.commons.views.MyEditText <com.simplemobiletools.commons.views.MyEditText
android:id="@+id/thread_type_message" android:id="@+id/thread_type_message"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentBottom="true" android:layout_marginStart="@dimen/small_margin"
android:layout_marginEnd="@dimen/small_margin" android:layout_marginEnd="@dimen/small_margin"
android:layout_toStartOf="@+id/thread_select_sim_icon"
android:layout_toEndOf="@+id/thread_add_attachment"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:gravity="center_vertical" android:gravity="center_vertical"
android:hint="@string/type_a_message" android:hint="@string/type_a_message"
android:inputType="textCapSentences|textMultiLine" android:inputType="textCapSentences|textMultiLine"
android:minHeight="@dimen/normal_icon_size" /> android:minHeight="@dimen/normal_icon_size"
app:layout_constraintBottom_toTopOf="@id/attachment_picker_divider"
app:layout_constraintEnd_toStartOf="@id/thread_select_sim_icon"
app:layout_constraintStart_toEndOf="@+id/thread_add_attachment" />
<ImageView <ImageView
android:id="@+id/thread_select_sim_icon" android:id="@+id/thread_select_sim_icon"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginTop="@dimen/small_margin"
android:layout_toStartOf="@+id/thread_character_counter"
android:alpha="0.9" android:alpha="0.9"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:paddingStart="@dimen/medium_margin" android:paddingStart="@dimen/medium_margin"
@ -242,43 +245,46 @@
android:paddingEnd="@dimen/medium_margin" android:paddingEnd="@dimen/medium_margin"
android:paddingBottom="@dimen/normal_margin" android:paddingBottom="@dimen/normal_margin"
android:src="@drawable/ic_sim_vector" android:src="@drawable/ic_sim_vector"
android:visibility="gone" /> android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/attachment_picker_divider"
app:layout_constraintEnd_toStartOf="@id/thread_character_counter"
tools:visibility="visible" />
<TextView <TextView
android:id="@+id/thread_select_sim_number" android:id="@+id/thread_select_sim_number"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_alignStart="@+id/thread_select_sim_icon"
android:layout_alignTop="@+id/thread_select_sim_icon"
android:layout_alignEnd="@+id/thread_select_sim_icon"
android:layout_alignBottom="@+id/thread_select_sim_icon"
android:gravity="center" android:gravity="center"
android:textSize="@dimen/normal_text_size" android:textSize="@dimen/normal_text_size"
android:visibility="gone" android:visibility="gone"
tools:text="1" /> app:layout_constraintBottom_toBottomOf="@id/thread_select_sim_icon"
app:layout_constraintEnd_toEndOf="@id/thread_select_sim_icon"
app:layout_constraintStart_toStartOf="@id/thread_select_sim_icon"
app:layout_constraintTop_toTopOf="@id/thread_select_sim_icon"
tools:text="1"
tools:textColor="@color/dark_grey"
tools:visibility="visible" />
<com.simplemobiletools.commons.views.MyTextView <com.simplemobiletools.commons.views.MyTextView
android:id="@+id/thread_character_counter" android:id="@+id/thread_character_counter"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_alignTop="@+id/thread_send_message"
android:layout_alignBottom="@+id/thread_send_message"
android:layout_toStartOf="@+id/thread_send_message"
android:gravity="center" android:gravity="center"
android:paddingStart="@dimen/small_margin" android:paddingStart="@dimen/small_margin"
android:paddingEnd="@dimen/small_margin" android:paddingEnd="@dimen/small_margin"
android:text="0" android:text="0"
android:textSize="@dimen/normal_text_size" android:textSize="@dimen/normal_text_size"
android:visibility="gone" android:visibility="gone"
tools:ignore="HardcodedText" /> app:layout_constraintBottom_toTopOf="@id/attachment_picker_divider"
app:layout_constraintEnd_toStartOf="@+id/thread_send_message"
app:layout_constraintTop_toTopOf="@+id/thread_send_message"
tools:ignore="HardcodedText"
tools:visibility="visible" />
<com.simplemobiletools.commons.views.MyButton <com.simplemobiletools.commons.views.MyButton
android:id="@+id/thread_send_message" android:id="@+id/thread_send_message"
android:layout_width="@dimen/normal_icon_size" android:layout_width="@dimen/normal_icon_size"
android:layout_height="@dimen/normal_icon_size" android:layout_height="@dimen/normal_icon_size"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_marginTop="@dimen/small_margin"
android:layout_marginEnd="@dimen/small_margin" android:layout_marginEnd="@dimen/small_margin"
android:alpha="0.4" android:alpha="0.4"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
@ -287,7 +293,139 @@
android:drawableTop="@drawable/ic_send_vector" android:drawableTop="@drawable/ic_send_vector"
android:paddingVertical="@dimen/small_margin" android:paddingVertical="@dimen/small_margin"
android:text="@string/sms" android:text="@string/sms"
android:textSize="@dimen/smaller_text_size" /> android:textSize="@dimen/smaller_text_size"
app:layout_constraintBottom_toTopOf="@id/attachment_picker_divider"
app:layout_constraintEnd_toEndOf="parent" />
</RelativeLayout> <View
android:id="@+id/attachment_picker_divider"
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:background="@color/divider_grey"
android:importantForAccessibility="no"
app:layout_constraintBottom_toTopOf="@id/attachment_picker_holder" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/attachment_picker_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingVertical="@dimen/normal_margin"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/pick_from_gallery"
android:layout_width="@dimen/attachment_button_width"
android:layout_height="@dimen/attachment_button_height"
android:background="?selectableItemBackgroundBorderless"
android:drawableTop="@drawable/ic_image_vector"
android:paddingHorizontal="@dimen/medium_margin"
android:paddingTop="@dimen/medium_margin"
android:text="@string/gallery_short"
android:textAllCaps="false"
android:textColor="@color/default_text_color"
app:layout_constraintEnd_toStartOf="@id/camera"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/camera"
android:layout_width="@dimen/attachment_button_width"
android:layout_height="@dimen/attachment_button_height"
android:background="?selectableItemBackgroundBorderless"
android:drawableTop="@drawable/ic_camera_vector"
android:paddingHorizontal="@dimen/medium_margin"
android:paddingTop="@dimen/medium_margin"
android:text="@string/take_photo"
android:textAllCaps="false"
android:textColor="@color/default_text_color"
app:layout_constraintEnd_toStartOf="@id/record_video"
app:layout_constraintStart_toEndOf="@id/pick_from_gallery"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/record_video"
android:layout_width="@dimen/attachment_button_width"
android:layout_height="@dimen/attachment_button_height"
android:background="?selectableItemBackgroundBorderless"
android:drawableTop="@drawable/ic_videocam_vector"
android:paddingHorizontal="@dimen/medium_margin"
android:paddingTop="@dimen/medium_margin"
android:text="Record Video"
android:textAllCaps="false"
android:textColor="@color/default_text_color"
app:layout_constraintEnd_toStartOf="@id/record_audio"
app:layout_constraintStart_toEndOf="@id/camera"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/record_audio"
android:layout_width="@dimen/attachment_button_width"
android:layout_height="@dimen/attachment_button_height"
android:background="?selectableItemBackgroundBorderless"
android:drawableTop="@drawable/ic_microphone_vector"
android:paddingHorizontal="@dimen/medium_margin"
android:paddingTop="@dimen/medium_margin"
android:text="Record Audio"
android:textAllCaps="false"
android:textColor="@color/default_text_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/record_video"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/pick_file"
android:layout_width="@dimen/attachment_button_width"
android:layout_height="@dimen/attachment_button_height"
android:layout_marginTop="@dimen/normal_margin"
android:background="?selectableItemBackgroundBorderless"
android:drawableTop="@drawable/ic_document_vector"
android:paddingHorizontal="@dimen/medium_margin"
android:paddingTop="@dimen/medium_margin"
android:text="Files"
android:textAllCaps="false"
android:textColor="@color/default_text_color"
app:layout_constraintStart_toStartOf="@id/pick_from_gallery"
app:layout_constraintTop_toBottomOf="@id/pick_from_gallery" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/pick_contact"
android:layout_width="@dimen/attachment_button_width"
android:layout_height="@dimen/attachment_button_height"
android:layout_marginTop="@dimen/normal_margin"
android:background="?selectableItemBackgroundBorderless"
android:drawableTop="@drawable/ic_person_vector"
android:paddingHorizontal="@dimen/medium_margin"
android:paddingTop="@dimen/medium_margin"
android:text="@string/contacts_short"
android:textAllCaps="false"
android:textColor="@color/default_text_color"
app:layout_constraintStart_toStartOf="@id/camera"
app:layout_constraintTop_toBottomOf="@id/camera" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/schedule_message"
android:layout_width="@dimen/attachment_button_width"
android:layout_height="@dimen/attachment_button_height"
android:layout_marginTop="@dimen/normal_margin"
android:background="?selectableItemBackgroundBorderless"
android:drawableTop="@drawable/ic_clock_vector"
android:paddingHorizontal="@dimen/medium_margin"
android:paddingTop="@dimen/medium_margin"
android:text="@string/schedule_message"
android:textAllCaps="false"
android:textColor="@color/default_text_color"
app:layout_constraintStart_toEndOf="@id/pick_contact"
app:layout_constraintStart_toStartOf="@id/record_video"
app:layout_constraintTop_toBottomOf="@id/record_video" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/conversation_holder"
android:layout_width="@dimen/attachment_preview_size"
android:layout_height="@dimen/attachment_preview_size">
<ImageView
android:id="@+id/thread_attachment_preview"
android:layout_width="@dimen/attachment_preview_size"
android:layout_height="@dimen/attachment_preview_size"
android:visibility="gone"
tools:visibility="visible" />
<ProgressBar
android:id="@+id/thread_attachment_progress"
android:layout_width="@dimen/remove_attachment_size"
android:layout_height="@dimen/remove_attachment_size"
android:layout_centerInParent="true"
android:visibility="gone"
tools:visibility="visible" />
<ImageView
android:id="@+id/thread_remove_attachment"
android:layout_width="@dimen/remove_attachment_size"
android:layout_height="@dimen/remove_attachment_size"
android:layout_alignTop="@+id/thread_attachment_preview"
android:layout_alignEnd="@+id/thread_attachment_preview"
android:padding="@dimen/tiny_margin"
android:src="@drawable/ic_cross_vector"
android:visibility="gone"
tools:visibility="visible" />
</RelativeLayout>

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/document_attachment_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/section_holder_stroke"
android:foreground="@drawable/ripple_all_corners"
android:minHeight="@dimen/attachment_preview_min_height"
android:orientation="horizontal"
android:padding="@dimen/normal_margin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_min="@dimen/attachment_preview_min_height"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/icon"
android:layout_width="@dimen/medium_icon_size"
android:layout_height="@dimen/medium_icon_size"
android:background="@drawable/circle_background"
android:padding="@dimen/medium_margin"
app:srcCompat="@drawable/ic_document_vector" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/medium_margin"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/filename"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="middle"
android:singleLine="true"
android:text="@string/attachment"
android:textSize="@dimen/normal_text_size"
android:textStyle="bold"
tools:text="Event_16_02_2022.ics" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/file_size"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/normal_text_size"
tools:text="2.18 KB" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/thread_attachment_wrapper"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<include
layout="@layout/item_attachment_document"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_min="@dimen/attachment_preview_min_height"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1"
app:layout_constraintWidth_max="@dimen/attachment_preview_max_width" />
<include
layout="@layout/item_remove_attachment_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/document_attachment_holder"
app:layout_constraintEnd_toEndOf="@id/document_attachment_holder"
app:layout_constraintStart_toEndOf="@id/document_attachment_holder"
app:layout_constraintTop_toTopOf="@id/document_attachment_holder" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/thread_attachment_wrapper"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/media_attachment_holder"
android:layout_width="@dimen/attachment_preview_size"
android:layout_height="@dimen/attachment_preview_size"
android:background="@drawable/section_holder_stroke"
android:foreground="@drawable/ripple_all_corners"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_margin="1dp" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/play_icon"
android:layout_width="@dimen/remove_attachment_size"
android:layout_height="@dimen/remove_attachment_size"
android:layout_gravity="center"
android:alpha="0.8"
android:src="@drawable/ic_vector_play_circle_outline"
android:visibility="gone"
tools:visibility="visible" />
<ProgressBar
android:id="@+id/compression_progress"
android:layout_width="@dimen/remove_attachment_size"
android:layout_height="@dimen/remove_attachment_size"
android:layout_gravity="center" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<include
layout="@layout/item_remove_attachment_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/media_attachment_holder"
app:layout_constraintEnd_toEndOf="@id/media_attachment_holder"
app:layout_constraintStart_toEndOf="@id/media_attachment_holder"
app:layout_constraintTop_toTopOf="@id/media_attachment_holder" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,57 +1,63 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content" android:id="@+id/vcard_attachment_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/section_holder_stroke" android:background="@drawable/section_holder_stroke"
android:orientation="vertical" android:foreground="@drawable/ripple_all_corners"
android:minHeight="@dimen/attachment_preview_min_height"
android:orientation="horizontal"
android:padding="@dimen/normal_margin"> android:padding="@dimen/normal_margin">
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/vcard_photo" android:id="@+id/vcard_photo"
android:layout_width="@dimen/normal_icon_size" android:layout_width="@dimen/medium_icon_size"
android:layout_height="@dimen/normal_icon_size" android:layout_height="@dimen/medium_icon_size"
android:layout_gravity="center_vertical"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent" />
tools:src="@color/md_red" />
<TextView <LinearLayout
android:id="@+id/vcard_title" android:layout_width="match_parent"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="@dimen/medium_margin"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/vcard_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/normal_margin"
android:ellipsize="end" android:ellipsize="end"
android:textSize="@dimen/bigger_text_size" android:singleLine="true"
app:layout_constraintStart_toEndOf="@id/vcard_photo" android:textSize="@dimen/normal_text_size"
app:layout_constraintTop_toTopOf="parent" android:textStyle="bold"
tools:text="Bob" /> android:visibility="gone"
tools:text="Elon Reeve Musk"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/vcard_subtitle" android:id="@+id/vcard_subtitle"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/normal_margin" android:textSize="@dimen/smaller_text_size"
android:textSize="@dimen/normal_text_size"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/view_contact_details"
app:layout_constraintStart_toEndOf="@id/vcard_photo"
app:layout_constraintTop_toBottomOf="@id/vcard_title"
tools:text="and 6 others" tools:text="and 6 others"
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/view_contact_details" android:id="@+id/view_contact_details"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/normal_margin"
android:layout_marginTop="@dimen/small_margin" android:layout_marginTop="@dimen/small_margin"
android:text="@string/view_contact_details" android:text="@string/view_contact_details"
android:textColor="@color/color_primary" android:textColor="@color/color_primary"
android:textSize="@dimen/smaller_text_size" android:textSize="@dimen/smaller_text_size"
app:layout_constraintBottom_toBottomOf="parent" android:visibility="gone"
app:layout_constraintStart_toEndOf="@id/vcard_photo" tools:visibility="visible" />
app:layout_constraintTop_toBottomOf="@id/vcard_subtitle" /> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </LinearLayout>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/thread_attachment_wrapper"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<include
layout="@layout/item_attachment_vcard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_min="@dimen/attachment_preview_min_height"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1"
app:layout_constraintWidth_max="@dimen/attachment_preview_max_width" />
<ProgressBar
android:id="@+id/vcard_progress"
android:layout_width="@dimen/remove_attachment_size"
android:layout_height="@dimen/remove_attachment_size"
app:layout_constraintBottom_toBottomOf="@id/vcard_attachment_holder"
app:layout_constraintEnd_toEndOf="@id/vcard_attachment_holder"
app:layout_constraintStart_toStartOf="@id/vcard_attachment_holder"
app:layout_constraintTop_toTopOf="@id/vcard_attachment_holder" />
<include
layout="@layout/item_remove_attachment_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/vcard_attachment_holder"
app:layout_constraintEnd_toEndOf="@id/vcard_attachment_holder"
app:layout_constraintStart_toEndOf="@id/vcard_attachment_holder"
app:layout_constraintTop_toTopOf="@id/vcard_attachment_holder" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/remove_attachment_button"
android:layout_width="@dimen/remove_attachment_size"
android:layout_height="@dimen/remove_attachment_size"
android:layout_marginTop="@dimen/medium_margin"
android:layout_marginEnd="@dimen/medium_margin"
android:background="@drawable/button_background_rounded"
android:clickable="true"
android:focusable="true"
android:padding="@dimen/tiny_margin"
android:src="@drawable/ic_cross_vector"
android:visibility="gone"
tools:visibility="visible" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/thread_received_attachment_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/section_holder_stroke"
android:foreground="@drawable/ripple_all_corners"
android:orientation="vertical"
android:padding="@dimen/normal_margin">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/icon"
android:layout_width="@dimen/normal_icon_size"
android:layout_height="@dimen/normal_icon_size"
android:background="@drawable/circle_background"
android:padding="@dimen/normal_margin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_document_vector" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/filename"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/normal_margin"
android:ellipsize="middle"
android:singleLine="true"
android:text="@string/attachment"
android:textSize="@dimen/bigger_text_size"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/file_size"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/icon"
app:layout_constraintTop_toTopOf="@id/icon"
tools:text="Events_2022_02_16.ics" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/file_size"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/normal_margin"
android:textSize="@dimen/bigger_text_size"
app:layout_constraintBottom_toBottomOf="@id/icon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/icon"
tools:text="2.18 KB" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -3,9 +3,14 @@
<dimen name="notification_large_icon_size">72dp</dimen> <dimen name="notification_large_icon_size">72dp</dimen>
<dimen name="bigger_avatar_size">64dp</dimen> <dimen name="bigger_avatar_size">64dp</dimen>
<dimen name="play_outline_size">36dp</dimen> <dimen name="play_outline_size">36dp</dimen>
<dimen name="attachment_preview_size">60dp</dimen> <dimen name="attachment_preview_size">64dp</dimen>
<dimen name="attachment_preview_min_height">@dimen/attachment_preview_size</dimen>
<dimen name="attachment_preview_max_width">156dp</dimen>
<dimen name="remove_attachment_size">24dp</dimen> <dimen name="remove_attachment_size">24dp</dimen>
<dimen name="pin_icon_size">15dp</dimen> <dimen name="pin_icon_size">15dp</dimen>
<dimen name="vcard_property_start_margin">64dp</dimen> <dimen name="vcard_property_start_margin">64dp</dimen>
<dimen name="small_icon_size">20dp</dimen> <dimen name="small_icon_size">20dp</dimen>
<dimen name="medium_icon_size">36dp</dimen>
<dimen name="attachment_button_height">80dp</dimen>
<dimen name="attachment_button_width">90dp</dimen>
</resources> </resources>