Merge pull request #471 from Naveen3Singh/feature_attachments

Handle sending and receiving all file types
This commit is contained in:
Tibor Kaputa
2022-11-08 10:35:11 +01:00
committed by GitHub
89 changed files with 2193 additions and 546 deletions

View File

@ -63,10 +63,10 @@ android {
} }
dependencies { dependencies {
implementation 'com.github.SimpleMobileTools:Simple-Commons:0828fecd09' implementation 'com.github.SimpleMobileTools:Simple-Commons:d5ae570e2a'
implementation 'org.greenrobot:eventbus:3.3.1' implementation 'org.greenrobot:eventbus:3.3.1'
implementation 'com.github.tibbi:IndicatorFastScroll:4524cd0b61' 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 "me.leolin:ShortcutBadger:1.1.22"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.googlecode.ez-vcard:ez-vcard:0.11.3' implementation 'com.googlecode.ez-vcard:ez-vcard:0.11.3'

View File

@ -52,7 +52,8 @@
<activity <activity
android:name=".activities.ThreadActivity" android:name=".activities.ThreadActivity"
android:exported="false" android:exported="false"
android:parentActivityName=".activities.MainActivity" /> android:parentActivityName=".activities.MainActivity"
android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".activities.SearchActivity" android:name=".activities.SearchActivity"
@ -93,17 +94,14 @@
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" /> <data android:mimeType="*/*" />
<data android:mimeType="image/*" />
<data android:mimeType="video/*" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" /> <action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" /> <data android:mimeType="*/*" />
<data android:mimeType="video/*" />
</intent-filter> </intent-filter>
</activity> </activity>

View File

@ -87,6 +87,7 @@ class MainActivity : SimpleActivity() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
setupToolbar(main_toolbar) setupToolbar(main_toolbar)
if (storedTextColor != getProperTextColor()) { if (storedTextColor != getProperTextColor()) {
(conversations_list.adapter as? ConversationsAdapter)?.updateTextColor(getProperTextColor()) (conversations_list.adapter as? ConversationsAdapter)?.updateTextColor(getProperTextColor())
} }

View File

@ -5,7 +5,6 @@ import android.app.Activity
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable import android.graphics.drawable.LayerDrawable
import android.media.MediaMetadataRetriever import android.media.MediaMetadataRetriever
import android.net.Uri import android.net.Uri
@ -27,21 +26,15 @@ import android.util.TypedValue
import android.view.Gravity import android.view.Gravity
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.view.animation.OvershootInterpolator
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.LinearLayout.LayoutParams import android.widget.LinearLayout.LayoutParams
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.annotation.StringRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import com.bumptech.glide.Glide import androidx.core.view.*
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.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.simplemobiletools.commons.dialogs.ConfirmationDialog import com.simplemobiletools.commons.dialogs.ConfirmationDialog
@ -53,6 +46,7 @@ import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.commons.models.SimpleContact import com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.adapters.AttachmentsAdapter
import com.simplemobiletools.smsmessenger.adapters.AutoCompleteTextViewAdapter import com.simplemobiletools.smsmessenger.adapters.AutoCompleteTextViewAdapter
import com.simplemobiletools.smsmessenger.adapters.ThreadAdapter import com.simplemobiletools.smsmessenger.adapters.ThreadAdapter
import com.simplemobiletools.smsmessenger.dialogs.ScheduleMessageDialog import com.simplemobiletools.smsmessenger.dialogs.ScheduleMessageDialog
@ -60,8 +54,8 @@ import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.* import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.models.* import com.simplemobiletools.smsmessenger.models.*
import kotlinx.android.synthetic.main.activity_thread.* import kotlinx.android.synthetic.main.activity_thread.*
import kotlinx.android.synthetic.main.item_attachment.view.*
import kotlinx.android.synthetic.main.item_selected_contact.view.* import kotlinx.android.synthetic.main.item_selected_contact.view.*
import kotlinx.android.synthetic.main.layout_attachment_picker.*
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
@ -72,12 +66,6 @@ import java.io.OutputStream
class ThreadActivity : SimpleActivity() { class ThreadActivity : SimpleActivity() {
private val MIN_DATE_TIME_DIFF_SECS = 300 private val MIN_DATE_TIME_DIFF_SECS = 300
private val PICK_ATTACHMENT_INTENT = 1
private val PICK_SAVE_FILE_INTENT = 11
private val TAKE_PHOTO_INTENT = 42
private val TYPE_TAKE_PHOTO = 12
private val TYPE_CHOOSE_PHOTO = 13
private val TYPE_EDIT = 14 private val TYPE_EDIT = 14
private val TYPE_SEND = 15 private val TYPE_SEND = 15
@ -93,8 +81,6 @@ class ThreadActivity : SimpleActivity() {
private var privateContacts = ArrayList<SimpleContact>() private var privateContacts = ArrayList<SimpleContact>()
private var messages = ArrayList<Message>() private var messages = ArrayList<Message>()
private val availableSIMCards = ArrayList<SIMCard>() private val availableSIMCards = ArrayList<SIMCard>()
private var attachmentSelections = mutableMapOf<String, AttachmentSelection>()
private val imageCompressor by lazy { ImageCompressor(this) }
private var lastAttachmentUri: String? = null private var lastAttachmentUri: String? = null
private var capturedImageUri: Uri? = null private var capturedImageUri: Uri? = null
private var loadingOlderMessages = false private var loadingOlderMessages = false
@ -105,6 +91,8 @@ class ThreadActivity : SimpleActivity() {
private var scheduledMessage: Message? = null private var scheduledMessage: Message? = null
private lateinit var scheduledDateTime: DateTime private lateinit var scheduledDateTime: DateTime
private var isAttachmentPickerVisible = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_thread) setContentView(R.layout.activity_thread)
@ -145,6 +133,10 @@ class ThreadActivity : SimpleActivity() {
finish() finish()
} }
} }
setupAttachmentPickerView()
setupKeyboardListener()
hideAttachmentPicker()
} }
override fun onResume() { override fun onResume() {
@ -161,17 +153,25 @@ class ThreadActivity : SimpleActivity() {
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
if (thread_type_message.value != "" && attachmentSelections.isEmpty()) { if (thread_type_message.value != "" && getAttachmentSelections().isEmpty()) {
saveSmsDraft(thread_type_message.value, threadId) saveSmsDraft(thread_type_message.value, threadId)
} else { } else {
deleteSmsDraft(threadId) deleteSmsDraft(threadId)
} }
bus?.post(Events.RefreshMessages()) bus?.post(Events.RefreshMessages())
isActivityVisible = false isActivityVisible = false
} }
override fun onBackPressed() {
isAttachmentPickerVisible = false
if (attachment_picker_holder.isVisible()) {
hideAttachmentPicker()
} else {
super.onBackPressed()
}
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
bus?.unregister(this) bus?.unregister(this)
@ -214,13 +214,16 @@ class ThreadActivity : SimpleActivity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
super.onActivityResult(requestCode, resultCode, resultData) super.onActivityResult(requestCode, resultCode, resultData)
if (resultCode != Activity.RESULT_OK) return if (resultCode != Activity.RESULT_OK) return
val data = resultData?.data
if (requestCode == TAKE_PHOTO_INTENT && capturedImageUri != null) { if (requestCode == CAPTURE_PHOTO_INTENT && capturedImageUri != null) {
addAttachment(capturedImageUri!!) addAttachment(capturedImageUri!!)
} else if (requestCode == PICK_ATTACHMENT_INTENT && resultData != null && resultData.data != null) { } else if (data != null) {
addAttachment(resultData.data!!) when (requestCode) {
} else if (requestCode == PICK_SAVE_FILE_INTENT && resultData != null && resultData.data != null) { CAPTURE_VIDEO_INTENT, PICK_DOCUMENT_INTENT, CAPTURE_AUDIO_INTENT, PICK_PHOTO_INTENT, PICK_VIDEO_INTENT -> addAttachment(data)
saveAttachment(resultData) PICK_CONTACT_INTENT -> addContactAttachment(data)
PICK_SAVE_FILE_INTENT -> saveAttachment(resultData)
}
} }
} }
@ -371,6 +374,7 @@ class ThreadActivity : SimpleActivity() {
} }
} }
runOnUiThread {
confirm_inserted_number?.setOnClickListener { confirm_inserted_number?.setOnClickListener {
val number = add_contact_or_number.value val number = add_contact_or_number.value
val phoneNumber = PhoneNumber(number, 0, "", number) val phoneNumber = PhoneNumber(number, 0, "", number)
@ -378,6 +382,7 @@ class ThreadActivity : SimpleActivity() {
addSelectedContact(contact) addSelectedContact(contact)
} }
} }
}
private fun handleItemClick(any: Any) { private fun handleItemClick(any: Any) {
when { when {
@ -427,6 +432,7 @@ class ThreadActivity : SimpleActivity() {
setTextColor(textColor) setTextColor(textColor)
compoundDrawables.forEach { it?.applyColorFilter(textColor) } compoundDrawables.forEach { it?.applyColorFilter(textColor) }
} }
confirm_manage_contacts.applyColorFilter(textColor) confirm_manage_contacts.applyColorFilter(textColor)
thread_add_attachment.applyColorFilter(textColor) thread_add_attachment.applyColorFilter(textColor)
@ -440,6 +446,7 @@ class ThreadActivity : SimpleActivity() {
thread_send_message.setOnClickListener { thread_send_message.setOnClickListener {
sendMessage() sendMessage()
} }
thread_send_message.setOnLongClickListener { thread_send_message.setOnLongClickListener {
if (!isScheduledMessage) { if (!isScheduledMessage) {
launchScheduleSendDialog() launchScheduleSendDialog()
@ -478,7 +485,15 @@ class ThreadActivity : SimpleActivity() {
thread_type_message.setText(intent.getStringExtra(THREAD_TEXT)) thread_type_message.setText(intent.getStringExtra(THREAD_TEXT))
thread_add_attachment.setOnClickListener { thread_add_attachment.setOnClickListener {
takeOrPickPhotoVideo() if (attachment_picker_holder.isVisible()) {
isAttachmentPickerVisible = false
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) { if (intent.extras?.containsKey(THREAD_ATTACHMENT_URI) == true) {
@ -775,135 +790,130 @@ class ThreadActivity : SimpleActivity() {
return items return items
} }
private fun takeOrPickPhotoVideo() { private fun launchActivityForResult(intent: Intent, requestCode: Int, @StringRes error: Int = R.string.no_app_found) {
val items = arrayListOf( hideKeyboard()
RadioItem(TYPE_TAKE_PHOTO, getString(R.string.take_photo)),
RadioItem(TYPE_CHOOSE_PHOTO, getString(R.string.choose_photo))
)
RadioGroupDialog(this, items = items) {
val checkedId = it as Int
if (checkedId == TYPE_TAKE_PHOTO) {
launchTakePhotoIntent()
} else if (checkedId == TYPE_CHOOSE_PHOTO) {
launchPickPhotoVideoIntent()
}
}
}
private fun launchTakePhotoIntent() {
val imageFile = createImageFile()
capturedImageUri = getMyFileUri(imageFile)
try { try {
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply { startActivityForResult(intent, requestCode)
putExtra(MediaStore.EXTRA_OUTPUT, capturedImageUri)
}
startActivityForResult(intent, TAKE_PHOTO_INTENT)
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
showErrorToast(getString(R.string.no_app_found)) showErrorToast(getString(error))
} catch (e: Exception) { } catch (e: Exception) {
showErrorToast(e) showErrorToast(e)
} }
} }
private fun launchPickPhotoVideoIntent() { private fun getAttachmentsDir(): File {
hideKeyboard() return File(cacheDir, "attachments").apply {
val mimeTypes = arrayOf("image/*", "video/*") 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<String>, requestCode: Int) {
Intent(Intent.ACTION_GET_CONTENT).apply { Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*" type = "*/*"
putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes) putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
launchActivityForResult(this, requestCode)
}
}
try { private fun launchPickContactIntent() {
startActivityForResult(this, PICK_ATTACHMENT_INTENT) Intent(Intent.ACTION_PICK).apply {
} catch (e: ActivityNotFoundException) { type = ContactsContract.Contacts.CONTENT_TYPE
showErrorToast(getString(R.string.no_app_found)) launchActivityForResult(this, PICK_CONTACT_INTENT)
} catch (e: Exception) { }
showErrorToast(e) }
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) { private fun addAttachment(uri: Uri) {
val originalUriString = uri.toString() val id = uri.toString()
if (attachmentSelections.containsKey(originalUriString)) { if (getAttachmentSelections().any { it.id == id }) {
toast(R.string.duplicate_item_warning)
return return
} }
attachmentSelections[originalUriString] = AttachmentSelection(uri, false) var adapter = getAttachmentsAdapter()
val attachmentView = addAttachmentView(originalUriString, uri) if (adapter == null) {
val mimeType = contentResolver.getType(uri) ?: return adapter = AttachmentsAdapter(
activity = this,
if (mimeType.isImageMimeType() && config.mmsFileSizeLimit != FILE_SIZE_NONE) { recyclerView = thread_attachments_recyclerview,
val selection = attachmentSelections[originalUriString] onAttachmentsRemoved = {
attachmentSelections[originalUriString] = selection!!.copy(isPending = true) thread_attachments_recyclerview.beGone()
checkSendMessageAvailability() checkSendMessageAvailability()
attachmentView.thread_attachment_progress.beVisible() },
imageCompressor.compressImage(uri, config.mmsFileSizeLimit) { compressedUri -> onReady = { checkSendMessageAvailability() }
runOnUiThread { )
if (compressedUri != null) { thread_attachments_recyclerview.adapter = adapter
attachmentSelections[originalUriString] = AttachmentSelection(compressedUri, false)
loadAttachmentPreview(attachmentView, compressedUri)
} else {
toast(R.string.compress_error)
removeAttachment(attachmentView, originalUriString)
}
checkSendMessageAvailability()
attachmentView.thread_attachment_progress.beGone()
}
}
}
} }
private fun addAttachmentView(originalUri: String, uri: Uri): View { thread_attachments_recyclerview.beVisible()
thread_attachments_holder.beVisible() val mimeType = contentResolver.getType(uri)
val attachmentView = layoutInflater.inflate(R.layout.item_attachment, null).apply { if (mimeType == null) {
thread_attachments_wrapper.addView(this) toast(R.string.unknown_error_occurred)
thread_remove_attachment.setOnClickListener {
removeAttachment(this, originalUri)
}
}
loadAttachmentPreview(attachmentView, uri)
return attachmentView
}
private fun loadAttachmentPreview(attachmentView: View, uri: Uri) {
if (isDestroyed || isFinishing) {
return return
} }
val attachment = AttachmentSelection(
val roundedCornersRadius = resources.getDimension(R.dimen.medium_margin).toInt() id = id,
val options = RequestOptions() uri = uri,
.diskCacheStrategy(DiskCacheStrategy.NONE) mimetype = mimeType,
.transform(CenterCrop(), RoundedCorners(roundedCornersRadius)) filename = getFilenameFromUri(uri),
isPending = mimeType.isImageMimeType() && !mimeType.isGifMimeType()
Glide.with(attachmentView.thread_attachment_preview) )
.load(uri) adapter.addAttachment(attachment)
.transition(DrawableTransitionOptions.withCrossFade())
.apply(options)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
attachmentView.thread_attachment_preview.beGone()
attachmentView.thread_remove_attachment.beGone()
return false
}
override fun onResourceReady(dr: Drawable?, a: Any?, t: Target<Drawable>?, d: DataSource?, i: Boolean): Boolean {
attachmentView.thread_attachment_preview.beVisible()
attachmentView.thread_remove_attachment.beVisible()
checkSendMessageAvailability()
return false
}
})
.into(attachmentView.thread_attachment_preview)
}
private fun removeAttachment(attachmentView: View, originalUri: String) {
thread_attachments_wrapper.removeView(attachmentView)
attachmentSelections.remove(originalUri)
if (attachmentSelections.isEmpty()) {
thread_attachments_holder.beGone()
}
checkSendMessageAvailability() checkSendMessageAvailability()
} }
@ -928,7 +938,7 @@ class ThreadActivity : SimpleActivity() {
} }
private fun checkSendMessageAvailability() { private fun checkSendMessageAvailability() {
if (thread_type_message.text!!.isNotEmpty() || (attachmentSelections.isNotEmpty() && !attachmentSelections.values.any { it.isPending })) { if (thread_type_message.text!!.isNotEmpty() || (getAttachmentSelections().isNotEmpty() && !getAttachmentSelections().any { it.isPending })) {
thread_send_message.isEnabled = true thread_send_message.isEnabled = true
thread_send_message.isClickable = true thread_send_message.isClickable = true
thread_send_message.alpha = 0.9f thread_send_message.alpha = 0.9f
@ -942,7 +952,7 @@ class ThreadActivity : SimpleActivity() {
private fun sendMessage() { private fun sendMessage() {
var text = thread_type_message.value var text = thread_type_message.value
if (text.isEmpty() && attachmentSelections.isEmpty()) { if (text.isEmpty() && getAttachmentSelections().isEmpty()) {
showErrorToast(getString(R.string.unknown_error_occurred)) showErrorToast(getString(R.string.unknown_error_occurred))
return return
} }
@ -982,7 +992,8 @@ class ThreadActivity : SimpleActivity() {
conversationsDB.insertOrUpdate(conversation.copy(date = nowSeconds)) conversationsDB.insertOrUpdate(conversation.copy(date = nowSeconds))
} }
scheduleMessage(message) scheduleMessage(message)
}
runOnUiThread {
clearCurrentMessage() clearCurrentMessage()
hideScheduleSendUi() hideScheduleSendUi()
scheduledMessage = null scheduledMessage = null
@ -990,6 +1001,8 @@ class ThreadActivity : SimpleActivity() {
if (!refreshedSinceSent) { if (!refreshedSinceSent) {
refreshMessages() refreshMessages()
} }
}
}
} catch (e: Exception) { } catch (e: Exception) {
showErrorToast(e.localizedMessage ?: getString(R.string.unknown_error_occurred)) showErrorToast(e.localizedMessage ?: getString(R.string.unknown_error_occurred))
} }
@ -997,8 +1010,7 @@ class ThreadActivity : SimpleActivity() {
private fun sendNormalMessage(text: String, subscriptionId: Int) { private fun sendNormalMessage(text: String, subscriptionId: Int) {
val addresses = participants.getAddresses() val addresses = participants.getAddresses()
val attachments = attachmentSelections.values val attachments = buildMessageAttachments()
.map { it.uri }
try { try {
refreshedSinceSent = false refreshedSinceSent = false
@ -1017,9 +1029,8 @@ class ThreadActivity : SimpleActivity() {
private fun clearCurrentMessage() { private fun clearCurrentMessage() {
thread_type_message.setText("") thread_type_message.setText("")
attachmentSelections.clear() getAttachmentsAdapter()?.clear()
thread_attachments_holder.beGone() checkSendMessageAvailability()
thread_attachments_wrapper.removeAllViews()
} }
// show selected contacts, properly split to new lines when appropriate // show selected contacts, properly split to new lines when appropriate
@ -1144,14 +1155,7 @@ class ThreadActivity : SimpleActivity() {
type = mimeType type = mimeType
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
putExtra(Intent.EXTRA_TITLE, path.split("/").last()) putExtra(Intent.EXTRA_TITLE, path.split("/").last())
launchActivityForResult(this, PICK_SAVE_FILE_INTENT, error = R.string.system_service_disabled)
try {
startActivityForResult(this, PICK_SAVE_FILE_INTENT)
} catch (e: ActivityNotFoundException) {
showErrorToast(getString(R.string.system_service_disabled))
} catch (e: Exception) {
showErrorToast(e)
}
} }
} }
@ -1199,7 +1203,7 @@ class ThreadActivity : SimpleActivity() {
private fun isMmsMessage(text: String): Boolean { private fun isMmsMessage(text: String): Boolean {
val isGroupMms = participants.size > 1 && config.sendGroupMessageMMS val isGroupMms = participants.size > 1 && config.sendGroupMessageMMS
val isLongMmsMessage = isLongMmsMessage(text) && config.sendLongMessageMMS val isLongMmsMessage = isLongMmsMessage(text) && config.sendLongMessageMMS
return attachmentSelections.isNotEmpty() || isGroupMms || isLongMmsMessage return getAttachmentSelections().isNotEmpty() || isGroupMms || isLongMmsMessage
} }
private fun updateMessageType() { private fun updateMessageType() {
@ -1212,15 +1216,6 @@ class ThreadActivity : SimpleActivity() {
thread_send_message.setText(stringId) thread_send_message.setText(stringId)
} }
private fun createImageFile(): File {
val outputDirectory = File(cacheDir, "captured").apply {
if (!exists()) {
mkdirs()
}
}
return File.createTempFile("IMG_", ".jpg", outputDirectory)
}
private fun showScheduledMessageInfo(message: Message) { private fun showScheduledMessageInfo(message: Message) {
val items = arrayListOf( val items = arrayListOf(
RadioItem(TYPE_EDIT, getString(R.string.update_message)), RadioItem(TYPE_EDIT, getString(R.string.update_message)),
@ -1277,7 +1272,7 @@ class ThreadActivity : SimpleActivity() {
private fun setupScheduleSendUi() { private fun setupScheduleSendUi() {
val textColor = getProperTextColor() val textColor = getProperTextColor()
scheduled_message_holder.background.applyColorFilter(getProperBackgroundColor().getContrastColor()) scheduled_message_holder.background.applyColorFilter(getProperPrimaryColor().darkenColor())
scheduled_message_button.apply { scheduled_message_button.apply {
val clockDrawable = ResourcesCompat.getDrawable(resources, R.drawable.ic_clock_vector, theme)?.apply { applyColorFilter(textColor) } val clockDrawable = ResourcesCompat.getDrawable(resources, R.drawable.ic_clock_vector, theme)?.apply { applyColorFilter(textColor) }
setCompoundDrawablesWithIntrinsicBounds(clockDrawable, null, null, null) setCompoundDrawablesWithIntrinsicBounds(clockDrawable, null, null, null)
@ -1344,7 +1339,7 @@ class ThreadActivity : SimpleActivity() {
read = false, read = false,
threadId = threadId, threadId = threadId,
isMMS = isMmsMessage(text), isMMS = isMmsMessage(text),
attachment = buildMessageAttachment(text, messageId), attachment = MessageAttachment(messageId, text, buildMessageAttachments(messageId)),
senderName = "", senderName = "",
senderPhotoUri = "", senderPhotoUri = "",
subscriptionId = subscriptionId, subscriptionId = subscriptionId,
@ -1352,11 +1347,138 @@ class ThreadActivity : SimpleActivity() {
) )
} }
private fun buildMessageAttachment(text: String, messageId: Long): MessageAttachment { private fun buildMessageAttachments(messageId: Long = -1L) = getAttachmentSelections()
val attachments = attachmentSelections.values .map { Attachment(null, messageId, it.uri.toString(), it.mimetype, 0, 0, it.filename) }
.map { Attachment(null, messageId, it.uri.toString(), contentResolver.getType(it.uri) ?: "*/*", 0, 0, "") }
.toArrayList() .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<ConstraintLayout.LayoutParams> {
height = config.keyboardHeight
}
}
animateAttachmentButton(rotation = 0f)
}
private fun animateAttachmentButton(rotation: Float) {
thread_add_attachment.animate()
.rotation(rotation)
.setDuration(500L)
.setInterpolator(OvershootInterpolator())
.start()
}
private fun setupKeyboardListener() {
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<WindowInsetsAnimationCompat>) = 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()
}
} }
} }

View File

@ -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<AttachmentSelection, AttachmentsAdapter.ViewHolder>(AttachmentDiffCallback()) {
private val config = activity.config
private val resources = activity.resources
private val primaryColor = activity.getProperPrimaryColor()
private val imageCompressor by lazy { ImageCompressor(activity) }
val attachments = mutableListOf<AttachmentSelection>()
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<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
removeAttachment(attachment)
activity.toast(R.string.unknown_error_occurred)
return false
}
override fun onResourceReady(dr: Drawable?, a: Any?, t: Target<Drawable>?, d: DataSource?, i: Boolean): Boolean {
view.thumbnail.beVisible()
view.play_icon.beVisibleIf(attachment.mimetype.isVideoMimeType())
view.compression_progress.beGone()
return false
}
})
.into(view.thumbnail)
}
open inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bindView(callback: (itemView: View, adapterPosition: Int) -> Unit): View {
return itemView.apply {
callback(this, adapterPosition)
}
}
}
}
private class AttachmentDiffCallback : DiffUtil.ItemCallback<AttachmentSelection>() {
override fun areItemsTheSame(oldItem: AttachmentSelection, newItem: AttachmentSelection): Boolean {
return AttachmentSelection.areItemsTheSame(oldItem, newItem)
}
override fun areContentsTheSame(oldItem: AttachmentSelection, newItem: AttachmentSelection): Boolean {
return AttachmentSelection.areContentsTheSame(oldItem, newItem)
}
}

View File

@ -1,13 +1,11 @@
package com.simplemobiletools.smsmessenger.adapters package com.simplemobiletools.smsmessenger.adapters
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri
import android.util.Size import android.util.Size
import android.util.TypedValue import android.util.TypedValue
import android.view.Menu import android.view.Menu
@ -39,20 +37,16 @@ import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.* import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.models.* import com.simplemobiletools.smsmessenger.models.*
import kotlinx.android.synthetic.main.item_attachment_image.view.* import kotlinx.android.synthetic.main.item_attachment_image.view.*
import kotlinx.android.synthetic.main.item_attachment_vcard.view.*
import kotlinx.android.synthetic.main.item_received_message.view.* import kotlinx.android.synthetic.main.item_received_message.view.*
import kotlinx.android.synthetic.main.item_received_message.view.thread_mesage_attachments_holder import kotlinx.android.synthetic.main.item_received_message.view.thread_mesage_attachments_holder
import kotlinx.android.synthetic.main.item_received_message.view.thread_message_body import kotlinx.android.synthetic.main.item_received_message.view.thread_message_body
import kotlinx.android.synthetic.main.item_received_message.view.thread_message_holder 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_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_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_date_time.view.*
import kotlinx.android.synthetic.main.item_thread_error.view.* import kotlinx.android.synthetic.main.item_thread_error.view.*
import kotlinx.android.synthetic.main.item_thread_sending.view.* import kotlinx.android.synthetic.main.item_thread_sending.view.*
import kotlinx.android.synthetic.main.item_thread_success.view.* import kotlinx.android.synthetic.main.item_thread_success.view.*
import java.util.*
class ThreadAdapter( class ThreadAdapter(
activity: SimpleActivity, var messages: ArrayList<ThreadItem>, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit, val onThreadIdUpdate: (Long) -> Unit activity: SimpleActivity, var messages: ArrayList<ThreadItem>, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit, val onThreadIdUpdate: (Long) -> Unit
@ -288,12 +282,10 @@ class ThreadAdapter(
if (message.attachment?.attachments?.isNotEmpty() == true) { if (message.attachment?.attachments?.isNotEmpty() == true) {
for (attachment in message.attachment.attachments) { for (attachment in message.attachment.attachments) {
val mimetype = attachment.mimetype val mimetype = attachment.mimetype
if (mimetype.isImageMimeType() || mimetype.startsWith("video/")) { when {
setupImageView(holder, view, message, attachment) mimetype.isImageMimeType() || mimetype.isVideoMimeType() -> setupImageView(holder, view, message, attachment)
} else if (mimetype.isVCardMimeType()) { mimetype.isVCardMimeType() -> setupVCardView(holder, view, message, attachment)
setupVCardView(holder, view, message, attachment) else -> setupFileView(holder, view, message, attachment)
} else {
setupFileView(holder, view, message, attachment)
} }
thread_message_play_outline.beVisibleIf(mimetype.startsWith("video/")) thread_message_play_outline.beVisibleIf(mimetype.startsWith("video/"))
@ -401,7 +393,7 @@ class ThreadAdapter(
if (actModeCallback.isSelectable) { if (actModeCallback.isSelectable) {
holder.viewClicked(message) holder.viewClicked(message)
} else { } else {
launchViewIntent(uri, mimetype, attachment.filename) activity.launchViewIntent(uri, mimetype, attachment.filename)
} }
} }
imageView.setOnLongClickListener { imageView.setOnLongClickListener {
@ -415,34 +407,10 @@ class ThreadAdapter(
val uri = attachment.getUri() val uri = attachment.getUri()
parent.apply { parent.apply {
val vCardView = layoutInflater.inflate(R.layout.item_attachment_vcard, null).apply { val vCardView = layoutInflater.inflate(R.layout.item_attachment_vcard, null).apply {
background.applyColorFilter(backgroundColor.getContrastColor()) setupVCardPreview(
vcard_title.setTextColor(textColor) activity = activity,
vcard_subtitle.setTextColor(textColor) uri = uri,
view_contact_details.setTextColor(properPrimaryColor) onClick = {
}
thread_mesage_attachments_holder.addView(vCardView)
parseVCardFromUri(context, uri) { vCards ->
val title = vCards.firstOrNull()?.parseNameFromVCard()
val imageIcon = if (title != null) {
SimpleContactsHelper(context).getContactLetterIcon(title)
} else {
null
}
activity.runOnUiThread {
vCardView.apply {
vcard_title.text = title
vcard_photo.setImageBitmap(imageIcon)
if (vCards.size > 1) {
vcard_subtitle.beVisible()
val quantity = vCards.size - 1
vcard_subtitle.text = resources.getQuantityString(R.plurals.and_other_contacts, quantity, quantity)
} else {
vcard_subtitle.beGone()
}
setOnClickListener {
if (actModeCallback.isSelectable) { if (actModeCallback.isSelectable) {
holder.viewClicked(message) holder.viewClicked(message)
} else { } else {
@ -451,14 +419,11 @@ class ThreadAdapter(
} }
context.startActivity(intent) context.startActivity(intent)
} }
},
onLongClick = { holder.viewLongClicked() }
)
} }
setOnLongClickListener { thread_mesage_attachments_holder.addView(vCardView)
holder.viewLongClicked()
true
}
}
}
}
} }
} }
@ -466,73 +431,22 @@ class ThreadAdapter(
val mimetype = attachment.mimetype val mimetype = attachment.mimetype
val uri = attachment.getUri() val uri = attachment.getUri()
parent.apply { parent.apply {
if (message.isReceivedMessage()) { val attachmentView = layoutInflater.inflate(R.layout.item_attachment_document, null).apply {
val attachmentView = layoutInflater.inflate(R.layout.item_received_unknown_attachment, null).apply { setupDocumentPreview(
thread_received_attachment_label.apply { uri = uri,
if (attachment.filename.isNotEmpty()) { title = attachment.filename,
thread_received_attachment_label.text = attachment.filename mimeType = attachment.mimetype,
} onClick = {
setTextColor(textColor)
setOnClickListener {
if (actModeCallback.isSelectable) { if (actModeCallback.isSelectable) {
holder.viewClicked(message) holder.viewClicked(message)
} else { } else {
launchViewIntent(uri, mimetype, attachment.filename) activity.launchViewIntent(uri, mimetype, attachment.filename)
}
}
setOnLongClickListener {
holder.viewLongClicked()
true
}
} }
},
onLongClick = { holder.viewLongClicked() },
)
} }
thread_mesage_attachments_holder.addView(attachmentView) thread_mesage_attachments_holder.addView(attachmentView)
} 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)
}
} }
} }

View File

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

View File

@ -35,6 +35,7 @@ import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.ThreadActivity import com.simplemobiletools.smsmessenger.activities.ThreadActivity
import com.simplemobiletools.smsmessenger.databases.MessagesDatabase import com.simplemobiletools.smsmessenger.databases.MessagesDatabase
import com.simplemobiletools.smsmessenger.helpers.* import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.helpers.AttachmentUtils.parseAttachmentNames
import com.simplemobiletools.smsmessenger.interfaces.AttachmentsDao import com.simplemobiletools.smsmessenger.interfaces.AttachmentsDao
import com.simplemobiletools.smsmessenger.interfaces.ConversationsDao import com.simplemobiletools.smsmessenger.interfaces.ConversationsDao
import com.simplemobiletools.smsmessenger.interfaces.MessageAttachmentsDao import com.simplemobiletools.smsmessenger.interfaces.MessageAttachmentsDao
@ -307,7 +308,8 @@ fun Context.getMmsAttachment(id: Long, getImageResolutions: Boolean): MessageAtt
val selectionArgs = arrayOf(id.toString()) val selectionArgs = arrayOf(id.toString())
val messageAttachment = MessageAttachment(id, "", arrayListOf()) val messageAttachment = MessageAttachment(id, "", arrayListOf())
var attachmentName = "" var attachmentNames: List<String>? = null
var attachmentCount = 0
queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor -> queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val partId = cursor.getLongValue(Mms._ID) val partId = cursor.getLongValue(Mms._ID)
val mimetype = cursor.getStringValue(Mms.Part.CONTENT_TYPE) 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, "") val attachment = Attachment(partId, id, fileUri.toString(), mimetype, width, height, "")
messageAttachment.attachments.add(attachment) messageAttachment.attachments.add(attachment)
} else if (mimetype != "application/smil") { } 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) val attachment = Attachment(partId, id, Uri.withAppendedPath(uri, partId.toString()).toString(), mimetype, 0, 0, attachmentName)
messageAttachment.attachments.add(attachment) messageAttachment.attachments.add(attachment)
attachmentCount++
} else { } else {
val text = cursor.getStringValue(Mms.Part.TEXT) val text = cursor.getStringValue(Mms.Part.TEXT)
val cutName = text.substringAfter("ref src=\"").substringBefore("\"") attachmentNames = parseAttachmentNames(text)
if (cutName.isNotEmpty()) {
attachmentName = cutName
}
} }
} }
@ -1064,3 +1065,5 @@ fun Context.clearExpiredScheduledMessages(threadId: Long, messagesToDelete: List
return return
} }
} }
fun Context.getDefaultKeyboardHeight() = resources.getDimensionPixelSize(R.dimen.default_keyboard_height)

View File

@ -15,7 +15,35 @@ fun String.isImageMimeType(): Boolean {
return lowercase().startsWith("image") return lowercase().startsWith("image")
} }
fun String.isGifMimeType(): Boolean {
return lowercase().endsWith("gif")
}
fun String.isVideoMimeType(): Boolean {
return lowercase().startsWith("video")
}
fun String.isVCardMimeType(): Boolean { fun String.isVCardMimeType(): Boolean {
val lowercase = lowercase() val lowercase = lowercase()
return lowercase.endsWith("x-vcard") || lowercase.endsWith("vcard") 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"
}

View File

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

View File

@ -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
}

View File

@ -0,0 +1,74 @@
package com.simplemobiletools.smsmessenger.helpers
import android.util.Xml
import org.xmlpull.v1.XmlPullParser
object AttachmentUtils {
fun parseAttachmentNames(text: String): List<String> {
val parser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
parser.setInput(text.reader())
parser.nextTag()
return readSmil(parser)
}
private fun readSmil(parser: XmlPullParser): List<String> {
parser.require(XmlPullParser.START_TAG, null, "smil")
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.eventType != XmlPullParser.START_TAG) {
continue
}
if (parser.name == "body") {
return readBody(parser)
} else {
skip(parser)
}
}
return emptyList()
}
private fun readBody(parser: XmlPullParser): List<String> {
val names = mutableListOf<String>()
parser.require(XmlPullParser.START_TAG, null, "body")
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.eventType != XmlPullParser.START_TAG) {
continue
}
if (parser.name == "par") {
parser.require(XmlPullParser.START_TAG, null, "par")
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.eventType != XmlPullParser.START_TAG) {
continue
}
if (parser.name == "ref") {
val value = parser.getAttributeValue(null, "src")
names.add(value)
parser.nextTag()
}
}
} else {
skip(parser)
}
}
return names
}
private fun skip(parser: XmlPullParser) {
if (parser.eventType != XmlPullParser.START_TAG) {
throw IllegalStateException()
}
var depth = 1
while (depth != 0) {
when (parser.next()) {
XmlPullParser.END_TAG -> depth--
XmlPullParser.START_TAG -> depth++
}
}
}
}

View File

@ -2,6 +2,7 @@ package com.simplemobiletools.smsmessenger.helpers
import android.content.Context import android.content.Context
import com.simplemobiletools.commons.helpers.BaseConfig import com.simplemobiletools.commons.helpers.BaseConfig
import com.simplemobiletools.smsmessenger.extensions.getDefaultKeyboardHeight
import com.simplemobiletools.smsmessenger.models.Conversation import com.simplemobiletools.smsmessenger.models.Conversation
class Config(context: Context) : BaseConfig(context) { class Config(context: Context) : BaseConfig(context) {
@ -86,4 +87,8 @@ class Config(context: Context) : BaseConfig(context) {
var wasDbCleared: Boolean var wasDbCleared: Boolean
get() = prefs.getBoolean(WAS_DB_CLEARED, false) get() = prefs.getBoolean(WAS_DB_CLEARED, false)
set(wasDbCleared) = prefs.edit().putBoolean(WAS_DB_CLEARED, wasDbCleared).apply() set(wasDbCleared) = prefs.edit().putBoolean(WAS_DB_CLEARED, wasDbCleared).apply()
var keyboardHeight: Int
get() = prefs.getInt(SOFT_KEYBOARD_HEIGHT, context.getDefaultKeyboardHeight())
set(keyboardHeight) = prefs.edit().putInt(SOFT_KEYBOARD_HEIGHT, keyboardHeight).apply()
} }

View File

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

View File

@ -0,0 +1,449 @@
package com.simplemobiletools.smsmessenger.helpers
import android.content.Context
import android.net.Uri
import android.provider.ContactsContract
import android.provider.ContactsContract.CommonDataKinds.*
import android.provider.ContactsContract.Data
import android.text.TextUtils
import android.util.SparseArray
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.models.PhoneNumber
import com.simplemobiletools.commons.models.contacts.*
import com.simplemobiletools.commons.models.contacts.Email
import com.simplemobiletools.commons.models.contacts.Event
import com.simplemobiletools.commons.models.contacts.Organization
import com.simplemobiletools.commons.overloads.times
// based on the ContactsHelper from Simple-Contacts
class ContactsHelper(val context: Context) {
private var displayContactSources = ArrayList<String>()
fun getContactFromUri(uri: Uri): Contact? {
val key = getLookupKeyFromUri(uri) ?: return null
return getContactWithLookupKey(key)
}
private fun getLookupKeyFromUri(lookupUri: Uri): String? {
val projection = arrayOf(ContactsContract.Contacts.LOOKUP_KEY)
val cursor = context.contentResolver.query(lookupUri, projection, null, null, null)
cursor?.use {
if (cursor.moveToFirst()) {
return cursor.getStringValue(ContactsContract.Contacts.LOOKUP_KEY)
}
}
return null
}
private fun getContactWithLookupKey(key: String): Contact? {
val selection = "(${Data.MIMETYPE} = ? OR ${Data.MIMETYPE} = ?) AND ${Data.LOOKUP_KEY} = ?"
val selectionArgs = arrayOf(StructuredName.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE, key)
return parseContactCursor(selection, selectionArgs)
}
private fun parseContactCursor(selection: String, selectionArgs: Array<String>): Contact? {
val storedGroups = getDeviceStoredGroups()
val uri = Data.CONTENT_URI
val projection = getContactProjection()
val cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
cursor?.use {
if (cursor.moveToFirst()) {
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
var prefix = ""
var firstName = ""
var middleName = ""
var surname = ""
var suffix = ""
val mimetype = cursor.getStringValue(Data.MIMETYPE)
// ignore names at Organization type contacts
if (mimetype == StructuredName.CONTENT_ITEM_TYPE) {
prefix = cursor.getStringValue(StructuredName.PREFIX) ?: ""
firstName = cursor.getStringValue(StructuredName.GIVEN_NAME) ?: ""
middleName = cursor.getStringValue(StructuredName.MIDDLE_NAME) ?: ""
surname = cursor.getStringValue(StructuredName.FAMILY_NAME) ?: ""
suffix = cursor.getStringValue(StructuredName.SUFFIX) ?: ""
}
val nickname = getNicknames(id)[id] ?: ""
val photoUri = cursor.getStringValue(Phone.PHOTO_URI) ?: ""
val number = getPhoneNumbers(id)[id] ?: ArrayList()
val emails = getEmails(id)[id] ?: ArrayList()
val addresses = getAddresses(id)[id] ?: ArrayList()
val events = getEvents(id)[id] ?: ArrayList()
val notes = getNotes(id)[id] ?: ""
val accountName = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_NAME) ?: ""
val starred = cursor.getIntValue(StructuredName.STARRED)
val ringtone = cursor.getStringValue(StructuredName.CUSTOM_RINGTONE)
val contactId = cursor.getIntValue(Data.CONTACT_ID)
val groups = getContactGroups(storedGroups, contactId)[contactId] ?: ArrayList()
val thumbnailUri = cursor.getStringValue(StructuredName.PHOTO_THUMBNAIL_URI) ?: ""
val organization = getOrganizations(id)[id] ?: Organization("", "")
val websites = getWebsites(id)[id] ?: ArrayList()
val ims = getIMs(id)[id] ?: ArrayList()
return Contact(
id, prefix, firstName, middleName, surname, suffix, nickname, photoUri, number, emails, addresses, events,
accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites, ims, mimetype, ringtone
)
}
}
return null
}
private fun getPhoneNumbers(contactId: Int? = null): SparseArray<ArrayList<PhoneNumber>> {
val phoneNumbers = SparseArray<ArrayList<PhoneNumber>>()
val uri = Phone.CONTENT_URI
val projection = arrayOf(
Data.RAW_CONTACT_ID,
Phone.NUMBER,
Phone.NORMALIZED_NUMBER,
Phone.TYPE,
Phone.LABEL,
Phone.IS_PRIMARY
)
val selection = if (contactId == null) getSourcesSelection() else "${Data.RAW_CONTACT_ID} = ?"
val selectionArgs = if (contactId == null) getSourcesSelectionArgs() else arrayOf(contactId.toString())
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
val number = cursor.getStringValue(Phone.NUMBER) ?: return@queryCursor
val normalizedNumber = cursor.getStringValue(Phone.NORMALIZED_NUMBER) ?: number.normalizePhoneNumber()
val type = cursor.getIntValue(Phone.TYPE)
val label = cursor.getStringValue(Phone.LABEL) ?: ""
val isPrimary = cursor.getIntValue(Phone.IS_PRIMARY) != 0
if (phoneNumbers[id] == null) {
phoneNumbers.put(id, ArrayList())
}
val phoneNumber = PhoneNumber(number, type, label, normalizedNumber, isPrimary)
phoneNumbers[id].add(phoneNumber)
}
return phoneNumbers
}
private fun getNicknames(contactId: Int? = null): SparseArray<String> {
val nicknames = SparseArray<String>()
val uri = Data.CONTENT_URI
val projection = arrayOf(
Data.RAW_CONTACT_ID,
Nickname.NAME
)
val selection = getSourcesSelection(true, contactId != null)
val selectionArgs = getSourcesSelectionArgs(Nickname.CONTENT_ITEM_TYPE, contactId)
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
val nickname = cursor.getStringValue(Nickname.NAME) ?: return@queryCursor
nicknames.put(id, nickname)
}
return nicknames
}
private fun getEmails(contactId: Int? = null): SparseArray<ArrayList<Email>> {
val emails = SparseArray<ArrayList<Email>>()
val uri = ContactsContract.CommonDataKinds.Email.CONTENT_URI
val projection = arrayOf(
Data.RAW_CONTACT_ID,
ContactsContract.CommonDataKinds.Email.DATA,
ContactsContract.CommonDataKinds.Email.TYPE,
ContactsContract.CommonDataKinds.Email.LABEL
)
val selection = if (contactId == null) getSourcesSelection() else "${Data.RAW_CONTACT_ID} = ?"
val selectionArgs = if (contactId == null) getSourcesSelectionArgs() else arrayOf(contactId.toString())
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
val email = cursor.getStringValue(ContactsContract.CommonDataKinds.Email.DATA) ?: return@queryCursor
val type = cursor.getIntValue(ContactsContract.CommonDataKinds.Email.TYPE)
val label = cursor.getStringValue(ContactsContract.CommonDataKinds.Email.LABEL) ?: ""
if (emails[id] == null) {
emails.put(id, ArrayList())
}
emails[id]!!.add(Email(email, type, label))
}
return emails
}
private fun getAddresses(contactId: Int? = null): SparseArray<ArrayList<Address>> {
val addresses = SparseArray<ArrayList<Address>>()
val uri = StructuredPostal.CONTENT_URI
val projection = arrayOf(
Data.RAW_CONTACT_ID,
StructuredPostal.FORMATTED_ADDRESS,
StructuredPostal.TYPE,
StructuredPostal.LABEL
)
val selection = if (contactId == null) getSourcesSelection() else "${Data.RAW_CONTACT_ID} = ?"
val selectionArgs = if (contactId == null) getSourcesSelectionArgs() else arrayOf(contactId.toString())
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
val address = cursor.getStringValue(StructuredPostal.FORMATTED_ADDRESS) ?: return@queryCursor
val type = cursor.getIntValue(StructuredPostal.TYPE)
val label = cursor.getStringValue(StructuredPostal.LABEL) ?: ""
if (addresses[id] == null) {
addresses.put(id, ArrayList())
}
addresses[id]!!.add(Address(address, type, label))
}
return addresses
}
private fun getIMs(contactId: Int? = null): SparseArray<ArrayList<IM>> {
val IMs = SparseArray<ArrayList<IM>>()
val uri = Data.CONTENT_URI
val projection = arrayOf(
Data.RAW_CONTACT_ID,
Im.DATA,
Im.PROTOCOL,
Im.CUSTOM_PROTOCOL
)
val selection = getSourcesSelection(true, contactId != null)
val selectionArgs = getSourcesSelectionArgs(Im.CONTENT_ITEM_TYPE, contactId)
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
val IM = cursor.getStringValue(Im.DATA) ?: return@queryCursor
val type = cursor.getIntValue(Im.PROTOCOL)
val label = cursor.getStringValue(Im.CUSTOM_PROTOCOL) ?: ""
if (IMs[id] == null) {
IMs.put(id, ArrayList())
}
IMs[id]!!.add(IM(IM, type, label))
}
return IMs
}
private fun getEvents(contactId: Int? = null): SparseArray<ArrayList<Event>> {
val events = SparseArray<ArrayList<Event>>()
val uri = Data.CONTENT_URI
val projection = arrayOf(
Data.RAW_CONTACT_ID,
ContactsContract.CommonDataKinds.Event.START_DATE,
ContactsContract.CommonDataKinds.Event.TYPE
)
val selection = getSourcesSelection(true, contactId != null)
val selectionArgs = getSourcesSelectionArgs(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE, contactId)
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
val startDate = cursor.getStringValue(ContactsContract.CommonDataKinds.Event.START_DATE) ?: return@queryCursor
val type = cursor.getIntValue(ContactsContract.CommonDataKinds.Event.TYPE)
if (events[id] == null) {
events.put(id, ArrayList())
}
events[id]!!.add(Event(startDate, type))
}
return events
}
private fun getNotes(contactId: Int? = null): SparseArray<String> {
val notes = SparseArray<String>()
val uri = Data.CONTENT_URI
val projection = arrayOf(
Data.RAW_CONTACT_ID,
Note.NOTE
)
val selection = getSourcesSelection(true, contactId != null)
val selectionArgs = getSourcesSelectionArgs(Note.CONTENT_ITEM_TYPE, contactId)
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
val note = cursor.getStringValue(Note.NOTE) ?: return@queryCursor
notes.put(id, note)
}
return notes
}
private fun getOrganizations(contactId: Int? = null): SparseArray<Organization> {
val organizations = SparseArray<Organization>()
val uri = Data.CONTENT_URI
val projection = arrayOf(
Data.RAW_CONTACT_ID,
ContactsContract.CommonDataKinds.Organization.COMPANY,
ContactsContract.CommonDataKinds.Organization.TITLE
)
val selection = getSourcesSelection(true, contactId != null)
val selectionArgs = getSourcesSelectionArgs(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE, contactId)
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
val company = cursor.getStringValue(ContactsContract.CommonDataKinds.Organization.COMPANY) ?: ""
val title = cursor.getStringValue(ContactsContract.CommonDataKinds.Organization.TITLE) ?: ""
if (company.isEmpty() && title.isEmpty()) {
return@queryCursor
}
val organization = Organization(company, title)
organizations.put(id, organization)
}
return organizations
}
private fun getWebsites(contactId: Int? = null): SparseArray<ArrayList<String>> {
val websites = SparseArray<ArrayList<String>>()
val uri = Data.CONTENT_URI
val projection = arrayOf(
Data.RAW_CONTACT_ID,
Website.URL
)
val selection = getSourcesSelection(true, contactId != null)
val selectionArgs = getSourcesSelectionArgs(Website.CONTENT_ITEM_TYPE, contactId)
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
val url = cursor.getStringValue(Website.URL) ?: return@queryCursor
if (websites[id] == null) {
websites.put(id, ArrayList())
}
websites[id]!!.add(url)
}
return websites
}
private fun getDeviceStoredGroups(): java.util.ArrayList<Group> {
val groups = java.util.ArrayList<Group>()
val uri = ContactsContract.Groups.CONTENT_URI
val projection = arrayOf(
ContactsContract.Groups._ID,
ContactsContract.Groups.TITLE,
ContactsContract.Groups.SYSTEM_ID
)
val selection = "${ContactsContract.Groups.AUTO_ADD} = ? AND ${ContactsContract.Groups.FAVORITES} = ?"
val selectionArgs = arrayOf("0", "0")
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getLongValue(ContactsContract.Groups._ID)
val title = cursor.getStringValue(ContactsContract.Groups.TITLE) ?: return@queryCursor
val systemId = cursor.getStringValue(ContactsContract.Groups.SYSTEM_ID)
if (groups.map { it.title }.contains(title) && systemId != null) {
return@queryCursor
}
groups.add(Group(id, title))
}
return groups
}
private fun getContactGroups(storedGroups: ArrayList<Group>, contactId: Int? = null): SparseArray<ArrayList<Group>> {
val groups = SparseArray<ArrayList<Group>>()
val uri = Data.CONTENT_URI
val projection = arrayOf(
Data.CONTACT_ID,
Data.DATA1
)
val selection = getSourcesSelection(true, contactId != null, false)
val selectionArgs = getSourcesSelectionArgs(GroupMembership.CONTENT_ITEM_TYPE, contactId)
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.CONTACT_ID)
val newRowId = cursor.getLongValue(Data.DATA1)
val groupTitle = storedGroups.firstOrNull { it.id == newRowId }?.title ?: return@queryCursor
val group = Group(newRowId, groupTitle)
if (groups[id] == null) {
groups.put(id, ArrayList())
}
groups[id]!!.add(group)
}
return groups
}
private fun getQuestionMarks() = ("?," * displayContactSources.filter { it.isNotEmpty() }.size).trimEnd(',')
private fun getSourcesSelection(addMimeType: Boolean = false, addContactId: Boolean = false, useRawContactId: Boolean = true): String {
val strings = ArrayList<String>()
if (addMimeType) {
strings.add("${Data.MIMETYPE} = ?")
}
if (addContactId) {
strings.add("${if (useRawContactId) Data.RAW_CONTACT_ID else Data.CONTACT_ID} = ?")
} else {
// sometimes local device storage has null account_name, handle it properly
val accountNameString = StringBuilder()
if (displayContactSources.contains("")) {
accountNameString.append("(")
}
accountNameString.append("${ContactsContract.RawContacts.ACCOUNT_NAME} IN (${getQuestionMarks()})")
if (displayContactSources.contains("")) {
accountNameString.append(" OR ${ContactsContract.RawContacts.ACCOUNT_NAME} IS NULL)")
}
strings.add(accountNameString.toString())
}
return TextUtils.join(" AND ", strings)
}
private fun getSourcesSelectionArgs(mimetype: String? = null, contactId: Int? = null): Array<String> {
val args = ArrayList<String>()
if (mimetype != null) {
args.add(mimetype)
}
if (contactId != null) {
args.add(contactId.toString())
} else {
args.addAll(displayContactSources.filter { it.isNotEmpty() })
}
return args.toTypedArray()
}
private fun getContactProjection() = arrayOf(
Data.MIMETYPE,
Data.CONTACT_ID,
Data.RAW_CONTACT_ID,
StructuredName.PREFIX,
StructuredName.GIVEN_NAME,
StructuredName.MIDDLE_NAME,
StructuredName.FAMILY_NAME,
StructuredName.SUFFIX,
StructuredName.PHOTO_URI,
StructuredName.PHOTO_THUMBNAIL_URI,
StructuredName.STARRED,
StructuredName.CUSTOM_RINGTONE,
ContactsContract.RawContacts.ACCOUNT_NAME,
ContactsContract.RawContacts.ACCOUNT_TYPE
)
}

View File

@ -4,7 +4,6 @@ import android.app.AlarmManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import androidx.core.app.AlarmManagerCompat import androidx.core.app.AlarmManagerCompat
@ -15,6 +14,8 @@ import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.helpers.isMarshmallowPlus import com.simplemobiletools.commons.helpers.isMarshmallowPlus
import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.extensions.config 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.models.Message
import com.simplemobiletools.smsmessenger.receivers.ScheduledMessageReceiver import com.simplemobiletools.smsmessenger.receivers.ScheduledMessageReceiver
import com.simplemobiletools.smsmessenger.receivers.SmsStatusDeliveredReceiver import com.simplemobiletools.smsmessenger.receivers.SmsStatusDeliveredReceiver
@ -34,7 +35,7 @@ fun Context.getSendMessageSettings(): Settings {
return settings return settings
} }
fun Context.sendMessage(text: String, addresses: List<String>, subscriptionId: Int?, attachments: List<Uri>) { fun Context.sendMessage(text: String, addresses: List<String>, subscriptionId: Int?, attachments: List<Attachment>) {
val settings = getSendMessageSettings() val settings = getSendMessageSettings()
if (subscriptionId != null) { if (subscriptionId != null) {
settings.subscriptionId = subscriptionId settings.subscriptionId = subscriptionId
@ -44,11 +45,19 @@ fun Context.sendMessage(text: String, addresses: List<String>, subscriptionId: I
val message = com.klinker.android.send_message.Message(text, addresses.toTypedArray()) val message = com.klinker.android.send_message.Message(text, addresses.toTypedArray())
if (attachments.isNotEmpty()) { if (attachments.isNotEmpty()) {
for (uri in attachments) { for (attachment in attachments) {
try { try {
val byteArray = contentResolver.openInputStream(uri)?.readBytes() ?: continue val uri = attachment.getUri()
val mimeType = contentResolver.getType(uri) ?: continue contentResolver.openInputStream(uri)?.use {
message.addMedia(byteArray, mimeType) 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) { } catch (e: Exception) {
showErrorToast(e) showErrorToast(e)
} catch (e: Error) { } catch (e: Error) {

View File

@ -8,7 +8,12 @@ import ezvcard.VCard
fun parseVCardFromUri(context: Context, uri: Uri, callback: (vCards: List<VCard>) -> Unit) { fun parseVCardFromUri(context: Context, uri: Uri, callback: (vCards: List<VCard>) -> Unit) {
ensureBackgroundThread { 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() val vCards = Ezvcard.parse(inputStream).all()
callback(vCards) callback(vCards)
} }

View File

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

View File

@ -10,6 +10,7 @@ import com.simplemobiletools.commons.extensions.normalizePhoneNumber
import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.extensions.* import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
// more info at https://github.com/klinker41/android-smsmms // more info at https://github.com/klinker41/android-smsmms
class MmsReceiver : com.klinker.android.send_message.MmsReceivedReceiver() { class MmsReceiver : com.klinker.android.send_message.MmsReceivedReceiver() {
@ -42,6 +43,7 @@ class MmsReceiver : com.klinker.android.send_message.MmsReceivedReceiver() {
ensureBackgroundThread { ensureBackgroundThread {
context.conversationsDB.insertOrUpdate(conversation) context.conversationsDB.insertOrUpdate(conversation)
context.updateUnreadCountBadge(context.conversationsDB.getUnreadConversations()) context.updateUnreadCountBadge(context.conversationsDB.getUnreadConversations())
refreshMessages()
} }
} }
} }

View File

@ -40,7 +40,7 @@ class ScheduledMessageReceiver : BroadcastReceiver() {
} }
val addresses = message.participants.getAddresses() val addresses = message.participants.getAddresses()
val attachments = message.attachment?.attachments?.mapNotNull { it.getUri() } ?: emptyList() val attachments = message.attachment?.attachments ?: emptyList()
try { try {
context.sendMessage(message.body, addresses, message.subscriptionId, attachments) context.sendMessage(message.body, addresses, message.subscriptionId, attachments)

View File

@ -0,0 +1,3 @@
<vector android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M14 2H6C4.9 2 4.01 2.9 4.01 4L4 20c0 1.1 0.89 2 1.99 2H18c1.1 0 2-0.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/>
</vector>

View File

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

View File

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="@android:color/white" android:pathData="M12 3v10.55C11.41 13.21 10.73 13 10 13c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
</vector>

View File

@ -0,0 +1,3 @@
<vector android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M14 2H6C4.9 2 4.01 2.9 4.01 4L4 20c0 1.1 0.89 2 1.99 2H18c1.1 0 2-0.9 2-2V8l-6-6zm2 11h-3v3.75c0 1.24-1.01 2.25-2.25 2.25S8.5 17.99 8.5 16.75s1.01-2.25 2.25-2.25c0.46 0 0.89 0.14 1.25 0.38V11h4v2zm-3-4V3.5L18.5 9H13z"/>
</vector>

View File

@ -0,0 +1,3 @@
<vector android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20 6h-8l-2-2H4C2.9 4 2.01 4.9 2.01 6L2 18c0 1.1 0.9 2 2 2h16c1.1 0 2-0.9 2-2V8c0-1.1-0.9-2-2-2zm-2 6h-2v2h2v2h-2v2h-2v-2h2v-2h-2v-2h2v-2h-2V8h2v2h2v2z"/>
</vector>

View File

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="1024" android:viewportHeight="1024">
<path android:fillColor="@android:color/white" android:pathData="M597.3 384H832L597.3 149.3V384M298.7 85.3H640l256 256v512a85.3 85.3 0 0 1-85.3 85.3h-512a85.3 85.3 0 0 1-85.3-85.3V170.7a85.3 85.3 0 0 1 85.3-85.3M509 530.8c17.5 38.4 39.7 70 65.3 91.7l17.5 13.7c-37.1 6.8-88.3 18.8-142.5 39.7l-4.7 1.7 21.3-44.4c19.2-37.1 33.3-70.8 43.1-102.4m276.5 162.6c7.7-7.7 11.5-17.5 11.9-28.2 1.3-8.5-0.9-16.6-5.1-23.5-12.4-20.1-44.4-29.4-97.3-29.4l-55 3-37.1-24.7c-26.9-22.2-51.2-61-68.3-109.2l1.7-6c14.1-56.7 27.3-125.4-0.9-153.6a36.4 36.4 0 0 0-26-10.2h-10.2c-15.8 0-29.9 16.6-33.7 32.9-15.8 56.7-6.4 87.9 9.4 139.5v0.4c-10.7 37.5-24.3 81.1-46.1 125l-41 76.8-38 20.9c-51.2 32-75.5 67.8-80.2 90.5-1.7 8.1-0.9 15.4 2.1 23l1.3 2.1 20.5 13.2 18.8 4.7c34.6 0 73.8-40.5 126.7-131l7.7-3c43.9-14.1 98.6-23.9 171.9-32 43.9 21.8 95.6 31.6 128 31.6 18.8 0 31.6-4.7 38.8-12.8m-17.5-30.3l3.8 4.7c-0.4 4.3-1.7 4.7-3.8 5.5h-1.7l-8.1 0.9c-19.6 0-49.9-8.1-81.1-21.8 3.8-4.3 5.5-4.3 9.8-4.3 59.7 0 76.8 10.7 81.1 14.9m-391.2 62.3c-27.7 50.8-52.9 78.9-72.1 85.3 2.1-16.2 21.3-44.4 51.6-72.1l20.5-13.2m128.9-294.8c-9.8-38.4-10.2-69.5-3-87.5l3-5.1 6.4 2.1c7.3 10.2 8.1 23.9 3.8 46.9l-1.3 6.8-6.8 35-2.1 1.7z"/>
</vector>

View File

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

View File

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="@android:color/white" android:pathData="M18 10.48V6c0-1.1-0.9-2-2-2H4C2.9 4 2 4.9 2 6v12c0 1.1 0.9 2 2 2h12c1.1 0 2-0.9 2-2v-4.48l4 3.98v-11l-4 3.98zM5 16l2.38-3.17L9 15l2.62-3.5L15 16H5z"/>
</vector>

View File

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

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size
android:width="0dp"
android:height="@dimen/tiny_margin" />
</shape>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -34,7 +34,9 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_toEndOf="@+id/thread_message_sender_photo" android:layout_toEndOf="@+id/thread_message_sender_photo"
android:orientation="vertical" /> android:divider="@drawable/linear_layout_vertical_divider"
android:orientation="vertical"
android:showDividers="middle" />
<ImageView <ImageView
android:id="@+id/thread_message_play_outline" android:id="@+id/thread_message_play_outline"

View File

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/thread_received_attachment_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/thread_received_attachment_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:background="@drawable/item_received_background"
android:drawableStart="@drawable/ic_attach_file_vector"
android:drawablePadding="@dimen/small_margin"
android:gravity="center_vertical"
android:paddingStart="@dimen/small_margin"
android:paddingTop="@dimen/normal_margin"
android:paddingEnd="@dimen/normal_margin"
android:paddingBottom="@dimen/normal_margin"
android:text="@string/attachment"
android:textSize="@dimen/normal_text_size" />
</RelativeLayout>

View File

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

View File

@ -23,7 +23,9 @@
android:id="@+id/thread_mesage_attachments_holder" android:id="@+id/thread_mesage_attachments_holder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" /> android:divider="@drawable/linear_layout_vertical_divider"
android:orientation="vertical"
android:showDividers="middle" />
<ImageView <ImageView
android:id="@+id/thread_message_play_outline" android:id="@+id/thread_message_play_outline"

View File

@ -0,0 +1,275 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="@dimen/normal_margin">
<androidx.constraintlayout.helper.widget.Flow
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:constraint_referenced_ids="choose_photo,choose_video,take_photo,record_video,record_audio,pick_file,pick_contact,schedule_message"
app:flow_verticalGap="@dimen/small_margin"
app:flow_wrapMode="aligned"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/choose_photo"
android:layout_width="@dimen/attachment_button_width"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:minHeight="@dimen/attachment_button_height"
android:orientation="vertical"
android:paddingHorizontal="@dimen/medium_margin"
android:paddingVertical="@dimen/medium_margin">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/choose_photo_icon"
android:layout_width="@dimen/medium_icon_size"
android:layout_height="@dimen/medium_icon_size"
android:layout_gravity="center"
android:background="@drawable/circle_background"
android:backgroundTint="@color/colorPrimary"
android:padding="@dimen/medium_margin"
android:src="@drawable/ic_image_vector" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/choose_photo_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/medium_margin"
android:text="@string/choose_photo"
android:textAlignment="center"
android:textColor="@color/default_text_color"
android:textSize="@dimen/normal_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/choose_video"
android:layout_width="@dimen/attachment_button_width"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:minHeight="@dimen/attachment_button_height"
android:orientation="vertical"
android:paddingHorizontal="@dimen/medium_margin"
android:paddingVertical="@dimen/medium_margin">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/choose_video_icon"
android:layout_width="@dimen/medium_icon_size"
android:layout_height="@dimen/medium_icon_size"
android:layout_gravity="center"
android:background="@drawable/circle_background"
android:backgroundTint="@color/colorPrimary"
android:padding="@dimen/medium_margin"
android:src="@drawable/ic_video_camera_vector" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/choose_video_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/medium_margin"
android:text="@string/choose_video"
android:textAlignment="center"
android:textColor="@color/default_text_color"
android:textSize="@dimen/normal_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/take_photo"
android:layout_width="@dimen/attachment_button_width"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:minHeight="@dimen/attachment_button_height"
android:orientation="vertical"
android:paddingHorizontal="@dimen/medium_margin"
android:paddingVertical="@dimen/medium_margin">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/take_photo_icon"
android:layout_width="@dimen/medium_icon_size"
android:layout_height="@dimen/medium_icon_size"
android:layout_gravity="center"
android:background="@drawable/circle_background"
android:backgroundTint="@color/colorPrimary"
android:padding="@dimen/medium_margin"
android:src="@drawable/ic_camera_vector" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/take_photo_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/medium_margin"
android:text="@string/take_photo"
android:textAlignment="center"
android:textColor="@color/default_text_color"
android:textSize="@dimen/normal_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/record_video"
android:layout_width="@dimen/attachment_button_width"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:minHeight="@dimen/attachment_button_height"
android:orientation="vertical"
android:paddingHorizontal="@dimen/medium_margin"
android:paddingVertical="@dimen/medium_margin">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/record_video_icon"
android:layout_width="@dimen/medium_icon_size"
android:layout_height="@dimen/medium_icon_size"
android:layout_gravity="center"
android:background="@drawable/circle_background"
android:backgroundTint="@color/colorPrimary"
android:padding="@dimen/medium_margin"
android:src="@drawable/ic_videocam_vector" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/record_video_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/medium_margin"
android:text="@string/record_video"
android:textAlignment="center"
android:textColor="@color/default_text_color"
android:textSize="@dimen/normal_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/record_audio"
android:layout_width="@dimen/attachment_button_width"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:minHeight="@dimen/attachment_button_height"
android:orientation="vertical"
android:paddingHorizontal="@dimen/medium_margin"
android:paddingVertical="@dimen/medium_margin">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/record_audio_icon"
android:layout_width="@dimen/medium_icon_size"
android:layout_height="@dimen/medium_icon_size"
android:layout_gravity="center"
android:background="@drawable/circle_background"
android:backgroundTint="@color/colorPrimary"
android:padding="@dimen/medium_margin"
android:src="@drawable/ic_microphone_vector" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/record_audio_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/medium_margin"
android:text="@string/record_audio"
android:textAlignment="center"
android:textColor="@color/default_text_color"
android:textSize="@dimen/normal_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/pick_file"
android:layout_width="@dimen/attachment_button_width"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:minHeight="@dimen/attachment_button_height"
android:orientation="vertical"
android:paddingHorizontal="@dimen/medium_margin"
android:paddingVertical="@dimen/medium_margin"
android:textColor="@color/default_text_color">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/pick_file_icon"
android:layout_width="@dimen/medium_icon_size"
android:layout_height="@dimen/medium_icon_size"
android:layout_gravity="center"
android:background="@drawable/circle_background"
android:backgroundTint="@color/colorPrimary"
android:padding="@dimen/medium_margin"
android:src="@drawable/ic_document_vector" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/pick_file_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/medium_margin"
android:text="@string/choose_file"
android:textAlignment="center"
android:textColor="@color/default_text_color"
android:textSize="@dimen/normal_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/pick_contact"
android:layout_width="@dimen/attachment_button_width"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:minHeight="@dimen/attachment_button_height"
android:orientation="vertical"
android:paddingHorizontal="@dimen/medium_margin"
android:paddingVertical="@dimen/medium_margin"
android:textColor="@color/default_text_color">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/pick_contact_icon"
android:layout_width="@dimen/medium_icon_size"
android:layout_height="@dimen/medium_icon_size"
android:layout_gravity="center"
android:background="@drawable/circle_background"
android:backgroundTint="@color/colorPrimary"
android:padding="@dimen/medium_margin"
android:src="@drawable/ic_person_vector" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/pick_contact_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/medium_margin"
android:text="@string/choose_contact"
android:textAlignment="center"
android:textColor="@color/default_text_color"
android:textSize="@dimen/normal_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/schedule_message"
android:layout_width="@dimen/attachment_button_width"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:minHeight="@dimen/attachment_button_height"
android:orientation="vertical"
android:paddingHorizontal="@dimen/medium_margin"
android:paddingVertical="@dimen/medium_margin">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/schedule_message_icon"
android:layout_width="@dimen/medium_icon_size"
android:layout_height="@dimen/medium_icon_size"
android:layout_gravity="center"
android:background="@drawable/circle_background"
android:backgroundTint="@color/colorPrimary"
android:padding="@dimen/medium_margin"
android:src="@drawable/ic_clock_vector" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/schedule_message_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/medium_margin"
android:text="@string/schedule_message"
android:textAlignment="center"
android:textColor="@color/default_text_color"
android:textSize="@dimen/normal_text_size" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">ازالة التثبيت</string> <string name="unpin_conversation">ازالة التثبيت</string>
<string name="forward_message">اعادة ارسال</string> <string name="forward_message">اعادة ارسال</string>
<string name="compress_error">غير قادر على ضغط الصورة إلى الحجم المحدد</string> <string name="compress_error">غير قادر على ضغط الصورة إلى الحجم المحدد</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="zero">و %d أخرى</item> <item quantity="zero">و %d أخرى</item>
<item quantity="one">و %d أخرى</item> <item quantity="one">و %d أخرى</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Unpin</string> <string name="unpin_conversation">Unpin</string>
<string name="forward_message">Forward</string> <string name="forward_message">Forward</string>
<string name="compress_error">Unable to compress image to selected size</string> <string name="compress_error">Unable to compress image to selected size</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">and %d other</item> <item quantity="one">and %d other</item>
<item quantity="other">and %d others</item> <item quantity="other">and %d others</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Адмацаваць</string> <string name="unpin_conversation">Адмацаваць</string>
<string name="forward_message">Пераслаць</string> <string name="forward_message">Пераслаць</string>
<string name="compress_error">Немагчыма сціснуць выяву да выбранага памеру</string> <string name="compress_error">Немагчыма сціснуць выяву да выбранага памеру</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">and %d other</item> <item quantity="one">and %d other</item>
<item quantity="few">and %d others</item> <item quantity="few">and %d others</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Откачване</string> <string name="unpin_conversation">Откачване</string>
<string name="forward_message">Препращане</string> <string name="forward_message">Препращане</string>
<string name="compress_error">Невъзможно е да се компресира изображението до избрания размер</string> <string name="compress_error">Невъзможно е да се компресира изображението до избрания размер</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">and %d other</item> <item quantity="one">and %d other</item>
<item quantity="other">and %d others</item> <item quantity="other">and %d others</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">No fixis</string> <string name="unpin_conversation">No fixis</string>
<string name="forward_message">Reenvia</string> <string name="forward_message">Reenvia</string>
<string name="compress_error">No s\'ha pogut comprimir la imatge a la mida seleccionada</string> <string name="compress_error">No s\'ha pogut comprimir la imatge a la mida seleccionada</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">i %d altra</item> <item quantity="one">i %d altra</item>
<item quantity="other">i %d altres</item> <item quantity="other">i %d altres</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Unpin</string> <string name="unpin_conversation">Unpin</string>
<string name="forward_message">Forward</string> <string name="forward_message">Forward</string>
<string name="compress_error">Unable to compress image to selected size</string> <string name="compress_error">Unable to compress image to selected size</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">and %d other</item> <item quantity="one">and %d other</item>
<item quantity="other">and %d others</item> <item quantity="other">and %d others</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Odepnout</string> <string name="unpin_conversation">Odepnout</string>
<string name="forward_message">Přeposlat</string> <string name="forward_message">Přeposlat</string>
<string name="compress_error">Nepodařilo se komprimovat obrázek na požadovanou velikost</string> <string name="compress_error">Nepodařilo se komprimovat obrázek na požadovanou velikost</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">a %d další</item> <item quantity="one">a %d další</item>
<item quantity="few">a %d další</item> <item quantity="few">a %d další</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Frigør</string> <string name="unpin_conversation">Frigør</string>
<string name="forward_message">Fremad</string> <string name="forward_message">Fremad</string>
<string name="compress_error">Billedet kan ikke komprimeres til den valgte størrelse</string> <string name="compress_error">Billedet kan ikke komprimeres til den valgte størrelse</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">and %d other</item> <item quantity="one">and %d other</item>
<item quantity="other">and %d others</item> <item quantity="other">and %d others</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Losheften</string> <string name="unpin_conversation">Losheften</string>
<string name="forward_message">Weiterleiten</string> <string name="forward_message">Weiterleiten</string>
<string name="compress_error">Bild kann nicht auf ausgewählte Größe komprimiert werden</string> <string name="compress_error">Bild kann nicht auf ausgewählte Größe komprimiert werden</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">und %d andere</item> <item quantity="one">und %d andere</item>
<item quantity="other">und %d anderen</item> <item quantity="other">und %d anderen</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Ξεκαρφίτσωμα</string> <string name="unpin_conversation">Ξεκαρφίτσωμα</string>
<string name="forward_message">Προώθηση</string> <string name="forward_message">Προώθηση</string>
<string name="compress_error">Αδυναμία συμπίεσης εικόνας στο επιλεγμένο μέγεθος</string> <string name="compress_error">Αδυναμία συμπίεσης εικόνας στο επιλεγμένο μέγεθος</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">και άλλος %d</item> <item quantity="one">και άλλος %d</item>
<item quantity="other">και άλλοι %d</item> <item quantity="other">και άλλοι %d</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Depingli</string> <string name="unpin_conversation">Depingli</string>
<string name="forward_message">Forward</string> <string name="forward_message">Forward</string>
<string name="compress_error">Unable to compress image to selected size</string> <string name="compress_error">Unable to compress image to selected size</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">and %d other</item> <item quantity="one">and %d other</item>
<item quantity="other">and %d others</item> <item quantity="other">and %d others</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Desanclar</string> <string name="unpin_conversation">Desanclar</string>
<string name="forward_message">Reenviar</string> <string name="forward_message">Reenviar</string>
<string name="compress_error">Incapaz de comprimir la imagen al tamaño seleccionado</string> <string name="compress_error">Incapaz de comprimir la imagen al tamaño seleccionado</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">y %d otro</item> <item quantity="one">y %d otro</item>
<item quantity="many">y %d otros</item> <item quantity="many">y %d otros</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Eemalda kinnitus</string> <string name="unpin_conversation">Eemalda kinnitus</string>
<string name="forward_message">Edasta</string> <string name="forward_message">Edasta</string>
<string name="compress_error">Pildi muutmine valitud suurusesse ei õnnestu</string> <string name="compress_error">Pildi muutmine valitud suurusesse ei õnnestu</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">ja %d muud</item> <item quantity="one">ja %d muud</item>
<item quantity="other">ja %d teised</item> <item quantity="other">ja %d teised</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Poista</string> <string name="unpin_conversation">Poista</string>
<string name="forward_message">Välitä</string> <string name="forward_message">Välitä</string>
<string name="compress_error">Kuvan pakkaaminen valittuun kokoon ei onnistu</string> <string name="compress_error">Kuvan pakkaaminen valittuun kokoon ei onnistu</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">ja %d muu</item> <item quantity="one">ja %d muu</item>
<item quantity="other">ja %d muuta</item> <item quantity="other">ja %d muuta</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Désépingler</string> <string name="unpin_conversation">Désépingler</string>
<string name="forward_message">Transférer</string> <string name="forward_message">Transférer</string>
<string name="compress_error">Impossible de compresser l\'image à la taille sélectionnée</string> <string name="compress_error">Impossible de compresser l\'image à la taille sélectionnée</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">et %d autre</item> <item quantity="one">et %d autre</item>
<item quantity="many">et %d autres</item> <item quantity="many">et %d autres</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Desenganche</string> <string name="unpin_conversation">Desenganche</string>
<string name="forward_message">Adiante</string> <string name="forward_message">Adiante</string>
<string name="compress_error">Non se puido comprimir a imaxe ao tamaño seleccionado</string> <string name="compress_error">Non se puido comprimir a imaxe ao tamaño seleccionado</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">E %d outro</item> <item quantity="one">E %d outro</item>
<item quantity="other">E %d outros</item> <item quantity="other">E %d outros</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Unpin</string> <string name="unpin_conversation">Unpin</string>
<string name="forward_message">Forward</string> <string name="forward_message">Forward</string>
<string name="compress_error">Unable to compress image to selected size</string> <string name="compress_error">Unable to compress image to selected size</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">and %d other</item> <item quantity="one">and %d other</item>
<item quantity="other">and %d others</item> <item quantity="other">and %d others</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Otkvači</string> <string name="unpin_conversation">Otkvači</string>
<string name="forward_message">Proslijedi</string> <string name="forward_message">Proslijedi</string>
<string name="compress_error">Isključi za komprimiranje slike na odabranu veličinu</string> <string name="compress_error">Isključi za komprimiranje slike na odabranu veličinu</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">i još %d druga</item> <item quantity="one">i još %d druga</item>
<item quantity="few">i još %d druge</item> <item quantity="few">i još %d druge</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Kitűzés megszüntetése</string> <string name="unpin_conversation">Kitűzés megszüntetése</string>
<string name="forward_message">Továbbítás</string> <string name="forward_message">Továbbítás</string>
<string name="compress_error">Nem lehet tömöríteni a képet a kiválasztott méretre</string> <string name="compress_error">Nem lehet tömöríteni a képet a kiválasztott méretre</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">és még %d fő</item> <item quantity="one">és még %d fő</item>
<item quantity="other">és még %d fő</item> <item quantity="other">és még %d fő</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Unpin</string> <string name="unpin_conversation">Unpin</string>
<string name="forward_message">Forward</string> <string name="forward_message">Forward</string>
<string name="compress_error">Unable to compress image to selected size</string> <string name="compress_error">Unable to compress image to selected size</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="other">and %d others</item> <item quantity="other">and %d others</item>
</plurals> </plurals>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Rimuovi</string> <string name="unpin_conversation">Rimuovi</string>
<string name="forward_message">Inoltra</string> <string name="forward_message">Inoltra</string>
<string name="compress_error">Impossibile comprimere l\'immagine alla dimensione selezionata</string> <string name="compress_error">Impossibile comprimere l\'immagine alla dimensione selezionata</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">e %d altro</item> <item quantity="one">e %d altro</item>
<item quantity="many">e %d altri</item> <item quantity="many">e %d altri</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">בטל הצמדה</string> <string name="unpin_conversation">בטל הצמדה</string>
<string name="forward_message">התקדם</string> <string name="forward_message">התקדם</string>
<string name="compress_error">לא ניתן לדחוס תמונה לגודל שנבחר</string> <string name="compress_error">לא ניתן לדחוס תמונה לגודל שנבחר</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">and %d other</item> <item quantity="one">and %d other</item>
<item quantity="two">and %d others</item> <item quantity="two">and %d others</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">固定を外す</string> <string name="unpin_conversation">固定を外す</string>
<string name="forward_message">転送</string> <string name="forward_message">転送</string>
<string name="compress_error">選択したサイズに画像を圧縮できません</string> <string name="compress_error">選択したサイズに画像を圧縮できません</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="other">and %d others</item> <item quantity="other">and %d others</item>
</plurals> </plurals>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Atjunkite</string> <string name="unpin_conversation">Atjunkite</string>
<string name="forward_message">Pirmyn</string> <string name="forward_message">Pirmyn</string>
<string name="compress_error">Nepavyksta suspausti vaizdo iki pasirinkto dydžio</string> <string name="compress_error">Nepavyksta suspausti vaizdo iki pasirinkto dydžio</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">and %d other</item> <item quantity="one">and %d other</item>
<item quantity="other">and %d others</item> <item quantity="other">and %d others</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Unpin</string> <string name="unpin_conversation">Unpin</string>
<string name="forward_message">Forward</string> <string name="forward_message">Forward</string>
<string name="compress_error">Unable to compress image to selected size</string> <string name="compress_error">Unable to compress image to selected size</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="zero">and %d other</item> <item quantity="zero">and %d other</item>
<item quantity="one">and %d others</item> <item quantity="one">and %d others</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Unpin</string> <string name="unpin_conversation">Unpin</string>
<string name="forward_message">Forward</string> <string name="forward_message">Forward</string>
<string name="compress_error">Unable to compress image to selected size</string> <string name="compress_error">Unable to compress image to selected size</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">and %d other</item> <item quantity="one">and %d other</item>
<item quantity="other">and %d others</item> <item quantity="other">and %d others</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Unpin</string> <string name="unpin_conversation">Unpin</string>
<string name="forward_message">Forward</string> <string name="forward_message">Forward</string>
<string name="compress_error">Unable to compress image to selected size</string> <string name="compress_error">Unable to compress image to selected size</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">and %d other</item> <item quantity="one">and %d other</item>
<item quantity="other">and %d others</item> <item quantity="other">and %d others</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Løsne</string> <string name="unpin_conversation">Løsne</string>
<string name="forward_message">Videresend</string> <string name="forward_message">Videresend</string>
<string name="compress_error">Unable to compress image to selected size</string> <string name="compress_error">Unable to compress image to selected size</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">and %d other</item> <item quantity="one">and %d other</item>
<item quantity="other">and %d others</item> <item quantity="other">and %d others</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Losmaken</string> <string name="unpin_conversation">Losmaken</string>
<string name="forward_message">Doorsturen</string> <string name="forward_message">Doorsturen</string>
<string name="compress_error">Kon de afbeelding niet comprimeren naar de gekozen grootte</string> <string name="compress_error">Kon de afbeelding niet comprimeren naar de gekozen grootte</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">en %d andere</item> <item quantity="one">en %d andere</item>
<item quantity="other">en %d anderen</item> <item quantity="other">en %d anderen</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Unpin</string> <string name="unpin_conversation">Unpin</string>
<string name="forward_message">اگے</string> <string name="forward_message">اگے</string>
<string name="compress_error">تصویر نوں چݨے ہوۓ اکار وچ سنکُچت کر نہیں سکدی</string> <string name="compress_error">تصویر نوں چݨے ہوۓ اکار وچ سنکُچت کر نہیں سکدی</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">تے %d ہور</item> <item quantity="one">تے %d ہور</item>
<item quantity="other">تے %d ہور</item> <item quantity="other">تے %d ہور</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Odepnij</string> <string name="unpin_conversation">Odepnij</string>
<string name="forward_message">Przekaż dalej</string> <string name="forward_message">Przekaż dalej</string>
<string name="compress_error">Nie udało się skompresować obrazu do wybranego rozmiaru</string> <string name="compress_error">Nie udało się skompresować obrazu do wybranego rozmiaru</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">i %d inny</item> <item quantity="one">i %d inny</item>
<item quantity="few">i %d inne</item> <item quantity="few">i %d inne</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Desfixar</string> <string name="unpin_conversation">Desfixar</string>
<string name="forward_message">Encaminhar</string> <string name="forward_message">Encaminhar</string>
<string name="compress_error">Não pôde comprimir imagem ao tamanho selecionado</string> <string name="compress_error">Não pôde comprimir imagem ao tamanho selecionado</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">and %d other</item> <item quantity="one">and %d other</item>
<item quantity="many">and %d others</item> <item quantity="many">and %d others</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Desafixar</string> <string name="unpin_conversation">Desafixar</string>
<string name="forward_message">Reencaminhar</string> <string name="forward_message">Reencaminhar</string>
<string name="compress_error">Incapaz de comprimir imagem no tamanho selecionado</string> <string name="compress_error">Incapaz de comprimir imagem no tamanho selecionado</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">and %d other</item> <item quantity="one">and %d other</item>
<item quantity="many">and %d others</item> <item quantity="many">and %d others</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Elimină fixarea</string> <string name="unpin_conversation">Elimină fixarea</string>
<string name="forward_message">Redirecţionare</string> <string name="forward_message">Redirecţionare</string>
<string name="compress_error">Nu se poate comprima imaginea la dimensiunea selectată</string> <string name="compress_error">Nu se poate comprima imaginea la dimensiunea selectată</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">și %d alt</item> <item quantity="one">și %d alt</item>
<item quantity="few">și %d altele</item> <item quantity="few">și %d altele</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Открепить</string> <string name="unpin_conversation">Открепить</string>
<string name="forward_message">Переслать</string> <string name="forward_message">Переслать</string>
<string name="compress_error">Невозможно сжать изображение до выбранного размера</string> <string name="compress_error">Невозможно сжать изображение до выбранного размера</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">и ещё %d</item> <item quantity="one">и ещё %d</item>
<item quantity="few">и ещё %d</item> <item quantity="few">и ещё %d</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Odopnúť</string> <string name="unpin_conversation">Odopnúť</string>
<string name="forward_message">Preposlať</string> <string name="forward_message">Preposlať</string>
<string name="compress_error">Nepodarilo sa zmenšiť obrázok na požadovanú veľkosť</string> <string name="compress_error">Nepodarilo sa zmenšiť obrázok na požadovanú veľkosť</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">a %d ďalší</item> <item quantity="one">a %d ďalší</item>
<item quantity="few">a %d ďalší</item> <item quantity="few">a %d ďalší</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Odpni</string> <string name="unpin_conversation">Odpni</string>
<string name="forward_message">Posreduj</string> <string name="forward_message">Posreduj</string>
<string name="compress_error">Slike ni mogoče stisniti na izbrano velikost</string> <string name="compress_error">Slike ni mogoče stisniti na izbrano velikost</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">in %d drug</item> <item quantity="one">in %d drug</item>
<item quantity="two">in %d druga</item> <item quantity="two">in %d druga</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Lossa</string> <string name="unpin_conversation">Lossa</string>
<string name="forward_message">Vidarebefordra</string> <string name="forward_message">Vidarebefordra</string>
<string name="compress_error">Det gick inte att komprimera bilden till den valda storleken</string> <string name="compress_error">Det gick inte att komprimera bilden till den valda storleken</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">och %d annan</item> <item quantity="one">och %d annan</item>
<item quantity="other">och %d andra</item> <item quantity="other">och %d andra</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">பின் நீக்கு</string> <string name="unpin_conversation">பின் நீக்கு</string>
<string name="forward_message">முன்னோக்கி</string> <string name="forward_message">முன்னோக்கி</string>
<string name="compress_error">Unable to compress image to selected size</string> <string name="compress_error">Unable to compress image to selected size</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">and %d other</item> <item quantity="one">and %d other</item>
<item quantity="other">and %d others</item> <item quantity="other">and %d others</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Unpin</string> <string name="unpin_conversation">Unpin</string>
<string name="forward_message">Forward</string> <string name="forward_message">Forward</string>
<string name="compress_error">Unable to compress image to selected size</string> <string name="compress_error">Unable to compress image to selected size</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="other">and %d others</item> <item quantity="other">and %d others</item>
</plurals> </plurals>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Sabitlemeyi kaldır</string> <string name="unpin_conversation">Sabitlemeyi kaldır</string>
<string name="forward_message">İlet</string> <string name="forward_message">İlet</string>
<string name="compress_error">Resim seçilen boyuta sıkıştırılamıyor</string> <string name="compress_error">Resim seçilen boyuta sıkıştırılamıyor</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">ve %d diğeri</item> <item quantity="one">ve %d diğeri</item>
<item quantity="other">ve %d diğeri</item> <item quantity="other">ve %d diğeri</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Відкріпити</string> <string name="unpin_conversation">Відкріпити</string>
<string name="forward_message">Переслати</string> <string name="forward_message">Переслати</string>
<string name="compress_error">Не вдається стиснути зображення до вибраного розміру</string> <string name="compress_error">Не вдається стиснути зображення до вибраного розміру</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">and %d other</item> <item quantity="one">and %d other</item>
<item quantity="few">and %d others</item> <item quantity="few">and %d others</item>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">取消固定</string> <string name="unpin_conversation">取消固定</string>
<string name="forward_message">转发</string> <string name="forward_message">转发</string>
<string name="compress_error">无法将图像压缩到选定的大小</string> <string name="compress_error">无法将图像压缩到选定的大小</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="other">及 %d 个其他人</item> <item quantity="other">及 %d 个其他人</item>
</plurals> </plurals>

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">取消釘選</string> <string name="unpin_conversation">取消釘選</string>
<string name="forward_message">轉傳</string> <string name="forward_message">轉傳</string>
<string name="compress_error">無法將圖片壓縮至指定大小</string> <string name="compress_error">無法將圖片壓縮至指定大小</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="other">and %d others</item> <item quantity="other">and %d others</item>
</plurals> </plurals>

View File

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

View File

@ -20,7 +20,9 @@
<string name="unpin_conversation">Unpin</string> <string name="unpin_conversation">Unpin</string>
<string name="forward_message">Forward</string> <string name="forward_message">Forward</string>
<string name="compress_error">Unable to compress image to selected size</string> <string name="compress_error">Unable to compress image to selected size</string>
<!-- vCard--> <!-- Attachments -->
<string name="duplicate_item_warning">Duplicate item was not included</string>
<!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">and %d other</item> <item quantity="one">and %d other</item>
<item quantity="other">and %d others</item> <item quantity="other">and %d others</item>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<paths> <paths>
<cache-path name="compressed_files" path="compressed/"/> <cache-path name="compressed_files" path="compressed/"/>
<cache-path name="captured_files" path="captured/"/> <cache-path name="attachment_files" path="attachments/"/>
</paths> </paths>