diff --git a/app/build.gradle b/app/build.gradle
index f208ea6e..7fd7b75b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -63,10 +63,10 @@ android {
}
dependencies {
- implementation 'com.github.SimpleMobileTools:Simple-Commons:0828fecd09'
+ implementation 'com.github.SimpleMobileTools:Simple-Commons:d5ae570e2a'
implementation 'org.greenrobot:eventbus:3.3.1'
implementation 'com.github.tibbi:IndicatorFastScroll:4524cd0b61'
- implementation 'com.github.tibbi:android-smsmms:875a46a9c4'
+ implementation 'com.github.tibbi:android-smsmms:3581774c39'
implementation "me.leolin:ShortcutBadger:1.1.22"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.googlecode.ez-vcard:ez-vcard:0.11.3'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b669ba54..e25bbf6b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -52,7 +52,8 @@
+ android:parentActivityName=".activities.MainActivity"
+ android:windowSoftInputMode="adjustResize" />
-
-
-
+
-
-
+
diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt
index 5b6641e2..22467a36 100644
--- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt
@@ -87,6 +87,7 @@ class MainActivity : SimpleActivity() {
override fun onResume() {
super.onResume()
setupToolbar(main_toolbar)
+
if (storedTextColor != getProperTextColor()) {
(conversations_list.adapter as? ConversationsAdapter)?.updateTextColor(getProperTextColor())
}
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 abe5a432..0e088d86 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,15 @@ 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.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.*
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
@@ -53,6 +46,7 @@ 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
@@ -60,8 +54,8 @@ import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.models.*
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.layout_attachment_picker.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@@ -72,12 +66,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 +81,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 +91,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 +133,10 @@ class ThreadActivity : SimpleActivity() {
finish()
}
}
+
+ setupAttachmentPickerView()
+ setupKeyboardListener()
+ hideAttachmentPicker()
}
override fun onResume() {
@@ -161,17 +153,25 @@ class ThreadActivity : SimpleActivity() {
override fun onPause() {
super.onPause()
- if (thread_type_message.value != "" && attachmentSelections.isEmpty()) {
+ if (thread_type_message.value != "" && getAttachmentSelections().isEmpty()) {
saveSmsDraft(thread_type_message.value, threadId)
} else {
deleteSmsDraft(threadId)
}
bus?.post(Events.RefreshMessages())
-
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 +214,16 @@ 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 && capturedImageUri != null) {
+ if (requestCode == CAPTURE_PHOTO_INTENT && capturedImageUri != null) {
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)
+ } else if (data != null) {
+ when (requestCode) {
+ CAPTURE_VIDEO_INTENT, PICK_DOCUMENT_INTENT, CAPTURE_AUDIO_INTENT, PICK_PHOTO_INTENT, PICK_VIDEO_INTENT -> addAttachment(data)
+ PICK_CONTACT_INTENT -> addContactAttachment(data)
+ PICK_SAVE_FILE_INTENT -> saveAttachment(resultData)
+ }
}
}
@@ -371,11 +374,13 @@ class ThreadActivity : SimpleActivity() {
}
}
- confirm_inserted_number?.setOnClickListener {
- val number = add_contact_or_number.value
- val phoneNumber = PhoneNumber(number, 0, "", number)
- val contact = SimpleContact(number.hashCode(), number.hashCode(), number, "", arrayListOf(phoneNumber), ArrayList(), ArrayList())
- addSelectedContact(contact)
+ runOnUiThread {
+ confirm_inserted_number?.setOnClickListener {
+ val number = add_contact_or_number.value
+ val phoneNumber = PhoneNumber(number, 0, "", number)
+ val contact = SimpleContact(number.hashCode(), number.hashCode(), number, "", arrayListOf(phoneNumber), ArrayList(), ArrayList())
+ addSelectedContact(contact)
+ }
}
}
@@ -427,6 +432,7 @@ class ThreadActivity : SimpleActivity() {
setTextColor(textColor)
compoundDrawables.forEach { it?.applyColorFilter(textColor) }
}
+
confirm_manage_contacts.applyColorFilter(textColor)
thread_add_attachment.applyColorFilter(textColor)
@@ -440,6 +446,7 @@ class ThreadActivity : SimpleActivity() {
thread_send_message.setOnClickListener {
sendMessage()
}
+
thread_send_message.setOnLongClickListener {
if (!isScheduledMessage) {
launchScheduleSendDialog()
@@ -478,7 +485,15 @@ class ThreadActivity : SimpleActivity() {
thread_type_message.setText(intent.getStringExtra(THREAD_TEXT))
thread_add_attachment.setOnClickListener {
- takeOrPickPhotoVideo()
+ if (attachment_picker_holder.isVisible()) {
+ isAttachmentPickerVisible = false
+ WindowCompat.getInsetsController(window, thread_type_message).show(WindowInsetsCompat.Type.ime())
+ } else {
+ isAttachmentPickerVisible = true
+ showOrHideAttachmentPicker()
+ WindowCompat.getInsetsController(window, thread_type_message).hide(WindowInsetsCompat.Type.ime())
+ }
+ window.decorView.requestApplyInsets()
}
if (intent.extras?.containsKey(THREAD_ATTACHMENT_URI) == true) {
@@ -775,135 +790,130 @@ 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 launchPickPhotoVideoIntent() {
- hideKeyboard()
- val mimeTypes = arrayOf("image/*", "video/*")
+ private fun getAttachmentsDir(): File {
+ return File(cacheDir, "attachments").apply {
+ if (!exists()) {
+ mkdirs()
+ }
+ }
+ }
+
+ private fun launchCapturePhotoIntent() {
+ val imageFile = File.createTempFile("attachment_", ".jpg", getAttachmentsDir())
+ 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 launchGetContentIntent(mimeTypes: Array, requestCode: Int) {
Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
+ launchActivityForResult(this, requestCode)
+ }
+ }
- try {
- startActivityForResult(this, PICK_ATTACHMENT_INTENT)
- } catch (e: ActivityNotFoundException) {
- showErrorToast(getString(R.string.no_app_found))
- } catch (e: Exception) {
- showErrorToast(e)
+ private fun launchPickContactIntent() {
+ Intent(Intent.ACTION_PICK).apply {
+ type = ContactsContract.Contacts.CONTENT_TYPE
+ launchActivityForResult(this, PICK_CONTACT_INTENT)
+ }
+ }
+
+ private fun addContactAttachment(contactUri: Uri) {
+ ensureBackgroundThread {
+ val contact = ContactsHelper(this).getContactFromUri(contactUri)
+ if (contact != null) {
+ val outputFile = File(getAttachmentsDir(), "${contact.contactId}.vcf")
+ val outputStream = outputFile.outputStream()
+
+ VcfExporter().exportContacts(
+ activity = this,
+ outputStream = outputStream,
+ contacts = arrayListOf(contact),
+ showExportingToast = false,
+ ) {
+ if (it == VcfExporter.ExportResult.EXPORT_OK) {
+ val vCardUri = getMyFileUri(outputFile)
+ runOnUiThread {
+ addAttachment(vCardUri)
+ }
+ } else {
+ toast(R.string.unknown_error_occurred)
+ }
+ }
+ } else {
+ toast(R.string.unknown_error_occurred)
}
}
}
+ private fun getAttachmentsAdapter(): AttachmentsAdapter? {
+ val adapter = thread_attachments_recyclerview.adapter
+ return adapter as? AttachmentsAdapter
+ }
+
+ private fun getAttachmentSelections() = getAttachmentsAdapter()?.attachments ?: emptyList()
+
private fun addAttachment(uri: Uri) {
- val originalUriString = uri.toString()
- if (attachmentSelections.containsKey(originalUriString)) {
+ val id = uri.toString()
+ if (getAttachmentSelections().any { it.id == id }) {
+ toast(R.string.duplicate_item_warning)
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,
+ recyclerView = thread_attachments_recyclerview,
+ 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
- }
-
- private fun loadAttachmentPreview(attachmentView: View, uri: Uri) {
- if (isDestroyed || isFinishing) {
+ thread_attachments_recyclerview.beVisible()
+ val mimeType = contentResolver.getType(uri)
+ if (mimeType == null) {
+ toast(R.string.unknown_error_occurred)
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()
- }
+ val attachment = AttachmentSelection(
+ id = id,
+ uri = uri,
+ mimetype = mimeType,
+ filename = getFilenameFromUri(uri),
+ isPending = mimeType.isImageMimeType() && !mimeType.isGifMimeType()
+ )
+ adapter.addAttachment(attachment)
checkSendMessageAvailability()
}
@@ -928,7 +938,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() || (getAttachmentSelections().isNotEmpty() && !getAttachmentSelections().any { it.isPending })) {
thread_send_message.isEnabled = true
thread_send_message.isClickable = true
thread_send_message.alpha = 0.9f
@@ -942,7 +952,7 @@ class ThreadActivity : SimpleActivity() {
private fun sendMessage() {
var text = thread_type_message.value
- if (text.isEmpty() && attachmentSelections.isEmpty()) {
+ if (text.isEmpty() && getAttachmentSelections().isEmpty()) {
showErrorToast(getString(R.string.unknown_error_occurred))
return
}
@@ -982,13 +992,16 @@ class ThreadActivity : SimpleActivity() {
conversationsDB.insertOrUpdate(conversation.copy(date = nowSeconds))
}
scheduleMessage(message)
- }
- clearCurrentMessage()
- hideScheduleSendUi()
- scheduledMessage = null
- if (!refreshedSinceSent) {
- refreshMessages()
+ runOnUiThread {
+ clearCurrentMessage()
+ hideScheduleSendUi()
+ scheduledMessage = null
+
+ if (!refreshedSinceSent) {
+ refreshMessages()
+ }
+ }
}
} catch (e: Exception) {
showErrorToast(e.localizedMessage ?: getString(R.string.unknown_error_occurred))
@@ -997,8 +1010,7 @@ class ThreadActivity : SimpleActivity() {
private fun sendNormalMessage(text: String, subscriptionId: Int) {
val addresses = participants.getAddresses()
- val attachments = attachmentSelections.values
- .map { it.uri }
+ val attachments = buildMessageAttachments()
try {
refreshedSinceSent = false
@@ -1017,9 +1029,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
@@ -1144,14 +1155,7 @@ class ThreadActivity : SimpleActivity() {
type = mimeType
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)
}
}
@@ -1199,7 +1203,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 getAttachmentSelections().isNotEmpty() || isGroupMms || isLongMmsMessage
}
private fun updateMessageType() {
@@ -1212,15 +1216,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)),
@@ -1277,7 +1272,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)
@@ -1344,7 +1339,7 @@ class ThreadActivity : SimpleActivity() {
read = false,
threadId = threadId,
isMMS = isMmsMessage(text),
- attachment = buildMessageAttachment(text, messageId),
+ attachment = MessageAttachment(messageId, text, buildMessageAttachments(messageId)),
senderName = "",
senderPhotoUri = "",
subscriptionId = subscriptionId,
@@ -1352,11 +1347,138 @@ class ThreadActivity : SimpleActivity() {
)
}
- private fun buildMessageAttachment(text: String, messageId: Long): MessageAttachment {
- val attachments = attachmentSelections.values
- .map { Attachment(null, messageId, it.uri.toString(), contentResolver.getType(it.uri) ?: "*/*", 0, 0, "") }
- .toArrayList()
+ private fun buildMessageAttachments(messageId: Long = -1L) = getAttachmentSelections()
+ .map { Attachment(null, messageId, it.uri.toString(), it.mimetype, 0, 0, it.filename) }
+ .toArrayList()
- return MessageAttachment(messageId, text, attachments)
+ private fun setupAttachmentPickerView() {
+ val buttonColors = arrayOf(
+ R.color.md_red_500,
+ R.color.md_brown_500,
+ R.color.md_pink_500,
+ R.color.md_purple_500,
+ R.color.md_teal_500,
+ R.color.md_green_500,
+ R.color.md_indigo_500,
+ R.color.md_blue_500
+ ).map { ResourcesCompat.getColor(resources, it, theme) }
+ arrayOf(
+ choose_photo_icon,
+ choose_video_icon,
+ take_photo_icon,
+ record_video_icon,
+ record_audio_icon,
+ pick_file_icon,
+ pick_contact_icon,
+ schedule_message_icon
+ ).forEachIndexed { index, icon ->
+ val iconColor = buttonColors[index]
+ icon.background.applyColorFilter(iconColor)
+ icon.applyColorFilter(iconColor.getContrastColor())
+ }
+
+ val textColor = getProperTextColor()
+ arrayOf(
+ choose_photo_text,
+ choose_video_text,
+ take_photo_text,
+ record_video_text,
+ record_audio_text,
+ pick_file_text,
+ pick_contact_text,
+ schedule_message_text
+ ).forEach { it.setTextColor(textColor) }
+
+ choose_photo.setOnClickListener {
+ launchGetContentIntent(arrayOf("image/*"), PICK_PHOTO_INTENT)
+ }
+ choose_video.setOnClickListener {
+ launchGetContentIntent(arrayOf("video/*"), PICK_VIDEO_INTENT)
+ }
+ take_photo.setOnClickListener {
+ launchCapturePhotoIntent()
+ }
+ record_video.setOnClickListener {
+ launchCaptureVideoIntent()
+ }
+ record_audio.setOnClickListener {
+ launchCaptureAudioIntent()
+ }
+ pick_file.setOnClickListener {
+ launchGetContentIntent(arrayOf("*/*"), PICK_DOCUMENT_INTENT)
+ }
+ 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() {
+ window.decorView.setOnApplyWindowInsetsListener { view, insets ->
+ showOrHideAttachmentPicker()
+ view.onApplyWindowInsets(insets)
+ }
+
+ val callback = object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
+ override fun onPrepare(animation: WindowInsetsAnimationCompat) {
+ super.onPrepare(animation)
+ showOrHideAttachmentPicker()
+ }
+
+ override fun onProgress(insets: WindowInsetsCompat, runningAnimations: MutableList) = insets
+ }
+ ViewCompat.setWindowInsetsAnimationCallback(window.decorView, callback)
+ }
+
+ private fun showOrHideAttachmentPicker() {
+ val type = WindowInsetsCompat.Type.ime()
+ val insets = ViewCompat.getRootWindowInsets(window.decorView) ?: return
+ val isKeyboardVisible = insets.isVisible(type)
+
+ if (isKeyboardVisible) {
+ val keyboardHeight = insets.getInsets(type).bottom
+ val bottomBarHeight = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom
+
+ // check keyboard height just to be sure, 150 seems like a good middle ground between ime and navigation bar
+ config.keyboardHeight = if (keyboardHeight > 150) {
+ keyboardHeight - bottomBarHeight
+ } else {
+ getDefaultKeyboardHeight()
+ }
+ hideAttachmentPicker()
+ } else if (isAttachmentPickerVisible) {
+ showAttachmentPicker()
+ }
}
}
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..adad4bb1
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/AttachmentsAdapter.kt
@@ -0,0 +1,207 @@
+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.R
+import com.simplemobiletools.smsmessenger.activities.VCardViewerActivity
+import com.simplemobiletools.smsmessenger.extensions.*
+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 recyclerView: RecyclerView,
+ 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()
+
+ override fun getItemViewType(position: Int): Int {
+ return getItem(position).viewType
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val layoutRes = when (viewType) {
+ ATTACHMENT_DOCUMENT -> R.layout.item_attachment_document_preview
+ ATTACHMENT_VCARD -> R.layout.item_attachment_vcard_preview
+ ATTACHMENT_MEDIA -> 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() { view, _ ->
+ when (attachment.viewType) {
+ ATTACHMENT_DOCUMENT -> {
+ view.setupDocumentPreview(
+ uri = attachment.uri,
+ title = attachment.filename,
+ mimeType = attachment.mimetype,
+ 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)
+ }
+ }
+ }
+
+ fun clear() {
+ attachments.clear()
+ submitList(emptyList())
+ recyclerView.onGlobalLayout {
+ 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()) {
+ clear()
+ } else {
+ submitList(attachments.toList())
+ }
+ }
+
+ 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)
+ }
+ }
+
+ val compressImage = attachment.mimetype.isImageMimeType() && !attachment.mimetype.isGifMimeType()
+ if (compressImage && 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(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(R.dimen.activity_margin).toInt()
+ val size = resources.getDimension(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(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(callback: (itemView: View, adapterPosition: Int) -> Unit): View {
+ return itemView.apply {
+ callback(this, adapterPosition)
+ }
+ }
+ }
+}
+
+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 066cbf13..63a484d3 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.Size
import android.util.TypedValue
import android.view.Menu
@@ -39,20 +37,16 @@ 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
import kotlinx.android.synthetic.main.item_received_message.view.thread_message_holder
import kotlinx.android.synthetic.main.item_received_message.view.thread_message_play_outline
-import kotlinx.android.synthetic.main.item_received_unknown_attachment.view.*
import kotlinx.android.synthetic.main.item_sent_message.view.*
-import kotlinx.android.synthetic.main.item_sent_unknown_attachment.view.*
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 java.util.*
class ThreadAdapter(
activity: SimpleActivity, var messages: ArrayList, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit, val onThreadIdUpdate: (Long) -> Unit
@@ -288,12 +282,10 @@ class ThreadAdapter(
if (message.attachment?.attachments?.isNotEmpty() == true) {
for (attachment in message.attachment.attachments) {
val mimetype = attachment.mimetype
- if (mimetype.isImageMimeType() || mimetype.startsWith("video/")) {
- setupImageView(holder, view, message, attachment)
- } else if (mimetype.isVCardMimeType()) {
- setupVCardView(holder, view, message, attachment)
- } else {
- setupFileView(holder, view, message, attachment)
+ when {
+ mimetype.isImageMimeType() || mimetype.isVideoMimeType() -> setupImageView(holder, view, message, attachment)
+ mimetype.isVCardMimeType() -> setupVCardView(holder, view, message, attachment)
+ else -> setupFileView(holder, view, message, attachment)
}
thread_message_play_outline.beVisibleIf(mimetype.startsWith("video/"))
@@ -401,7 +393,7 @@ class ThreadAdapter(
if (actModeCallback.isSelectable) {
holder.viewClicked(message)
} else {
- launchViewIntent(uri, mimetype, attachment.filename)
+ activity.launchViewIntent(uri, mimetype, attachment.filename)
}
}
imageView.setOnLongClickListener {
@@ -415,50 +407,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
- }
- }
- }
- }
}
}
@@ -466,73 +431,22 @@ class ThreadAdapter(
val mimetype = attachment.mimetype
val uri = attachment.getUri()
parent.apply {
- if (message.isReceivedMessage()) {
- val attachmentView = layoutInflater.inflate(R.layout.item_received_unknown_attachment, null).apply {
- thread_received_attachment_label.apply {
- if (attachment.filename.isNotEmpty()) {
- thread_received_attachment_label.text = attachment.filename
+ val attachmentView = layoutInflater.inflate(R.layout.item_attachment_document, null).apply {
+ setupDocumentPreview(
+ uri = uri,
+ title = attachment.filename,
+ mimeType = attachment.mimetype,
+ onClick = {
+ if (actModeCallback.isSelectable) {
+ holder.viewClicked(message)
+ } else {
+ activity.launchViewIntent(uri, mimetype, attachment.filename)
}
- setTextColor(textColor)
- setOnClickListener {
- if (actModeCallback.isSelectable) {
- holder.viewClicked(message)
- } else {
- launchViewIntent(uri, mimetype, attachment.filename)
- }
- }
- setOnLongClickListener {
- holder.viewLongClicked()
- true
- }
- }
- }
- thread_mesage_attachments_holder.addView(attachmentView)
- } else {
- val background = context.getProperPrimaryColor()
- val attachmentView = layoutInflater.inflate(R.layout.item_sent_unknown_attachment, null).apply {
- thread_sent_attachment_label.apply {
- this.background.applyColorFilter(background)
- setTextColor(background.getContrastColor())
- if (attachment.filename.isNotEmpty()) {
- thread_sent_attachment_label.text = attachment.filename
- }
- setOnClickListener {
- if (actModeCallback.isSelectable) {
- holder.viewClicked(message)
- } else {
- launchViewIntent(uri, mimetype, attachment.filename)
- }
- }
- setOnLongClickListener {
- holder.viewLongClicked()
- true
- }
- }
- }
- 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)
+ },
+ onLongClick = { holder.viewLongClicked() },
+ )
}
+ thread_mesage_attachments_holder.addView(attachmentView)
}
}
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..6b97672f 100644
--- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt
@@ -35,6 +35,7 @@ import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.ThreadActivity
import com.simplemobiletools.smsmessenger.databases.MessagesDatabase
import com.simplemobiletools.smsmessenger.helpers.*
+import com.simplemobiletools.smsmessenger.helpers.AttachmentUtils.parseAttachmentNames
import com.simplemobiletools.smsmessenger.interfaces.AttachmentsDao
import com.simplemobiletools.smsmessenger.interfaces.ConversationsDao
import com.simplemobiletools.smsmessenger.interfaces.MessageAttachmentsDao
@@ -307,7 +308,8 @@ fun Context.getMmsAttachment(id: Long, getImageResolutions: Boolean): MessageAtt
val selectionArgs = arrayOf(id.toString())
val messageAttachment = MessageAttachment(id, "", arrayListOf())
- var attachmentName = ""
+ var attachmentNames: List? = null
+ var attachmentCount = 0
queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val partId = cursor.getLongValue(Mms._ID)
val mimetype = cursor.getStringValue(Mms.Part.CONTENT_TYPE)
@@ -332,14 +334,13 @@ fun Context.getMmsAttachment(id: Long, getImageResolutions: Boolean): MessageAtt
val attachment = Attachment(partId, id, fileUri.toString(), mimetype, width, height, "")
messageAttachment.attachments.add(attachment)
} else if (mimetype != "application/smil") {
+ val attachmentName = attachmentNames?.getOrNull(attachmentCount) ?: ""
val attachment = Attachment(partId, id, Uri.withAppendedPath(uri, partId.toString()).toString(), mimetype, 0, 0, attachmentName)
messageAttachment.attachments.add(attachment)
+ attachmentCount++
} else {
val text = cursor.getStringValue(Mms.Part.TEXT)
- val cutName = text.substringAfter("ref src=\"").substringBefore("\"")
- if (cutName.isNotEmpty()) {
- attachmentName = cutName
- }
+ attachmentNames = parseAttachmentNames(text)
}
}
@@ -1064,3 +1065,5 @@ fun Context.clearExpiredScheduledMessages(threadId: Long, messagesToDelete: List
return
}
}
+
+fun Context.getDefaultKeyboardHeight() = resources.getDimensionPixelSize(R.dimen.default_keyboard_height)
diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/String.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/String.kt
index 89fb0f03..8ad4bd49 100644
--- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/String.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/String.kt
@@ -15,7 +15,35 @@ fun String.isImageMimeType(): Boolean {
return lowercase().startsWith("image")
}
+fun String.isGifMimeType(): Boolean {
+ return lowercase().endsWith("gif")
+}
+
+fun String.isVideoMimeType(): Boolean {
+ return lowercase().startsWith("video")
+}
+
fun String.isVCardMimeType(): Boolean {
val lowercase = lowercase()
return lowercase.endsWith("x-vcard") || lowercase.endsWith("vcard")
}
+
+fun String.isAudioMimeType(): Boolean {
+ return lowercase().startsWith("audio")
+}
+
+fun String.isCalendarMimeType(): Boolean {
+ return lowercase().endsWith("calendar")
+}
+
+fun String.isPdfMimeType(): Boolean {
+ return lowercase().endsWith("pdf")
+}
+
+fun String.isZipMimeType(): Boolean {
+ return lowercase().endsWith("zip")
+}
+
+fun String.isPlainTextMimeType(): Boolean {
+ return lowercase() == "text/plain"
+}
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/AttachmentPreviews.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/AttachmentPreviews.kt
new file mode 100644
index 00000000..82d7eeb8
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/AttachmentPreviews.kt
@@ -0,0 +1,152 @@
+package com.simplemobiletools.smsmessenger.helpers
+
+import android.app.Activity
+import android.net.Uri
+import android.view.View
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.SimpleContactsHelper
+import com.simplemobiletools.smsmessenger.R
+import com.simplemobiletools.smsmessenger.extensions.*
+import kotlinx.android.synthetic.main.item_attachment_document.view.*
+import kotlinx.android.synthetic.main.item_attachment_vcard.view.*
+import kotlinx.android.synthetic.main.item_attachment_vcard_preview.view.*
+import kotlinx.android.synthetic.main.item_remove_attachment_button.view.*
+
+fun View.setupDocumentPreview(
+ uri: Uri,
+ title: String,
+ mimeType: String,
+ attachment: Boolean = false,
+ onClick: (() -> Unit)? = null,
+ onLongClick: (() -> Unit)? = null,
+ onRemoveButtonClicked: (() -> Unit)? = null
+) {
+ if (title.isNotEmpty()) {
+ filename.text = title
+ }
+
+ try {
+ val size = context.getFileSizeFromUri(uri)
+ file_size.beVisible()
+ file_size.text = size.formatSize()
+ } catch (e: Exception) {
+ file_size.beGone()
+ }
+
+ val textColor = context.getProperTextColor()
+ val primaryColor = context.getProperPrimaryColor()
+
+ document_attachment_holder.background.applyColorFilter(textColor)
+ filename.setTextColor(textColor)
+ file_size.setTextColor(textColor)
+
+ icon.setImageResource(getIconResourceForMimeType(mimeType))
+ icon.background.setTint(primaryColor)
+ document_attachment_holder.background.applyColorFilter(primaryColor.darkenColor())
+
+ if (attachment) {
+ remove_attachment_button.apply {
+ beVisible()
+ background.applyColorFilter(primaryColor)
+ if (onRemoveButtonClicked != null) {
+ setOnClickListener {
+ onRemoveButtonClicked.invoke()
+ }
+ }
+ }
+ }
+
+ document_attachment_holder.setOnClickListener {
+ onClick?.invoke()
+ }
+ document_attachment_holder.setOnLongClickListener {
+ onLongClick?.invoke()
+ true
+ }
+}
+
+fun View.setupVCardPreview(
+ activity: Activity,
+ uri: Uri,
+ attachment: Boolean = false,
+ onClick: (() -> Unit)? = null,
+ onLongClick: (() -> Unit)? = null,
+ onRemoveButtonClicked: (() -> Unit)? = null,
+) {
+ val textColor = activity.getProperTextColor()
+ val primaryColor = activity.getProperPrimaryColor()
+
+ vcard_attachment_holder.background.applyColorFilter(primaryColor.darkenColor())
+ vcard_title.setTextColor(textColor)
+ vcard_subtitle.setTextColor(textColor)
+
+ if (attachment) {
+ vcard_progress.beVisible()
+ }
+ arrayOf(vcard_photo, vcard_title, vcard_subtitle, view_contact_details).forEach {
+ it.beGone()
+ }
+
+ parseVCardFromUri(activity, uri) { vCards ->
+ activity.runOnUiThread {
+ if (vCards.isEmpty()) {
+ vcard_title.beVisible()
+ vcard_title.text = context.getString(R.string.unknown_error_occurred)
+ return@runOnUiThread
+ }
+ val title = vCards.firstOrNull()?.parseNameFromVCard()
+ val imageIcon = if (title != null) {
+ SimpleContactsHelper(activity).getContactLetterIcon(title)
+ } else {
+ null
+ }
+
+ arrayOf(vcard_photo, vcard_title).forEach {
+ it.beVisible()
+ }
+
+ vcard_photo.setImageBitmap(imageIcon)
+ vcard_title.text = title
+
+ if (vCards.size > 1) {
+ vcard_subtitle.beVisible()
+ val quantity = vCards.size - 1
+ vcard_subtitle.text = resources.getQuantityString(R.plurals.and_other_contacts, quantity, quantity)
+ } else {
+ vcard_subtitle.beGone()
+ }
+
+ if (attachment) {
+ vcard_progress.beGone()
+ remove_attachment_button.apply {
+ beVisible()
+ background.applyColorFilter(primaryColor)
+ if (onRemoveButtonClicked != null) {
+ setOnClickListener {
+ onRemoveButtonClicked.invoke()
+ }
+ }
+ }
+ } else {
+ view_contact_details.setTextColor(primaryColor)
+ view_contact_details.beVisible()
+ }
+
+ vcard_attachment_holder.setOnClickListener {
+ onClick?.invoke()
+ }
+ vcard_attachment_holder.setOnLongClickListener {
+ onLongClick?.invoke()
+ true
+ }
+ }
+ }
+}
+
+private fun getIconResourceForMimeType(mimeType: String) = when {
+ mimeType.isAudioMimeType() -> R.drawable.ic_vector_audio_file
+ mimeType.isCalendarMimeType() -> R.drawable.ic_calendar_month_vector
+ mimeType.isPdfMimeType() -> R.drawable.ic_vector_pdf
+ mimeType.isZipMimeType() -> R.drawable.ic_vector_folder_zip
+ else -> R.drawable.ic_document_vector
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/AttachmentUtils.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/AttachmentUtils.kt
new file mode 100644
index 00000000..1b062c86
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/AttachmentUtils.kt
@@ -0,0 +1,74 @@
+package com.simplemobiletools.smsmessenger.helpers
+
+import android.util.Xml
+import org.xmlpull.v1.XmlPullParser
+
+object AttachmentUtils {
+
+ fun parseAttachmentNames(text: String): List {
+ val parser = Xml.newPullParser()
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
+ parser.setInput(text.reader())
+ parser.nextTag()
+ return readSmil(parser)
+ }
+
+ private fun readSmil(parser: XmlPullParser): List {
+ parser.require(XmlPullParser.START_TAG, null, "smil")
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.eventType != XmlPullParser.START_TAG) {
+ continue
+ }
+
+ if (parser.name == "body") {
+ return readBody(parser)
+ } else {
+ skip(parser)
+ }
+ }
+
+ return emptyList()
+ }
+
+ private fun readBody(parser: XmlPullParser): List {
+ val names = mutableListOf()
+ parser.require(XmlPullParser.START_TAG, null, "body")
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.eventType != XmlPullParser.START_TAG) {
+ continue
+ }
+
+ if (parser.name == "par") {
+ parser.require(XmlPullParser.START_TAG, null, "par")
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.eventType != XmlPullParser.START_TAG) {
+ continue
+ }
+
+ if (parser.name == "ref") {
+ val value = parser.getAttributeValue(null, "src")
+ names.add(value)
+ parser.nextTag()
+ }
+ }
+ } else {
+ skip(parser)
+ }
+ }
+ return names
+ }
+
+ private fun skip(parser: XmlPullParser) {
+ if (parser.eventType != XmlPullParser.START_TAG) {
+ throw IllegalStateException()
+ }
+
+ var depth = 1
+ while (depth != 0) {
+ when (parser.next()) {
+ XmlPullParser.END_TAG -> depth--
+ XmlPullParser.START_TAG -> depth++
+ }
+ }
+ }
+}
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..c1fa3d74 100644
--- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Config.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Config.kt
@@ -2,6 +2,7 @@ package com.simplemobiletools.smsmessenger.helpers
import android.content.Context
import com.simplemobiletools.commons.helpers.BaseConfig
+import com.simplemobiletools.smsmessenger.extensions.getDefaultKeyboardHeight
import com.simplemobiletools.smsmessenger.models.Conversation
class Config(context: Context) : BaseConfig(context) {
@@ -86,4 +87,8 @@ class Config(context: Context) : BaseConfig(context) {
var wasDbCleared: Boolean
get() = prefs.getBoolean(WAS_DB_CLEARED, false)
set(wasDbCleared) = prefs.edit().putBoolean(WAS_DB_CLEARED, wasDbCleared).apply()
+
+ var keyboardHeight: Int
+ get() = prefs.getInt(SOFT_KEYBOARD_HEIGHT, context.getDefaultKeyboardHeight())
+ set(keyboardHeight) = prefs.edit().putInt(SOFT_KEYBOARD_HEIGHT, keyboardHeight).apply()
}
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..bf8f2761 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,16 @@ const val FILE_SIZE_2_MB = 2_097_152L
const val MESSAGES_LIMIT = 50
+// intent launch request codes
+const val PICK_PHOTO_INTENT = 42
+const val PICK_VIDEO_INTENT = 49
+const val PICK_SAVE_FILE_INTENT = 43
+const val CAPTURE_PHOTO_INTENT = 44
+const val CAPTURE_VIDEO_INTENT = 45
+const val CAPTURE_AUDIO_INTENT = 46
+const val PICK_DOCUMENT_INTENT = 47
+const val PICK_CONTACT_INTENT = 48
+
fun refreshMessages() {
EventBus.getDefault().post(Events.RefreshMessages())
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/ContactsHelper.kt
new file mode 100644
index 00000000..0bfc46db
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/ContactsHelper.kt
@@ -0,0 +1,449 @@
+package com.simplemobiletools.smsmessenger.helpers
+
+import android.content.Context
+import android.net.Uri
+import android.provider.ContactsContract
+import android.provider.ContactsContract.CommonDataKinds.*
+import android.provider.ContactsContract.Data
+import android.text.TextUtils
+import android.util.SparseArray
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.models.PhoneNumber
+import com.simplemobiletools.commons.models.contacts.*
+import com.simplemobiletools.commons.models.contacts.Email
+import com.simplemobiletools.commons.models.contacts.Event
+import com.simplemobiletools.commons.models.contacts.Organization
+import com.simplemobiletools.commons.overloads.times
+
+// based on the ContactsHelper from Simple-Contacts
+class ContactsHelper(val context: Context) {
+ private var displayContactSources = ArrayList()
+
+ fun getContactFromUri(uri: Uri): Contact? {
+ val key = getLookupKeyFromUri(uri) ?: return null
+ return getContactWithLookupKey(key)
+ }
+
+ private fun getLookupKeyFromUri(lookupUri: Uri): String? {
+ val projection = arrayOf(ContactsContract.Contacts.LOOKUP_KEY)
+ val cursor = context.contentResolver.query(lookupUri, projection, null, null, null)
+ cursor?.use {
+ if (cursor.moveToFirst()) {
+ return cursor.getStringValue(ContactsContract.Contacts.LOOKUP_KEY)
+ }
+ }
+ return null
+ }
+
+ private fun getContactWithLookupKey(key: String): Contact? {
+ val selection = "(${Data.MIMETYPE} = ? OR ${Data.MIMETYPE} = ?) AND ${Data.LOOKUP_KEY} = ?"
+ val selectionArgs = arrayOf(StructuredName.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE, key)
+ return parseContactCursor(selection, selectionArgs)
+ }
+
+ private fun parseContactCursor(selection: String, selectionArgs: Array): Contact? {
+ val storedGroups = getDeviceStoredGroups()
+ val uri = Data.CONTENT_URI
+ val projection = getContactProjection()
+
+ val cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
+ cursor?.use {
+ if (cursor.moveToFirst()) {
+ val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
+
+ var prefix = ""
+ var firstName = ""
+ var middleName = ""
+ var surname = ""
+ var suffix = ""
+ val mimetype = cursor.getStringValue(Data.MIMETYPE)
+
+ // ignore names at Organization type contacts
+ if (mimetype == StructuredName.CONTENT_ITEM_TYPE) {
+ prefix = cursor.getStringValue(StructuredName.PREFIX) ?: ""
+ firstName = cursor.getStringValue(StructuredName.GIVEN_NAME) ?: ""
+ middleName = cursor.getStringValue(StructuredName.MIDDLE_NAME) ?: ""
+ surname = cursor.getStringValue(StructuredName.FAMILY_NAME) ?: ""
+ suffix = cursor.getStringValue(StructuredName.SUFFIX) ?: ""
+ }
+
+ val nickname = getNicknames(id)[id] ?: ""
+ val photoUri = cursor.getStringValue(Phone.PHOTO_URI) ?: ""
+ val number = getPhoneNumbers(id)[id] ?: ArrayList()
+ val emails = getEmails(id)[id] ?: ArrayList()
+ val addresses = getAddresses(id)[id] ?: ArrayList()
+ val events = getEvents(id)[id] ?: ArrayList()
+ val notes = getNotes(id)[id] ?: ""
+ val accountName = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_NAME) ?: ""
+ val starred = cursor.getIntValue(StructuredName.STARRED)
+ val ringtone = cursor.getStringValue(StructuredName.CUSTOM_RINGTONE)
+ val contactId = cursor.getIntValue(Data.CONTACT_ID)
+ val groups = getContactGroups(storedGroups, contactId)[contactId] ?: ArrayList()
+ val thumbnailUri = cursor.getStringValue(StructuredName.PHOTO_THUMBNAIL_URI) ?: ""
+ val organization = getOrganizations(id)[id] ?: Organization("", "")
+ val websites = getWebsites(id)[id] ?: ArrayList()
+ val ims = getIMs(id)[id] ?: ArrayList()
+ return Contact(
+ id, prefix, firstName, middleName, surname, suffix, nickname, photoUri, number, emails, addresses, events,
+ accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites, ims, mimetype, ringtone
+ )
+ }
+ }
+
+ return null
+ }
+
+ private fun getPhoneNumbers(contactId: Int? = null): SparseArray> {
+ val phoneNumbers = SparseArray>()
+ val uri = Phone.CONTENT_URI
+ val projection = arrayOf(
+ Data.RAW_CONTACT_ID,
+ Phone.NUMBER,
+ Phone.NORMALIZED_NUMBER,
+ Phone.TYPE,
+ Phone.LABEL,
+ Phone.IS_PRIMARY
+ )
+
+ val selection = if (contactId == null) getSourcesSelection() else "${Data.RAW_CONTACT_ID} = ?"
+ val selectionArgs = if (contactId == null) getSourcesSelectionArgs() else arrayOf(contactId.toString())
+
+ context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
+ val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
+ val number = cursor.getStringValue(Phone.NUMBER) ?: return@queryCursor
+ val normalizedNumber = cursor.getStringValue(Phone.NORMALIZED_NUMBER) ?: number.normalizePhoneNumber()
+ val type = cursor.getIntValue(Phone.TYPE)
+ val label = cursor.getStringValue(Phone.LABEL) ?: ""
+ val isPrimary = cursor.getIntValue(Phone.IS_PRIMARY) != 0
+
+ if (phoneNumbers[id] == null) {
+ phoneNumbers.put(id, ArrayList())
+ }
+
+ val phoneNumber = PhoneNumber(number, type, label, normalizedNumber, isPrimary)
+ phoneNumbers[id].add(phoneNumber)
+ }
+
+ return phoneNumbers
+ }
+
+ private fun getNicknames(contactId: Int? = null): SparseArray {
+ val nicknames = SparseArray()
+ val uri = Data.CONTENT_URI
+ val projection = arrayOf(
+ Data.RAW_CONTACT_ID,
+ Nickname.NAME
+ )
+
+ val selection = getSourcesSelection(true, contactId != null)
+ val selectionArgs = getSourcesSelectionArgs(Nickname.CONTENT_ITEM_TYPE, contactId)
+
+ context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
+ val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
+ val nickname = cursor.getStringValue(Nickname.NAME) ?: return@queryCursor
+ nicknames.put(id, nickname)
+ }
+
+ return nicknames
+ }
+
+ private fun getEmails(contactId: Int? = null): SparseArray> {
+ val emails = SparseArray>()
+ val uri = ContactsContract.CommonDataKinds.Email.CONTENT_URI
+ val projection = arrayOf(
+ Data.RAW_CONTACT_ID,
+ ContactsContract.CommonDataKinds.Email.DATA,
+ ContactsContract.CommonDataKinds.Email.TYPE,
+ ContactsContract.CommonDataKinds.Email.LABEL
+ )
+
+ val selection = if (contactId == null) getSourcesSelection() else "${Data.RAW_CONTACT_ID} = ?"
+ val selectionArgs = if (contactId == null) getSourcesSelectionArgs() else arrayOf(contactId.toString())
+
+ context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
+ val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
+ val email = cursor.getStringValue(ContactsContract.CommonDataKinds.Email.DATA) ?: return@queryCursor
+ val type = cursor.getIntValue(ContactsContract.CommonDataKinds.Email.TYPE)
+ val label = cursor.getStringValue(ContactsContract.CommonDataKinds.Email.LABEL) ?: ""
+
+ if (emails[id] == null) {
+ emails.put(id, ArrayList())
+ }
+
+ emails[id]!!.add(Email(email, type, label))
+ }
+
+ return emails
+ }
+
+ private fun getAddresses(contactId: Int? = null): SparseArray> {
+ val addresses = SparseArray>()
+ val uri = StructuredPostal.CONTENT_URI
+ val projection = arrayOf(
+ Data.RAW_CONTACT_ID,
+ StructuredPostal.FORMATTED_ADDRESS,
+ StructuredPostal.TYPE,
+ StructuredPostal.LABEL
+ )
+
+ val selection = if (contactId == null) getSourcesSelection() else "${Data.RAW_CONTACT_ID} = ?"
+ val selectionArgs = if (contactId == null) getSourcesSelectionArgs() else arrayOf(contactId.toString())
+
+ context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
+ val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
+ val address = cursor.getStringValue(StructuredPostal.FORMATTED_ADDRESS) ?: return@queryCursor
+ val type = cursor.getIntValue(StructuredPostal.TYPE)
+ val label = cursor.getStringValue(StructuredPostal.LABEL) ?: ""
+
+ if (addresses[id] == null) {
+ addresses.put(id, ArrayList())
+ }
+
+ addresses[id]!!.add(Address(address, type, label))
+ }
+
+ return addresses
+ }
+
+ private fun getIMs(contactId: Int? = null): SparseArray> {
+ val IMs = SparseArray>()
+ val uri = Data.CONTENT_URI
+ val projection = arrayOf(
+ Data.RAW_CONTACT_ID,
+ Im.DATA,
+ Im.PROTOCOL,
+ Im.CUSTOM_PROTOCOL
+ )
+
+ val selection = getSourcesSelection(true, contactId != null)
+ val selectionArgs = getSourcesSelectionArgs(Im.CONTENT_ITEM_TYPE, contactId)
+
+ context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
+ val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
+ val IM = cursor.getStringValue(Im.DATA) ?: return@queryCursor
+ val type = cursor.getIntValue(Im.PROTOCOL)
+ val label = cursor.getStringValue(Im.CUSTOM_PROTOCOL) ?: ""
+
+ if (IMs[id] == null) {
+ IMs.put(id, ArrayList())
+ }
+
+ IMs[id]!!.add(IM(IM, type, label))
+ }
+
+ return IMs
+ }
+
+ private fun getEvents(contactId: Int? = null): SparseArray> {
+ val events = SparseArray>()
+ val uri = Data.CONTENT_URI
+ val projection = arrayOf(
+ Data.RAW_CONTACT_ID,
+ ContactsContract.CommonDataKinds.Event.START_DATE,
+ ContactsContract.CommonDataKinds.Event.TYPE
+ )
+
+ val selection = getSourcesSelection(true, contactId != null)
+ val selectionArgs = getSourcesSelectionArgs(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE, contactId)
+
+ context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
+ val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
+ val startDate = cursor.getStringValue(ContactsContract.CommonDataKinds.Event.START_DATE) ?: return@queryCursor
+ val type = cursor.getIntValue(ContactsContract.CommonDataKinds.Event.TYPE)
+
+ if (events[id] == null) {
+ events.put(id, ArrayList())
+ }
+
+ events[id]!!.add(Event(startDate, type))
+ }
+
+ return events
+ }
+
+ private fun getNotes(contactId: Int? = null): SparseArray {
+ val notes = SparseArray()
+ val uri = Data.CONTENT_URI
+ val projection = arrayOf(
+ Data.RAW_CONTACT_ID,
+ Note.NOTE
+ )
+
+ val selection = getSourcesSelection(true, contactId != null)
+ val selectionArgs = getSourcesSelectionArgs(Note.CONTENT_ITEM_TYPE, contactId)
+
+ context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
+ val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
+ val note = cursor.getStringValue(Note.NOTE) ?: return@queryCursor
+ notes.put(id, note)
+ }
+
+ return notes
+ }
+
+ private fun getOrganizations(contactId: Int? = null): SparseArray {
+ val organizations = SparseArray()
+ val uri = Data.CONTENT_URI
+ val projection = arrayOf(
+ Data.RAW_CONTACT_ID,
+ ContactsContract.CommonDataKinds.Organization.COMPANY,
+ ContactsContract.CommonDataKinds.Organization.TITLE
+ )
+
+ val selection = getSourcesSelection(true, contactId != null)
+ val selectionArgs = getSourcesSelectionArgs(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE, contactId)
+
+ context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
+ val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
+ val company = cursor.getStringValue(ContactsContract.CommonDataKinds.Organization.COMPANY) ?: ""
+ val title = cursor.getStringValue(ContactsContract.CommonDataKinds.Organization.TITLE) ?: ""
+ if (company.isEmpty() && title.isEmpty()) {
+ return@queryCursor
+ }
+
+ val organization = Organization(company, title)
+ organizations.put(id, organization)
+ }
+
+ return organizations
+ }
+
+ private fun getWebsites(contactId: Int? = null): SparseArray> {
+ val websites = SparseArray>()
+ val uri = Data.CONTENT_URI
+ val projection = arrayOf(
+ Data.RAW_CONTACT_ID,
+ Website.URL
+ )
+
+ val selection = getSourcesSelection(true, contactId != null)
+ val selectionArgs = getSourcesSelectionArgs(Website.CONTENT_ITEM_TYPE, contactId)
+
+ context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
+ val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
+ val url = cursor.getStringValue(Website.URL) ?: return@queryCursor
+
+ if (websites[id] == null) {
+ websites.put(id, ArrayList())
+ }
+
+ websites[id]!!.add(url)
+ }
+
+ return websites
+ }
+
+ private fun getDeviceStoredGroups(): java.util.ArrayList {
+ val groups = java.util.ArrayList()
+
+ val uri = ContactsContract.Groups.CONTENT_URI
+ val projection = arrayOf(
+ ContactsContract.Groups._ID,
+ ContactsContract.Groups.TITLE,
+ ContactsContract.Groups.SYSTEM_ID
+ )
+
+ val selection = "${ContactsContract.Groups.AUTO_ADD} = ? AND ${ContactsContract.Groups.FAVORITES} = ?"
+ val selectionArgs = arrayOf("0", "0")
+
+ context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
+ val id = cursor.getLongValue(ContactsContract.Groups._ID)
+ val title = cursor.getStringValue(ContactsContract.Groups.TITLE) ?: return@queryCursor
+
+ val systemId = cursor.getStringValue(ContactsContract.Groups.SYSTEM_ID)
+ if (groups.map { it.title }.contains(title) && systemId != null) {
+ return@queryCursor
+ }
+
+ groups.add(Group(id, title))
+ }
+ return groups
+ }
+
+ private fun getContactGroups(storedGroups: ArrayList, contactId: Int? = null): SparseArray> {
+ val groups = SparseArray>()
+
+ val uri = Data.CONTENT_URI
+ val projection = arrayOf(
+ Data.CONTACT_ID,
+ Data.DATA1
+ )
+
+ val selection = getSourcesSelection(true, contactId != null, false)
+ val selectionArgs = getSourcesSelectionArgs(GroupMembership.CONTENT_ITEM_TYPE, contactId)
+
+ context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
+ val id = cursor.getIntValue(Data.CONTACT_ID)
+ val newRowId = cursor.getLongValue(Data.DATA1)
+
+ val groupTitle = storedGroups.firstOrNull { it.id == newRowId }?.title ?: return@queryCursor
+ val group = Group(newRowId, groupTitle)
+ if (groups[id] == null) {
+ groups.put(id, ArrayList())
+ }
+ groups[id]!!.add(group)
+ }
+
+ return groups
+ }
+
+ private fun getQuestionMarks() = ("?," * displayContactSources.filter { it.isNotEmpty() }.size).trimEnd(',')
+
+ private fun getSourcesSelection(addMimeType: Boolean = false, addContactId: Boolean = false, useRawContactId: Boolean = true): String {
+ val strings = ArrayList()
+ if (addMimeType) {
+ strings.add("${Data.MIMETYPE} = ?")
+ }
+
+ if (addContactId) {
+ strings.add("${if (useRawContactId) Data.RAW_CONTACT_ID else Data.CONTACT_ID} = ?")
+ } else {
+ // sometimes local device storage has null account_name, handle it properly
+ val accountNameString = StringBuilder()
+ if (displayContactSources.contains("")) {
+ accountNameString.append("(")
+ }
+ accountNameString.append("${ContactsContract.RawContacts.ACCOUNT_NAME} IN (${getQuestionMarks()})")
+ if (displayContactSources.contains("")) {
+ accountNameString.append(" OR ${ContactsContract.RawContacts.ACCOUNT_NAME} IS NULL)")
+ }
+ strings.add(accountNameString.toString())
+ }
+
+ return TextUtils.join(" AND ", strings)
+ }
+
+ private fun getSourcesSelectionArgs(mimetype: String? = null, contactId: Int? = null): Array {
+ val args = ArrayList()
+
+ if (mimetype != null) {
+ args.add(mimetype)
+ }
+
+ if (contactId != null) {
+ args.add(contactId.toString())
+ } else {
+ args.addAll(displayContactSources.filter { it.isNotEmpty() })
+ }
+
+ return args.toTypedArray()
+ }
+
+ private fun getContactProjection() = arrayOf(
+ Data.MIMETYPE,
+ Data.CONTACT_ID,
+ Data.RAW_CONTACT_ID,
+ StructuredName.PREFIX,
+ StructuredName.GIVEN_NAME,
+ StructuredName.MIDDLE_NAME,
+ StructuredName.FAMILY_NAME,
+ StructuredName.SUFFIX,
+ StructuredName.PHOTO_URI,
+ StructuredName.PHOTO_THUMBNAIL_URI,
+ StructuredName.STARRED,
+ StructuredName.CUSTOM_RINGTONE,
+ ContactsContract.RawContacts.ACCOUNT_NAME,
+ ContactsContract.RawContacts.ACCOUNT_TYPE
+ )
+
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt
index fb048c6c..6935744d 100644
--- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt
@@ -4,7 +4,6 @@ import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
-import android.net.Uri
import android.os.Handler
import android.os.Looper
import androidx.core.app.AlarmManagerCompat
@@ -15,6 +14,8 @@ import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.helpers.isMarshmallowPlus
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.extensions.config
+import com.simplemobiletools.smsmessenger.extensions.isPlainTextMimeType
+import com.simplemobiletools.smsmessenger.models.Attachment
import com.simplemobiletools.smsmessenger.models.Message
import com.simplemobiletools.smsmessenger.receivers.ScheduledMessageReceiver
import com.simplemobiletools.smsmessenger.receivers.SmsStatusDeliveredReceiver
@@ -34,7 +35,7 @@ fun Context.getSendMessageSettings(): Settings {
return settings
}
-fun Context.sendMessage(text: String, addresses: List, subscriptionId: Int?, attachments: List) {
+fun Context.sendMessage(text: String, addresses: List, subscriptionId: Int?, attachments: List) {
val settings = getSendMessageSettings()
if (subscriptionId != null) {
settings.subscriptionId = subscriptionId
@@ -44,11 +45,19 @@ fun Context.sendMessage(text: String, addresses: List, subscriptionId: I
val message = com.klinker.android.send_message.Message(text, addresses.toTypedArray())
if (attachments.isNotEmpty()) {
- for (uri in attachments) {
+ for (attachment in attachments) {
try {
- val byteArray = contentResolver.openInputStream(uri)?.readBytes() ?: continue
- val mimeType = contentResolver.getType(uri) ?: continue
- message.addMedia(byteArray, mimeType)
+ val uri = attachment.getUri()
+ contentResolver.openInputStream(uri)?.use {
+ val bytes = it.readBytes()
+ val mimeType = if (attachment.mimetype.isPlainTextMimeType()) {
+ "application/txt"
+ } else {
+ attachment.mimetype
+ }
+ val name = attachment.filename
+ message.addMedia(bytes, mimeType, name, name)
+ }
} catch (e: Exception) {
showErrorToast(e)
} catch (e: Error) {
diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/VCardParser.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/VCardParser.kt
index 3456c080..d93c36ac 100644
--- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/VCardParser.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/VCardParser.kt
@@ -8,7 +8,12 @@ import ezvcard.VCard
fun parseVCardFromUri(context: Context, uri: Uri, callback: (vCards: List) -> Unit) {
ensureBackgroundThread {
- val inputStream = context.contentResolver.openInputStream(uri)
+ val inputStream = try {
+ context.contentResolver.openInputStream(uri)
+ } catch (e: Exception) {
+ callback(emptyList())
+ return@ensureBackgroundThread
+ }
val vCards = Ezvcard.parse(inputStream).all()
callback(vCards)
}
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..dc92e584 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,36 @@
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 id: String,
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.id == second.id
+ }
+
+ 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/kotlin/com/simplemobiletools/smsmessenger/receivers/MmsReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/MmsReceiver.kt
index 18d97921..8c2acf0b 100644
--- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/MmsReceiver.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/MmsReceiver.kt
@@ -10,6 +10,7 @@ import com.simplemobiletools.commons.extensions.normalizePhoneNumber
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.extensions.*
+import com.simplemobiletools.smsmessenger.helpers.refreshMessages
// more info at https://github.com/klinker41/android-smsmms
class MmsReceiver : com.klinker.android.send_message.MmsReceivedReceiver() {
@@ -42,6 +43,7 @@ class MmsReceiver : com.klinker.android.send_message.MmsReceivedReceiver() {
ensureBackgroundThread {
context.conversationsDB.insertOrUpdate(conversation)
context.updateUnreadCountBadge(context.conversationsDB.getUnreadConversations())
+ refreshMessages()
}
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt
index bc89a283..53c9e020 100644
--- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt
@@ -40,7 +40,7 @@ class ScheduledMessageReceiver : BroadcastReceiver() {
}
val addresses = message.participants.getAddresses()
- val attachments = message.attachment?.attachments?.mapNotNull { it.getUri() } ?: emptyList()
+ val attachments = message.attachment?.attachments ?: emptyList()
try {
context.sendMessage(message.body, addresses, message.subscriptionId, attachments)
diff --git a/app/src/main/res/drawable/ic_document_vector.xml b/app/src/main/res/drawable/ic_document_vector.xml
new file mode 100644
index 00000000..d2fae544
--- /dev/null
+++ b/app/src/main/res/drawable/ic_document_vector.xml
@@ -0,0 +1,3 @@
+
+
+
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..b20b8ad6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_image_vector.xml
@@ -0,0 +1,3 @@
+
+
+
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..0c4e948d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_music_vector.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_vector_audio_file.xml b/app/src/main/res/drawable/ic_vector_audio_file.xml
new file mode 100644
index 00000000..4705cfa2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_vector_audio_file.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_vector_folder_zip.xml b/app/src/main/res/drawable/ic_vector_folder_zip.xml
new file mode 100644
index 00000000..1a4b5f79
--- /dev/null
+++ b/app/src/main/res/drawable/ic_vector_folder_zip.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_vector_pdf.xml b/app/src/main/res/drawable/ic_vector_pdf.xml
new file mode 100644
index 00000000..23b0a48b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_vector_pdf.xml
@@ -0,0 +1,3 @@
+
+
+
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..6003cd60
--- /dev/null
+++ b/app/src/main/res/drawable/ic_vector_play_circle_outline.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_video_camera_vector.xml b/app/src/main/res/drawable/ic_video_camera_vector.xml
new file mode 100644
index 00000000..872944c0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_video_camera_vector.xml
@@ -0,0 +1,3 @@
+
+
+
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..7b16409b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_videocam_vector.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/app/src/main/res/drawable/linear_layout_vertical_divider.xml b/app/src/main/res/drawable/linear_layout_vertical_divider.xml
new file mode 100644
index 00000000..ceb1be00
--- /dev/null
+++ b/app/src/main/res/drawable/linear_layout_vertical_divider.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/app/src/main/res/layout/activity_thread.xml b/app/src/main/res/layout/activity_thread.xml
index 4e3baf2c..42448012 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_received_message.xml b/app/src/main/res/layout/item_received_message.xml
index c36e4b8c..409f6c18 100644
--- a/app/src/main/res/layout/item_received_message.xml
+++ b/app/src/main/res/layout/item_received_message.xml
@@ -34,7 +34,9 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toEndOf="@+id/thread_message_sender_photo"
- android:orientation="vertical" />
+ android:divider="@drawable/linear_layout_vertical_divider"
+ android:orientation="vertical"
+ android:showDividers="middle" />
-
-
-
-
-
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_sent_message.xml b/app/src/main/res/layout/item_sent_message.xml
index def52b64..2db0bdc9 100644
--- a/app/src/main/res/layout/item_sent_message.xml
+++ b/app/src/main/res/layout/item_sent_message.xml
@@ -23,7 +23,9 @@
android:id="@+id/thread_mesage_attachments_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical" />
+ android:divider="@drawable/linear_layout_vertical_divider"
+ android:orientation="vertical"
+ android:showDividers="middle" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index bbda648f..dbcf6559 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -20,7 +20,9 @@
ازالة التثبيت
اعادة ارسال
غير قادر على ضغط الصورة إلى الحجم المحدد
-
+
+ Duplicate item was not included
+
- و %d أخرى
- و %d أخرى
@@ -99,4 +101,4 @@
Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml
index ce4cfab9..d3ecbea8 100644
--- a/app/src/main/res/values-az/strings.xml
+++ b/app/src/main/res/values-az/strings.xml
@@ -20,7 +20,9 @@
Unpin
Forward
Unable to compress image to selected size
-
+
+ Duplicate item was not included
+
- and %d other
- and %d others
diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml
index 3cb94d5c..ae2b2073 100644
--- a/app/src/main/res/values-be/strings.xml
+++ b/app/src/main/res/values-be/strings.xml
@@ -20,7 +20,9 @@
Адмацаваць
Пераслаць
Немагчыма сціснуць выяву да выбранага памеру
-
+
+ Duplicate item was not included
+
- and %d other
- and %d others
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
index 8a4ba571..1f708488 100644
--- a/app/src/main/res/values-bg/strings.xml
+++ b/app/src/main/res/values-bg/strings.xml
@@ -20,7 +20,9 @@
Откачване
Препращане
Невъзможно е да се компресира изображението до избрания размер
-
+
+ Duplicate item was not included
+
- and %d other
- and %d others
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index 5818bef5..672e8f60 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -20,7 +20,9 @@
No fixis
Reenvia
No s\'ha pogut comprimir la imatge a la mida seleccionada
-
+
+ Duplicate item was not included
+
- i %d altra
- i %d altres
@@ -87,4 +89,4 @@
Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-cr/strings.xml b/app/src/main/res/values-cr/strings.xml
index ce4cfab9..d3ecbea8 100644
--- a/app/src/main/res/values-cr/strings.xml
+++ b/app/src/main/res/values-cr/strings.xml
@@ -20,7 +20,9 @@
Unpin
Forward
Unable to compress image to selected size
-
+
+ Duplicate item was not included
+
- and %d other
- and %d others
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 826c1fc6..8eafa162 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -20,7 +20,9 @@
Odepnout
Přeposlat
Nepodařilo se komprimovat obrázek na požadovanou velikost
-
+
+ Duplicate item was not included
+
- a %d další
- a %d další
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index e7eee89a..a56e24ab 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -20,7 +20,9 @@
Frigør
Fremad
Billedet kan ikke komprimeres til den valgte størrelse
-
+
+ Duplicate item was not included
+
- and %d other
- and %d others
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 2070b2f8..6983cc16 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -20,7 +20,9 @@
Losheften
Weiterleiten
Bild kann nicht auf ausgewählte Größe komprimiert werden
-
+
+ Duplicate item was not included
+
- und %d andere
- und %d anderen
@@ -87,4 +89,4 @@
Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index b736ea37..84e8d44c 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -20,7 +20,9 @@
Ξεκαρφίτσωμα
Προώθηση
Αδυναμία συμπίεσης εικόνας στο επιλεγμένο μέγεθος
-
+
+ Duplicate item was not included
+
- και άλλος %d
- και άλλοι %d
diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml
index 8efb4fed..bfd97ea1 100644
--- a/app/src/main/res/values-eo/strings.xml
+++ b/app/src/main/res/values-eo/strings.xml
@@ -20,7 +20,9 @@
Depingli
Forward
Unable to compress image to selected size
-
+
+ Duplicate item was not included
+
- and %d other
- and %d others
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 14149d72..95c07450 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -20,7 +20,9 @@
Desanclar
Reenviar
Incapaz de comprimir la imagen al tamaño seleccionado
-
+
+ Duplicate item was not included
+
- y %d otro
- y %d otros
@@ -90,4 +92,4 @@
Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
index 0d38e20e..19dccea1 100644
--- a/app/src/main/res/values-et/strings.xml
+++ b/app/src/main/res/values-et/strings.xml
@@ -20,7 +20,9 @@
Eemalda kinnitus
Edasta
Pildi muutmine valitud suurusesse ei õnnestu
-
+
+ Duplicate item was not included
+
- ja %d muud
- ja %d teised
@@ -87,4 +89,4 @@
Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
index 9002ea6c..57764854 100644
--- a/app/src/main/res/values-fi/strings.xml
+++ b/app/src/main/res/values-fi/strings.xml
@@ -20,7 +20,9 @@
Poista
Välitä
Kuvan pakkaaminen valittuun kokoon ei onnistu
-
+
+ Duplicate item was not included
+
- ja %d muu
- ja %d muuta
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 0151d724..a6362373 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -20,7 +20,9 @@
Désépingler
Transférer
Impossible de compresser l\'image à la taille sélectionnée
-
+
+ Duplicate item was not included
+
- et %d autre
- et %d autres
@@ -90,4 +92,4 @@
Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index 8d0b9a2d..9be31218 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -20,7 +20,9 @@
Desenganche
Adiante
Non se puido comprimir a imaxe ao tamaño seleccionado
-
+
+ Duplicate item was not included
+
- E %d outro
- E %d outros
@@ -87,4 +89,4 @@
Non atopaches algunhas cadeas? Hai máis en
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml
index ce4cfab9..d3ecbea8 100644
--- a/app/src/main/res/values-hi/strings.xml
+++ b/app/src/main/res/values-hi/strings.xml
@@ -20,7 +20,9 @@
Unpin
Forward
Unable to compress image to selected size
-
+
+ Duplicate item was not included
+
- and %d other
- and %d others
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index 14cd1f2c..3fda8528 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -20,7 +20,9 @@
Otkvači
Proslijedi
Isključi za komprimiranje slike na odabranu veličinu
-
+
+ Duplicate item was not included
+
- i još %d druga
- i još %d druge
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index c5cc383b..8e99341d 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -20,7 +20,9 @@
Kitűzés megszüntetése
Továbbítás
Nem lehet tömöríteni a képet a kiválasztott méretre
-
+
+ Duplicate item was not included
+
- és még %d fő
- és még %d fő
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index e4307a8b..f09f240e 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -20,7 +20,9 @@
Unpin
Forward
Unable to compress image to selected size
-
+
+ Duplicate item was not included
+
- and %d others
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 5d50ef49..818c4175 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -20,7 +20,9 @@
Rimuovi
Inoltra
Impossibile comprimere l\'immagine alla dimensione selezionata
-
+
+ Duplicate item was not included
+
- e %d altro
- e %d altri
@@ -90,4 +92,4 @@
Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
index d1abdaa8..578270c1 100644
--- a/app/src/main/res/values-iw/strings.xml
+++ b/app/src/main/res/values-iw/strings.xml
@@ -20,7 +20,9 @@
בטל הצמדה
התקדם
לא ניתן לדחוס תמונה לגודל שנבחר
-
+
+ Duplicate item was not included
+
- and %d other
- and %d others
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 204b05cf..c33d2132 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -20,7 +20,9 @@
固定を外す
転送
選択したサイズに画像を圧縮できません
-
+
+ Duplicate item was not included
+
- and %d others
@@ -84,4 +86,4 @@
Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml
index 2f6a4218..fc00464f 100644
--- a/app/src/main/res/values-lt/strings.xml
+++ b/app/src/main/res/values-lt/strings.xml
@@ -20,7 +20,9 @@
Atjunkite
Pirmyn
Nepavyksta suspausti vaizdo iki pasirinkto dydžio
-
+
+ Duplicate item was not included
+
- and %d other
- and %d others
diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml
index b49e6d50..3f955f9e 100644
--- a/app/src/main/res/values-lv/strings.xml
+++ b/app/src/main/res/values-lv/strings.xml
@@ -20,7 +20,9 @@
Unpin
Forward
Unable to compress image to selected size
-
+
+ Duplicate item was not included
+
- and %d other
- and %d others
diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml
index ce4cfab9..d3ecbea8 100644
--- a/app/src/main/res/values-mk/strings.xml
+++ b/app/src/main/res/values-mk/strings.xml
@@ -20,7 +20,9 @@
Unpin
Forward
Unable to compress image to selected size
-
+
+ Duplicate item was not included
+
- and %d other
- and %d others
diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml
index 284ab728..7e4bb7fe 100644
--- a/app/src/main/res/values-ml/strings.xml
+++ b/app/src/main/res/values-ml/strings.xml
@@ -20,7 +20,9 @@
Unpin
Forward
Unable to compress image to selected size
-
+
+ Duplicate item was not included
+
- and %d other
- and %d others
diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml
index 3a6750d7..fd7d141f 100644
--- a/app/src/main/res/values-nb-rNO/strings.xml
+++ b/app/src/main/res/values-nb-rNO/strings.xml
@@ -20,7 +20,9 @@
Løsne
Videresend
Unable to compress image to selected size
-
+
+ Duplicate item was not included
+
- and %d other
- and %d others
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index fca96f51..552320b0 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -20,7 +20,9 @@
Losmaken
Doorsturen
Kon de afbeelding niet comprimeren naar de gekozen grootte
-
+
+ Duplicate item was not included
+
- en %d andere
- en %d anderen
@@ -87,4 +89,4 @@
Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-pa-rPK/strings.xml b/app/src/main/res/values-pa-rPK/strings.xml
index a2d002f7..66f5a68c 100644
--- a/app/src/main/res/values-pa-rPK/strings.xml
+++ b/app/src/main/res/values-pa-rPK/strings.xml
@@ -20,7 +20,9 @@
Unpin
اگے
تصویر نوں چݨے ہوۓ اکار وچ سنکُچت کر نہیں سکدی
-
+
+ Duplicate item was not included
+
- تے %d ہور
- تے %d ہور
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 0d013873..50cbb1d9 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -20,7 +20,9 @@
Odepnij
Przekaż dalej
Nie udało się skompresować obrazu do wybranego rozmiaru
-
+
+ Duplicate item was not included
+
- i %d inny
- i %d inne
@@ -93,4 +95,4 @@
Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index fce03488..e9adec12 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -20,7 +20,9 @@
Desfixar
Encaminhar
Não pôde comprimir imagem ao tamanho selecionado
-
+
+ Duplicate item was not included
+
- and %d other
- and %d others
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 66339642..7a4e6f27 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -20,7 +20,9 @@
Desafixar
Reencaminhar
Incapaz de comprimir imagem no tamanho selecionado
-
+
+ Duplicate item was not included
+
- and %d other
- and %d others
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 6fb541ce..8883889b 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -20,7 +20,9 @@
Elimină fixarea
Redirecţionare
Nu se poate comprima imaginea la dimensiunea selectată
-
+
+ Duplicate item was not included
+
- și %d alt
- și %d altele
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 3ad117cc..05ca7ed8 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -20,7 +20,9 @@
Открепить
Переслать
Невозможно сжать изображение до выбранного размера
-
+
+ Duplicate item was not included
+
- и ещё %d
- и ещё %d
@@ -93,4 +95,4 @@
Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 28cd4030..d1fe3311 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -20,7 +20,9 @@
Odopnúť
Preposlať
Nepodarilo sa zmenšiť obrázok na požadovanú veľkosť
-
+
+ Duplicate item was not included
+
- a %d ďalší
- a %d ďalší
diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml
index 388241a0..8a14192f 100644
--- a/app/src/main/res/values-sl/strings.xml
+++ b/app/src/main/res/values-sl/strings.xml
@@ -20,7 +20,9 @@
Odpni
Posreduj
Slike ni mogoče stisniti na izbrano velikost
-
+
+ Duplicate item was not included
+
- in %d drug
- in %d druga
@@ -93,4 +95,4 @@
Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index c9fe0303..01abcdaa 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -20,7 +20,9 @@
Lossa
Vidarebefordra
Det gick inte att komprimera bilden till den valda storleken
-
+
+ Duplicate item was not included
+
- och %d annan
- och %d andra
@@ -87,4 +89,4 @@
Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml
index 65cbce32..c5f0f542 100644
--- a/app/src/main/res/values-ta/strings.xml
+++ b/app/src/main/res/values-ta/strings.xml
@@ -20,7 +20,9 @@
பின் நீக்கு
முன்னோக்கி
Unable to compress image to selected size
-
+
+ Duplicate item was not included
+
- and %d other
- and %d others
diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml
index aca6f73e..e5eaf577 100644
--- a/app/src/main/res/values-th/strings.xml
+++ b/app/src/main/res/values-th/strings.xml
@@ -20,7 +20,9 @@
Unpin
Forward
Unable to compress image to selected size
-
+
+ Duplicate item was not included
+
- and %d others
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index c03eb152..e778a209 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -20,7 +20,9 @@
Sabitlemeyi kaldır
İlet
Resim seçilen boyuta sıkıştırılamıyor
-
+
+ Duplicate item was not included
+
- ve %d diğeri
- ve %d diğeri
@@ -87,4 +89,4 @@
Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index ac09c46c..70c414ca 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -20,7 +20,9 @@
Відкріпити
Переслати
Не вдається стиснути зображення до вибраного розміру
-
+
+ Duplicate item was not included
+
- and %d other
- and %d others
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 1f50f0b6..d39fd9c8 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -20,7 +20,9 @@
取消固定
转发
无法将图像压缩到选定的大小
-
+
+ Duplicate item was not included
+
- 及 %d 个其他人
@@ -84,4 +86,4 @@
Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index e553273c..661c5b8a 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -20,7 +20,9 @@
取消釘選
轉傳
無法將圖片壓縮至指定大小
-
+
+ Duplicate item was not included
+
- and %d others
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index e0959a28..324712d5 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -3,9 +3,15 @@
72dp
64dp
36dp
- 60dp
+ 64dp
+ @dimen/attachment_preview_size
+ 156dp
24dp
15dp
64dp
20dp
+ 36dp
+ 96dp
+ 90dp
+ 250dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d05f0dc0..ed8a94d4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -20,7 +20,9 @@
Unpin
Forward
Unable to compress image to selected size
-
+
+ Duplicate item was not included
+
- and %d other
- and %d others
diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml
index 758c02cd..7c909b1b 100644
--- a/app/src/main/res/xml/provider_paths.xml
+++ b/app/src/main/res/xml/provider_paths.xml
@@ -1,5 +1,5 @@
-
+