From 23643d3198be6faa6dbe8bb9cd66235a6e046f82 Mon Sep 17 00:00:00 2001 From: Naveen Date: Wed, 2 Nov 2022 18:03:35 +0530 Subject: [PATCH] Add support for sending all types of files --- app/src/main/AndroidManifest.xml | 7 +- .../smsmessenger/activities/ThreadActivity.kt | 409 +++++++++++------- .../adapters/AttachmentsAdapter.kt | 227 ++++++++++ .../smsmessenger/adapters/ThreadAdapter.kt | 132 ++---- .../smsmessenger/extensions/Activity.kt | 24 + .../smsmessenger/extensions/Context.kt | 1 + .../smsmessenger/extensions/View.kt | 18 + .../smsmessenger/helpers/Config.kt | 4 + .../smsmessenger/helpers/Constants.kt | 15 + .../smsmessenger/helpers/DocumentPreview.kt | 55 +++ .../smsmessenger/helpers/VCardPreview.kt | 83 ++++ .../models/AttachmentSelection.kt | 31 +- app/src/main/res/drawable/ic_image_vector.xml | 10 + app/src/main/res/drawable/ic_music_vector.xml | 10 + .../ic_vector_play_circle_outline.xml | 10 + .../main/res/drawable/ic_videocam_vector.xml | 10 + app/src/main/res/layout/activity_thread.xml | 268 +++++++++--- app/src/main/res/layout/item_attachment.xml | 34 -- .../res/layout/item_attachment_document.xml | 53 +++ .../item_attachment_document_preview.xml | 30 ++ .../layout/item_attachment_media_preview.xml | 55 +++ .../main/res/layout/item_attachment_vcard.xml | 88 ++-- .../layout/item_attachment_vcard_preview.xml | 39 ++ .../layout/item_remove_attachment_button.xml | 22 + .../res/layout/item_unknown_attachment.xml | 51 --- app/src/main/res/values/dimens.xml | 7 +- 26 files changed, 1234 insertions(+), 459 deletions(-) create mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/AttachmentsAdapter.kt create mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/View.kt create mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/DocumentPreview.kt create mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/VCardPreview.kt create mode 100644 app/src/main/res/drawable/ic_image_vector.xml create mode 100644 app/src/main/res/drawable/ic_music_vector.xml create mode 100644 app/src/main/res/drawable/ic_vector_play_circle_outline.xml create mode 100644 app/src/main/res/drawable/ic_videocam_vector.xml delete mode 100644 app/src/main/res/layout/item_attachment.xml create mode 100644 app/src/main/res/layout/item_attachment_document.xml create mode 100644 app/src/main/res/layout/item_attachment_document_preview.xml create mode 100644 app/src/main/res/layout/item_attachment_media_preview.xml create mode 100644 app/src/main/res/layout/item_attachment_vcard_preview.xml create mode 100644 app/src/main/res/layout/item_remove_attachment_button.xml delete mode 100644 app/src/main/res/layout/item_unknown_attachment.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b669ba54..b12c0b35 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -93,17 +93,14 @@ - - - + - - + diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt index 9d7dde11..2f0263ec 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -5,7 +5,6 @@ import android.app.Activity import android.content.ActivityNotFoundException import android.content.Intent import android.graphics.BitmapFactory -import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable import android.media.MediaMetadataRetriever import android.net.Uri @@ -27,21 +26,19 @@ import android.util.TypedValue import android.view.Gravity import android.view.View import android.view.WindowManager +import android.view.animation.OvershootInterpolator import android.view.inputmethod.EditorInfo import android.widget.LinearLayout import android.widget.LinearLayout.LayoutParams 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 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 androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.children +import androidx.core.view.updateLayoutParams import com.google.gson.Gson import com.google.gson.reflect.TypeToken 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.views.MyRecyclerView import com.simplemobiletools.smsmessenger.R +import com.simplemobiletools.smsmessenger.adapters.AttachmentsAdapter import com.simplemobiletools.smsmessenger.adapters.AutoCompleteTextViewAdapter import com.simplemobiletools.smsmessenger.adapters.ThreadAdapter import com.simplemobiletools.smsmessenger.dialogs.ScheduleMessageDialog import com.simplemobiletools.smsmessenger.extensions.* import com.simplemobiletools.smsmessenger.helpers.* 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.item_attachment.view.* import kotlinx.android.synthetic.main.item_selected_contact.view.* import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe @@ -72,12 +72,6 @@ import java.io.OutputStream class ThreadActivity : SimpleActivity() { 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_SEND = 15 @@ -93,8 +87,6 @@ class ThreadActivity : SimpleActivity() { private var privateContacts = ArrayList() private var messages = ArrayList() private val availableSIMCards = ArrayList() - private var attachmentSelections = mutableMapOf() - private val imageCompressor by lazy { ImageCompressor(this) } private var lastAttachmentUri: String? = null private var capturedImageUri: Uri? = null private var loadingOlderMessages = false @@ -105,6 +97,8 @@ class ThreadActivity : SimpleActivity() { private var scheduledMessage: Message? = null private lateinit var scheduledDateTime: DateTime + private var isAttachmentPickerVisible = false + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_thread) @@ -145,6 +139,10 @@ class ThreadActivity : SimpleActivity() { finish() } } + + setupAttachmentPickerView() + setupKeyboardListener() + hideAttachmentPicker() } override fun onResume() { @@ -161,7 +159,7 @@ class ThreadActivity : SimpleActivity() { override fun onPause() { super.onPause() - if (thread_type_message.value != "" && attachmentSelections.isEmpty()) { + if (thread_type_message.value != "" && getAttachments().isEmpty()) { saveSmsDraft(thread_type_message.value, threadId) } else { deleteSmsDraft(threadId) @@ -172,6 +170,15 @@ class ThreadActivity : SimpleActivity() { isActivityVisible = false } + override fun onBackPressed() { + isAttachmentPickerVisible = false + if (attachment_picker_holder.isVisible()) { + hideAttachmentPicker() + } else { + super.onBackPressed() + } + } + override fun onDestroy() { super.onDestroy() bus?.unregister(this) @@ -214,13 +221,28 @@ class ThreadActivity : SimpleActivity() { override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { super.onActivityResult(requestCode, resultCode, resultData) if (resultCode != Activity.RESULT_OK) return + val data = resultData?.data - if (requestCode == TAKE_PHOTO_INTENT) { - addAttachment(capturedImageUri!!) - } else if (requestCode == PICK_ATTACHMENT_INTENT && resultData != null && resultData.data != null) { - addAttachment(resultData.data!!) - } else if (requestCode == PICK_SAVE_FILE_INTENT && resultData != null && resultData.data != null) { - saveAttachment(resultData) + when (requestCode) { + CAPTURE_PHOTO_INTENT -> addAttachment(capturedImageUri!!) + CAPTURE_VIDEO_INTENT -> if (data != null) { + addAttachment(data) + } + 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) + } } } @@ -487,7 +509,14 @@ class ThreadActivity : SimpleActivity() { thread_type_message.setText(intent.getStringExtra(THREAD_TEXT)) 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) { @@ -780,135 +809,134 @@ class ThreadActivity : SimpleActivity() { return items } - private fun takeOrPickPhotoVideo() { - val items = arrayListOf( - 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) + private fun launchActivityForResult(intent: Intent, requestCode: Int, @StringRes error: Int = R.string.no_app_found) { + hideKeyboard() try { - val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply { - putExtra(MediaStore.EXTRA_OUTPUT, capturedImageUri) - } - startActivityForResult(intent, TAKE_PHOTO_INTENT) + startActivityForResult(intent, requestCode) } catch (e: ActivityNotFoundException) { - showErrorToast(getString(R.string.no_app_found)) + showErrorToast(getString(error)) } catch (e: Exception) { 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() { - hideKeyboard() val mimeTypes = arrayOf("image/*", "video/*") Intent(Intent.ACTION_GET_CONTENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "*/*" putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes) - try { - startActivityForResult(this, PICK_ATTACHMENT_INTENT) - } catch (e: ActivityNotFoundException) { - showErrorToast(getString(R.string.no_app_found)) - } catch (e: Exception) { - showErrorToast(e) + launchActivityForResult(this, PICK_PHOTO_VIDEO_INTENT) + } + } + + private fun launchPickDocumentIntent() { + 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) { - val originalUriString = uri.toString() - if (attachmentSelections.containsKey(originalUriString)) { + if (getAttachments().any { it.uri.toString() == uri.toString() }) { return } - attachmentSelections[originalUriString] = AttachmentSelection(uri, false) - val attachmentView = addAttachmentView(originalUriString, uri) - val mimeType = contentResolver.getType(uri) ?: return - - if (mimeType.isImageMimeType() && config.mmsFileSizeLimit != FILE_SIZE_NONE) { - val selection = attachmentSelections[originalUriString] - attachmentSelections[originalUriString] = selection!!.copy(isPending = true) - checkSendMessageAvailability() - attachmentView.thread_attachment_progress.beVisible() - imageCompressor.compressImage(uri, config.mmsFileSizeLimit) { compressedUri -> - runOnUiThread { - if (compressedUri != null) { - attachmentSelections[originalUriString] = AttachmentSelection(compressedUri, false) - loadAttachmentPreview(attachmentView, compressedUri) - } else { - toast(R.string.compress_error) - removeAttachment(attachmentView, originalUriString) - } + var adapter = getAttachmentsAdapter() + if (adapter == null) { + adapter = AttachmentsAdapter( + activity = this, + onItemClick = {}, + onAttachmentsRemoved = { + thread_attachments_recyclerview.beGone() checkSendMessageAvailability() - attachmentView.thread_attachment_progress.beGone() - } - } - } - } - - private fun addAttachmentView(originalUri: String, uri: Uri): View { - thread_attachments_holder.beVisible() - val attachmentView = layoutInflater.inflate(R.layout.item_attachment, null).apply { - thread_attachments_wrapper.addView(this) - thread_remove_attachment.setOnClickListener { - removeAttachment(this, originalUri) - } + }, + onReady = { checkSendMessageAvailability() } + ) + thread_attachments_recyclerview.adapter = adapter } - loadAttachmentPreview(attachmentView, uri) - return attachmentView - } + thread_attachments_recyclerview.beVisible() + val mimeType = contentResolver.getType(uri).orEmpty() + val attachment = AttachmentSelection( + uri = uri, + mimetype = mimeType, + filename = getFilenameFromUri(uri), + isPending = mimeType.isImageMimeType() + ) + adapter.addAttachment(attachment) - 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 { - override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean { - attachmentView.thread_attachment_preview.beGone() - attachmentView.thread_remove_attachment.beGone() - return false - } - - override fun onResourceReady(dr: Drawable?, a: Any?, t: Target?, 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() } @@ -933,7 +961,7 @@ class ThreadActivity : SimpleActivity() { } 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.isClickable = true thread_send_message.alpha = 0.9f @@ -947,7 +975,7 @@ class ThreadActivity : SimpleActivity() { private fun sendMessage() { var text = thread_type_message.value - if (text.isEmpty() && attachmentSelections.isEmpty()) { + if (text.isEmpty() && getAttachments().isEmpty()) { showErrorToast(getString(R.string.unknown_error_occurred)) return } @@ -1002,8 +1030,7 @@ class ThreadActivity : SimpleActivity() { private fun sendNormalMessage(text: String, subscriptionId: Int) { val addresses = participants.getAddresses() - val attachments = attachmentSelections.values - .map { it.uri } + val attachments = getAttachments().map { it.uri } try { refreshedSinceSent = false @@ -1022,9 +1049,8 @@ class ThreadActivity : SimpleActivity() { private fun clearCurrentMessage() { thread_type_message.setText("") - attachmentSelections.clear() - thread_attachments_holder.beGone() - thread_attachments_wrapper.removeAllViews() + getAttachmentsAdapter()?.clear() + checkSendMessageAvailability() } // show selected contacts, properly split to new lines when appropriate @@ -1150,13 +1176,7 @@ class ThreadActivity : SimpleActivity() { addCategory(Intent.CATEGORY_OPENABLE) putExtra(Intent.EXTRA_TITLE, path.split("/").last()) - try { - startActivityForResult(this, PICK_SAVE_FILE_INTENT) - } catch (e: ActivityNotFoundException) { - showErrorToast(getString(R.string.system_service_disabled)) - } catch (e: Exception) { - showErrorToast(e) - } + launchActivityForResult(this, PICK_SAVE_FILE_INTENT, error = R.string.system_service_disabled) } } @@ -1204,7 +1224,7 @@ class ThreadActivity : SimpleActivity() { private fun isMmsMessage(text: String): Boolean { val isGroupMms = participants.size > 1 && config.sendGroupMessageMMS val isLongMmsMessage = isLongMmsMessage(text) && config.sendLongMessageMMS - return attachmentSelections.isNotEmpty() || isGroupMms || isLongMmsMessage + return getAttachments().isNotEmpty() || isGroupMms || isLongMmsMessage } private fun updateMessageType() { @@ -1217,15 +1237,6 @@ class ThreadActivity : SimpleActivity() { 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) { val items = arrayListOf( RadioItem(TYPE_EDIT, getString(R.string.update_message)), @@ -1282,7 +1293,7 @@ class ThreadActivity : SimpleActivity() { private fun setupScheduleSendUi() { val textColor = getProperTextColor() - scheduled_message_holder.background.applyColorFilter(getProperBackgroundColor().getContrastColor()) + scheduled_message_holder.background.applyColorFilter(getProperPrimaryColor().darkenColor()) scheduled_message_button.apply { val clockDrawable = ResourcesCompat.getDrawable(resources, R.drawable.ic_clock_vector, theme)?.apply { applyColorFilter(textColor) } setCompoundDrawablesWithIntrinsicBounds(clockDrawable, null, null, null) @@ -1358,10 +1369,94 @@ class ThreadActivity : SimpleActivity() { } 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, "") } .toArrayList() 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().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 { + 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 + } + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/AttachmentsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/AttachmentsAdapter.kt new file mode 100644 index 00000000..37baa75b --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/AttachmentsAdapter.kt @@ -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(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() + + 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 { + override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, 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?, 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() { + 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) + } + +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt index 06366948..9aa0f4ee 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt @@ -1,13 +1,11 @@ package com.simplemobiletools.smsmessenger.adapters import android.annotation.SuppressLint -import android.content.ActivityNotFoundException import android.content.Intent import android.graphics.Color import android.graphics.Typeface import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable -import android.net.Uri import android.util.TypedValue import android.view.Menu import android.view.View @@ -38,7 +36,6 @@ import com.simplemobiletools.smsmessenger.extensions.* import com.simplemobiletools.smsmessenger.helpers.* import com.simplemobiletools.smsmessenger.models.* 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.thread_mesage_attachments_holder 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_sending.view.* import kotlinx.android.synthetic.main.item_thread_success.view.* -import kotlinx.android.synthetic.main.item_unknown_attachment.view.* -import java.util.* class ThreadAdapter( activity: SimpleActivity, var messages: ArrayList, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit, val onThreadIdUpdate: (Long) -> Unit @@ -386,7 +381,7 @@ class ThreadAdapter( if (actModeCallback.isSelectable) { holder.viewClicked(message) } else { - launchViewIntent(uri, mimetype, attachment.filename) + activity.launchViewIntent(uri, mimetype, attachment.filename) } } imageView.setOnLongClickListener { @@ -400,50 +395,23 @@ class ThreadAdapter( val uri = attachment.getUri() parent.apply { val vCardView = layoutInflater.inflate(R.layout.item_attachment_vcard, null).apply { - background.applyColorFilter(backgroundColor.getContrastColor()) - vcard_title.setTextColor(textColor) - vcard_subtitle.setTextColor(textColor) - view_contact_details.setTextColor(properPrimaryColor) + setupVCardPreview( + activity = activity, + uri = uri, + onClick = { + if (actModeCallback.isSelectable) { + holder.viewClicked(message) + } else { + val intent = Intent(context, VCardViewerActivity::class.java).also { + it.putExtra(EXTRA_VCARD_URI, uri) + } + context.startActivity(intent) + } + }, + onLongClick = { holder.viewLongClicked() } + ) } 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) { - holder.viewClicked(message) - } else { - val intent = Intent(context, VCardViewerActivity::class.java).also { - it.putExtra(EXTRA_VCARD_URI, uri) - } - context.startActivity(intent) - } - } - setOnLongClickListener { - holder.viewLongClicked() - true - } - } - } - } } } @@ -451,66 +419,24 @@ class ThreadAdapter( val mimetype = attachment.mimetype val uri = attachment.getUri() parent.apply { - val attachmentView = layoutInflater.inflate(R.layout.item_unknown_attachment, null).apply { - if (attachment.filename.isNotEmpty()) { - filename.text = attachment.filename - } - - 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) { - holder.viewClicked(message) - } else { - launchViewIntent(uri, mimetype, attachment.filename) - } - } - setOnLongClickListener { - holder.viewLongClicked() - true - } + val attachmentView = layoutInflater.inflate(R.layout.item_attachment_document, null).apply { + setupDocumentPreview( + uri = uri, + title = attachment.filename, + onClick = { + if (actModeCallback.isSelectable) { + holder.viewClicked(message) + } else { + activity.launchViewIntent(uri, mimetype, attachment.filename) + } + }, + onLongClick = { holder.viewLongClicked() }, + ) } 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) { view.apply { thread_date_time.apply { diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Activity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Activity.kt index 1d165091..8bbd408c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Activity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Activity.kt @@ -4,10 +4,12 @@ import android.app.Activity import android.content.ActivityNotFoundException import android.content.Intent import android.net.Uri +import com.simplemobiletools.commons.extensions.getMimeType import com.simplemobiletools.commons.extensions.hideKeyboard import com.simplemobiletools.commons.extensions.showErrorToast import com.simplemobiletools.commons.extensions.toast import com.simplemobiletools.smsmessenger.R +import java.util.* fun Activity.dialNumber(phoneNumber: String, callback: (() -> Unit)? = null) { 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) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt index c285ec98..17cc4b90 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt @@ -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) messageAttachment.attachments.add(attachment) } else { + // todo: fix filename parsing, xml is shown some sometimes val text = cursor.getStringValue(Mms.Part.TEXT) val cutName = text.substringAfter("ref src=\"").substringBefore("\"") if (cutName.isNotEmpty()) { diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/View.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/View.kt new file mode 100644 index 00000000..47ff0dba --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/View.kt @@ -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() + } +} + diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Config.kt index 57c5ad8d..bec52c26 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Config.kt @@ -86,4 +86,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, 600) + set(value) = prefs.edit().putInt(SOFT_KEYBOARD_HEIGHT, value).apply() } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt index 15baf492..2e96f376 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt @@ -30,6 +30,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" @@ -45,6 +46,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 @@ -60,6 +66,15 @@ const val FILE_SIZE_2_MB = 2_097_152L 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() { EventBus.getDefault().post(Events.RefreshMessages()) } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/DocumentPreview.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/DocumentPreview.kt new file mode 100644 index 00000000..f737a823 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/DocumentPreview.kt @@ -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 + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/VCardPreview.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/VCardPreview.kt new file mode 100644 index 00000000..8cf7052c --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/VCardPreview.kt @@ -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 + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/AttachmentSelection.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/AttachmentSelection.kt index e56835d2..eb81fda2 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/AttachmentSelection.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/AttachmentSelection.kt @@ -1,8 +1,35 @@ package com.simplemobiletools.smsmessenger.models 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( 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 + } + } +} diff --git a/app/src/main/res/drawable/ic_image_vector.xml b/app/src/main/res/drawable/ic_image_vector.xml new file mode 100644 index 00000000..0a221c91 --- /dev/null +++ b/app/src/main/res/drawable/ic_image_vector.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_music_vector.xml b/app/src/main/res/drawable/ic_music_vector.xml new file mode 100644 index 00000000..1a7c4d5b --- /dev/null +++ b/app/src/main/res/drawable/ic_music_vector.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_vector_play_circle_outline.xml b/app/src/main/res/drawable/ic_vector_play_circle_outline.xml new file mode 100644 index 00000000..8a252301 --- /dev/null +++ b/app/src/main/res/drawable/ic_vector_play_circle_outline.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_videocam_vector.xml b/app/src/main/res/drawable/ic_videocam_vector.xml new file mode 100644 index 00000000..5ea7a412 --- /dev/null +++ b/app/src/main/res/drawable/ic_videocam_vector.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/activity_thread.xml b/app/src/main/res/layout/activity_thread.xml index 4e3baf2c..7bdec389 100644 --- a/app/src/main/res/layout/activity_thread.xml +++ b/app/src/main/res/layout/activity_thread.xml @@ -22,10 +22,10 @@ - @@ -34,6 +34,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone" + app:layout_constraintTop_toTopOf="parent" tools:visibility="visible"> + android:importantForAccessibility="no" + app:layout_constraintBottom_toTopOf="@id/scheduled_message_holder" + app:layout_constraintTop_toBottomOf="@id/thread_messages_fastscroller" + tools:layout_height="1dp" /> + android:src="@drawable/ic_plus_vector" + app:layout_constraintBottom_toTopOf="@id/attachment_picker_divider" + app:layout_constraintStart_toStartOf="parent" /> - - + - - - - + 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" /> + 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" /> + android:visibility="gone" + app:layout_constraintBottom_toTopOf="@id/attachment_picker_divider" + app:layout_constraintEnd_toStartOf="@id/thread_character_counter" + tools:visibility="visible" /> + 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" /> + 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" /> + android:textSize="@dimen/smaller_text_size" + app:layout_constraintBottom_toTopOf="@id/attachment_picker_divider" + app:layout_constraintEnd_toEndOf="parent" /> - + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_attachment.xml b/app/src/main/res/layout/item_attachment.xml deleted file mode 100644 index be794651..00000000 --- a/app/src/main/res/layout/item_attachment.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/layout/item_attachment_document.xml b/app/src/main/res/layout/item_attachment_document.xml new file mode 100644 index 00000000..603454b4 --- /dev/null +++ b/app/src/main/res/layout/item_attachment_document.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_attachment_document_preview.xml b/app/src/main/res/layout/item_attachment_document_preview.xml new file mode 100644 index 00000000..19f29d13 --- /dev/null +++ b/app/src/main/res/layout/item_attachment_document_preview.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/app/src/main/res/layout/item_attachment_media_preview.xml b/app/src/main/res/layout/item_attachment_media_preview.xml new file mode 100644 index 00000000..c5a4c8df --- /dev/null +++ b/app/src/main/res/layout/item_attachment_media_preview.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_attachment_vcard.xml b/app/src/main/res/layout/item_attachment_vcard.xml index 03ab668b..ff446f8f 100644 --- a/app/src/main/res/layout/item_attachment_vcard.xml +++ b/app/src/main/res/layout/item_attachment_vcard.xml @@ -1,57 +1,63 @@ - + app:layout_constraintTop_toTopOf="parent" /> - + android:layout_gravity="center_vertical" + android:layout_marginStart="@dimen/medium_margin" + android:orientation="vertical"> - + - + - + + + + diff --git a/app/src/main/res/layout/item_attachment_vcard_preview.xml b/app/src/main/res/layout/item_attachment_vcard_preview.xml new file mode 100644 index 00000000..ac469552 --- /dev/null +++ b/app/src/main/res/layout/item_attachment_vcard_preview.xml @@ -0,0 +1,39 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/item_remove_attachment_button.xml b/app/src/main/res/layout/item_remove_attachment_button.xml new file mode 100644 index 00000000..b516b97b --- /dev/null +++ b/app/src/main/res/layout/item_remove_attachment_button.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/app/src/main/res/layout/item_unknown_attachment.xml b/app/src/main/res/layout/item_unknown_attachment.xml deleted file mode 100644 index 8b884084..00000000 --- a/app/src/main/res/layout/item_unknown_attachment.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index e0959a28..0a03e197 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -3,9 +3,14 @@ 72dp 64dp 36dp - 60dp + 64dp + @dimen/attachment_preview_size + 156dp 24dp 15dp 64dp 20dp + 36dp + 80dp + 90dp