From 14b2d2ef8b8ac5dec2848ca48a6f60183e1a5c0f Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Tue, 25 Dec 2018 22:44:47 -0500 Subject: [PATCH] Add vcard to message --- .../QKSMS/repository/MessageRepositoryImpl.kt | 11 ++- .../QKSMS/interactor/SendScheduledMessage.kt | 2 +- .../java/com/moez/QKSMS/model/Attachment.kt | 38 +++++--- .../feature/compose/AttachmentAdapter.kt | 40 +++++++- .../QKSMS/feature/compose/ComposeActivity.kt | 1 - .../feature/compose/ComposeActivityModule.kt | 5 +- .../QKSMS/feature/compose/ComposeViewModel.kt | 20 ++-- .../ScheduledMessageAttachmentAdapter.kt | 2 +- .../layout/attachment_contact_list_item.xml | 92 +++++++++++++++++++ ...tem.xml => attachment_image_list_item.xml} | 0 .../src/main/res/layout/compose_activity.xml | 2 +- 11 files changed, 177 insertions(+), 36 deletions(-) create mode 100644 presentation/src/main/res/layout/attachment_contact_list_item.xml rename presentation/src/main/res/layout/{attachment_list_item.xml => attachment_image_list_item.xml} (100%) diff --git a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt index a2f8c460..0c95a25c 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt @@ -235,10 +235,9 @@ class MessageRepositoryImpl @Inject constructor( parts += MMSPart("text", ContentType.TEXT_PLAIN, body.toByteArray()) } - // Add the GIFs as attachments. The app currently can't compress them, which may result - // in a lot of these messages failing to send - // TODO Add support for GIF compression + // Add the GIFs as attachments parts += attachments + .mapNotNull { attachment -> attachment as? Attachment.Image } .filter { attachment -> attachment.isGif(context) } .mapNotNull { attachment -> attachment.getUri() } .map { uri -> ImageUtils.compressGif(context, uri, prefs.mmsSize.get() * 1024) } @@ -247,6 +246,7 @@ class MessageRepositoryImpl @Inject constructor( // Compress the images and add them as attachments var totalImageBytes = 0 parts += attachments + .mapNotNull { attachment -> attachment as? Attachment.Image } .filter { attachment -> !attachment.isGif(context) } .mapNotNull { attachment -> attachment.getUri() } .mapNotNull { uri -> tryOrNull { imageRepository.loadImage(uri) } } @@ -257,6 +257,11 @@ class MessageRepositoryImpl @Inject constructor( } .map { bitmap -> MMSPart("image", ContentType.IMAGE_JPEG, bitmap) } + // Send contacts + parts += attachments + .mapNotNull { attachment -> attachment as? Attachment.Contact } + .map { attachment -> attachment.vCard.toByteArray() } + .map { vCard -> MMSPart("contact", ContentType.TEXT_VCARD, vCard) } val transaction = Transaction(context) transaction.sendNewMessage(subId, threadId, addresses.map(PhoneNumberUtils::stripSeparators), parts, null) diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/SendScheduledMessage.kt b/domain/src/main/java/com/moez/QKSMS/interactor/SendScheduledMessage.kt index 29123b1d..00a3ef09 100644 --- a/domain/src/main/java/com/moez/QKSMS/interactor/SendScheduledMessage.kt +++ b/domain/src/main/java/com/moez/QKSMS/interactor/SendScheduledMessage.kt @@ -47,7 +47,7 @@ class SendScheduledMessage @Inject constructor( } .map { message -> val threadId = TelephonyCompat.getOrCreateThreadId(context, message.recipients) - val attachments = message.attachments.mapNotNull(Uri::parse).map { Attachment(it) } + val attachments = message.attachments.mapNotNull(Uri::parse).map { Attachment.Image(it) } SendMessage.Params(message.subId, threadId, message.recipients, message.body, attachments) } .flatMap(sendMessage::buildObservable) diff --git a/domain/src/main/java/com/moez/QKSMS/model/Attachment.kt b/domain/src/main/java/com/moez/QKSMS/model/Attachment.kt index 6e952f9e..df8d78a3 100644 --- a/domain/src/main/java/com/moez/QKSMS/model/Attachment.kt +++ b/domain/src/main/java/com/moez/QKSMS/model/Attachment.kt @@ -23,22 +23,32 @@ import android.net.Uri import android.os.Build import androidx.core.view.inputmethod.InputContentInfoCompat -data class Attachment(private val uri: Uri? = null, private val inputContent: InputContentInfoCompat? = null) { +sealed class Attachment { - fun getUri(): Uri? { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { - inputContent?.contentUri ?: uri - } else { - uri + data class Image( + private val uri: Uri? = null, + private val inputContent: InputContentInfoCompat? = null + ) : Attachment() { + + fun getUri(): Uri? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + inputContent?.contentUri ?: uri + } else { + uri + } + } + + fun isGif(context: Context): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 && inputContent != null) { + inputContent.description.hasMimeType("image/gif") + } else { + context.contentResolver.getType(uri) == "image/gif" + } } } - fun isGif(context: Context): Boolean { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 && inputContent != null) { - inputContent.description.hasMimeType("image/gif") - } else { - context.contentResolver.getType(uri) == "image/gif" - } - } + data class Contact(val vCard: String) : Attachment() -} \ No newline at end of file +} + +class Attachments(attachments: List): List by attachments diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt index 2a28fafd..ba740ec0 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt @@ -25,22 +25,39 @@ import com.bumptech.glide.Glide import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder +import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.model.Attachment +import ezvcard.Ezvcard +import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.attachment_list_item.view.* +import kotlinx.android.synthetic.main.attachment_contact_list_item.view.* +import kotlinx.android.synthetic.main.attachment_image_list_item.view.* import javax.inject.Inject class AttachmentAdapter @Inject constructor( private val context: Context ) : QkAdapter() { + companion object { + private const val VIEW_TYPE_IMAGE = 0 + private const val VIEW_TYPE_CONTACT = 1 + } + val attachmentDeleted: Subject = PublishSubject.create() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val view = LayoutInflater.from(parent.context).inflate(R.layout.attachment_list_item, parent, false) + val inflater = LayoutInflater.from(parent.context) + val view = when (viewType) { + VIEW_TYPE_IMAGE -> inflater.inflate(R.layout.attachment_image_list_item, parent, false) + .apply { thumbnailBounds.clipToOutline = true } - view.thumbnailBounds.clipToOutline = true + VIEW_TYPE_CONTACT -> inflater.inflate(R.layout.attachment_contact_list_item, parent, false) + + else -> null!! // Impossible + } return QkViewHolder(view).apply { view.setOnClickListener { @@ -54,7 +71,22 @@ class AttachmentAdapter @Inject constructor( val attachment = getItem(position) val view = holder.itemView - Glide.with(context).load(attachment.getUri()).into(view.thumbnail) + when (attachment) { + is Attachment.Image -> Glide.with(context) + .load(attachment.getUri()) + .into(view.thumbnail) + + is Attachment.Contact -> Observable.just(attachment.vCard) + .mapNotNull { vCard -> Ezvcard.parse(vCard).first() } + .subscribeOn(Schedulers.computation()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { vcard -> view.name?.text = vcard.formattedName.value } + } + } + + override fun getItemViewType(position: Int) = when (getItem(position)) { + is Attachment.Image -> VIEW_TYPE_IMAGE + is Attachment.Contact -> VIEW_TYPE_CONTACT } } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt index 16a046dd..aa55ae6c 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt @@ -65,7 +65,6 @@ import java.text.SimpleDateFormat import java.util.* import javax.inject.Inject - class ComposeActivity : QkThemedActivity(), ComposeView { companion object { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt index 5ed27769..51ea25cd 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt @@ -23,6 +23,7 @@ import android.net.Uri import androidx.lifecycle.ViewModel import com.moez.QKSMS.injection.ViewModelKey import com.moez.QKSMS.model.Attachment +import com.moez.QKSMS.model.Attachments import dagger.Module import dagger.Provides import dagger.multibindings.IntoMap @@ -70,11 +71,11 @@ class ComposeActivityModule { @Provides @Named("attachments") - fun provideSharedAttachments(activity: ComposeActivity): List { + fun provideSharedAttachments(activity: ComposeActivity): Attachments { val sharedImages = mutableListOf() activity.intent.getParcelableExtra(Intent.EXTRA_STREAM)?.run(sharedImages::add) activity.intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)?.run(sharedImages::addAll) - return sharedImages.map { Attachment(it) } + return Attachments(sharedImages.map { Attachment.Image(it) }) } @Provides diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index 4b06796c..074c8dd8 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -49,6 +49,7 @@ import com.moez.QKSMS.interactor.SendMessage import com.moez.QKSMS.manager.ActiveConversationManager import com.moez.QKSMS.manager.PermissionManager import com.moez.QKSMS.model.Attachment +import com.moez.QKSMS.model.Attachments import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.model.Message @@ -75,13 +76,12 @@ import java.util.* import javax.inject.Inject import javax.inject.Named - class ComposeViewModel @Inject constructor( @Named("query") private val query: String, @Named("threadId") private val threadId: Long, @Named("address") private val address: String, @Named("text") private val sharedText: String, - @Named("attachments") private val sharedAttachments: List, + @Named("attachments") private val sharedAttachments: Attachments, private val context: Context, private val activeConversationManager: ActiveConversationManager, private val addScheduledMessage: AddScheduledMessage, @@ -506,8 +506,8 @@ class ComposeViewModel @Inject constructor( // A photo was selected Observable.merge( - view.attachmentSelectedIntent.map { uri -> Attachment(uri) }, - view.inputContentIntent.map { inputContent -> Attachment(inputContent = inputContent) }) + view.attachmentSelectedIntent.map { uri -> Attachment.Image(uri) }, + view.inputContentIntent.map { inputContent -> Attachment.Image(inputContent = inputContent) }) .withLatestFrom(attachments) { attachment, attachments -> attachments + attachment } .doOnNext { attachments.onNext(it) } .autoDisposable(view.scope()) @@ -531,12 +531,14 @@ class ComposeViewModel @Inject constructor( // Contact was selected for attachment view.contactSelectedIntent - .map(::vCard) + .map { uri -> Attachment.Contact(getVCard(uri)!!) } + .withLatestFrom(attachments) { attachment, attachments -> attachments + attachment} + .subscribeOn(Schedulers.io()) .autoDisposable(view.scope()) - .subscribe({}, { error -> + .subscribe(attachments::onNext) { error -> context.makeToast(R.string.compose_contact_error) Timber.w(error) - }) + } // Detach a photo view.attachmentDeletedIntent @@ -634,7 +636,7 @@ class ComposeViewModel @Inject constructor( // Scheduling a message state.scheduled != 0L -> { newState { copy(scheduled = 0) } - val uris = attachments.map { it.getUri() }.map { it.toString() } + val uris = attachments.mapNotNull { it as? Attachment.Image }.map { it.getUri() }.map { it.toString() } val params = AddScheduledMessage.Params(state.scheduled, subId, addresses, state.sendAsGroup, body, uris) addScheduledMessage.execute(params) context.makeToast(R.string.compose_scheduled_toast) @@ -697,7 +699,7 @@ class ComposeViewModel @Inject constructor( } - private fun vCard(contactData: Uri): String? { + private fun getVCard(contactData: Uri): String? { val lookupKey = context.contentResolver.query(contactData, null, null, null, null)?.use { cursor -> cursor.moveToFirst() cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAttachmentAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAttachmentAdapter.kt index 1eb9a228..520026b2 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAttachmentAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAttachmentAdapter.kt @@ -25,7 +25,7 @@ import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.util.GlideApp -import kotlinx.android.synthetic.main.attachment_list_item.view.* +import kotlinx.android.synthetic.main.attachment_image_list_item.view.* import javax.inject.Inject class ScheduledMessageAttachmentAdapter @Inject constructor() : QkAdapter() { diff --git a/presentation/src/main/res/layout/attachment_contact_list_item.xml b/presentation/src/main/res/layout/attachment_contact_list_item.xml new file mode 100644 index 00000000..bfb98e81 --- /dev/null +++ b/presentation/src/main/res/layout/attachment_contact_list_item.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/attachment_list_item.xml b/presentation/src/main/res/layout/attachment_image_list_item.xml similarity index 100% rename from presentation/src/main/res/layout/attachment_list_item.xml rename to presentation/src/main/res/layout/attachment_image_list_item.xml diff --git a/presentation/src/main/res/layout/compose_activity.xml b/presentation/src/main/res/layout/compose_activity.xml index f31dcacd..a59ee65b 100644 --- a/presentation/src/main/res/layout/compose_activity.xml +++ b/presentation/src/main/res/layout/compose_activity.xml @@ -255,7 +255,7 @@ app:layout_constraintBottom_toTopOf="@id/message" app:layout_constraintEnd_toEndOf="@id/messageBackground" app:layout_constraintStart_toStartOf="@id/messageBackground" - tools:listitem="@layout/attachment_list_item" /> + tools:listitem="@layout/attachment_image_list_item" />