Skip to content

Commit

Permalink
#763 - Support viewing vCard attachments
Browse files Browse the repository at this point in the history
  • Loading branch information
moezbhatti committed Jun 9, 2018
1 parent 18feee7 commit 69392e1
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 32 deletions.
2 changes: 1 addition & 1 deletion data/src/main/java/util/extensions/MmsPartExtensions.kt
Expand Up @@ -29,4 +29,4 @@ fun MmsPart.isVideo() = ContentType.isVideoType(type)

fun MmsPart.isText() = ContentType.isTextType(type)

fun MmsPart.hasThumbnails() = isImage() || isVideo()
fun MmsPart.isVCard() = ContentType.TEXT_VCARD == type
1 change: 1 addition & 0 deletions presentation/build.gradle
Expand Up @@ -139,6 +139,7 @@ dependencies {
implementation "com.github.chrisbanes:PhotoView:2.0.0"
implementation "com.f2prateek.rx.preferences2:rx-preferences:$rx_preferences_version"
implementation "com.google.android:flexbox:0.3.1"
implementation 'com.googlecode.ez-vcard:ez-vcard:0.10.4'
implementation "com.jakewharton.timber:timber:$timber_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation project(":android-smsmms")
Expand Down
7 changes: 7 additions & 0 deletions presentation/src/main/java/common/Navigator.kt
Expand Up @@ -210,6 +210,13 @@ class Navigator @Inject constructor(private val context: Context, private val no
startActivityExternal(intent)
}

fun saveVcard(uri: Uri) {
val intent = Intent(Intent.ACTION_VIEW)
.setDataAndType(uri, "text/x-vcard")

startActivityExternal(intent)
}

fun showNotificationSettings(threadId: Long = 0) {
val intent = Intent(context, NotificationPrefsActivity::class.java)
intent.putExtra("threadId", threadId)
Expand Down
88 changes: 58 additions & 30 deletions presentation/src/main/java/feature/compose/MessagesAdapter.kt
Expand Up @@ -19,6 +19,7 @@
package feature.compose

import android.animation.ObjectAnimator
import android.content.ContentUris
import android.content.Context
import android.graphics.Typeface
import android.support.v7.widget.RecyclerView
Expand Down Expand Up @@ -47,19 +48,22 @@ import common.util.extensions.setPadding
import common.util.extensions.setTint
import common.util.extensions.setVisible
import common.widget.BubbleImageView.Style.*
import ezvcard.Ezvcard
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.subjects.PublishSubject
import io.reactivex.subjects.Subject
import io.realm.RealmResults
import kotlinx.android.synthetic.main.message_list_item_in.view.*
import kotlinx.android.synthetic.main.mms_preview_list_item.view.*
import kotlinx.android.synthetic.main.mms_vcard_list_item.view.*
import mapper.CursorToPartImpl
import model.Conversation
import model.Message
import model.Recipient
import util.Preferences
import util.SubscriptionUtils
import util.extensions.hasThumbnails
import util.extensions.isImage
import util.extensions.isVCard
import util.extensions.isVideo
import java.util.concurrent.TimeUnit
import javax.inject.Inject
Expand Down Expand Up @@ -112,7 +116,6 @@ class MessagesAdapter @Inject constructor(
notifyDataSetChanged()
}

private val layoutInflater = LayoutInflater.from(context)
private val contactCache = ContactCache()
private val expanded = HashMap<Long, Boolean>()
private val disposables = CompositeDisposable()
Expand Down Expand Up @@ -218,7 +221,7 @@ class MessagesAdapter @Inject constructor(


// Bind the grouping
val media = message.parts.filter { it.hasThumbnails() }
val media = message.parts.filter { it.isImage() || it.isVideo() || it.isVCard() }
view.setPadding(bottom = if (canGroup(message, next)) 0 else 16.dpToPx(context))


Expand All @@ -238,9 +241,10 @@ class MessagesAdapter @Inject constructor(
subject.setSpan(StyleSpan(Typeface.BOLD), 0, subject.length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)

val body = message.parts
.mapNotNull { it.text }
.filter { it.isNotBlank() }
.joinToString("\n") { text -> text }
.filter { part -> !part.isVCard() }
.mapNotNull { part -> part.text }
.filter { part -> part.isNotBlank() }
.joinToString("\n")

when {
subject.isNotBlank() && body.isNotBlank() -> subject.append("\n$body")
Expand All @@ -250,15 +254,10 @@ class MessagesAdapter @Inject constructor(
}
}
view.body.setVisible(message.isSms() || view.body.text.isNotBlank())

val canBodyGroupWithPrevious = canGroup(message, previous) || media.isNotEmpty()
val canBodyGroupWithNext = canGroup(message, next)
view.body.setBackgroundResource(when {
!canBodyGroupWithPrevious && canBodyGroupWithNext -> if (message.isMe()) R.drawable.message_out_first else R.drawable.message_in_first
canBodyGroupWithPrevious && canBodyGroupWithNext -> if (message.isMe()) R.drawable.message_out_middle else R.drawable.message_in_middle
canBodyGroupWithPrevious && !canBodyGroupWithNext -> if (message.isMe()) R.drawable.message_out_last else R.drawable.message_in_last
else -> R.drawable.message_only
})
view.body.setBackgroundResource(getBubble(
canGroupWithPrevious = canGroup(message, previous) || media.isNotEmpty(),
canGroupWithNext = canGroup(message, next),
isMe = message.isMe()))


// Bind the attachments
Expand All @@ -268,24 +267,44 @@ class MessagesAdapter @Inject constructor(
}

media.forEachIndexed { index, part ->
val mediaView = layoutInflater.inflate(R.layout.mms_preview_list_item, view.attachments, false)
mediaView.video.setVisible(part.isVideo())
mediaView.forwardTouches(view)
mediaView.clicks().subscribe {
if (part.isImage() || part.isVideo()) navigator.showMedia(part.id)
}

val canMediaGroupWithPrevious = canGroup(message, previous) || index > 0
val canMediaGroupWithNext = canGroup(message, next) || index < media.size && view.body.visibility == View.VISIBLE
mediaView.thumbnail.bubbleStyle = when {
!canMediaGroupWithPrevious && canMediaGroupWithNext -> if (message.isMe()) OUT_FIRST else IN_FIRST
canMediaGroupWithPrevious && canMediaGroupWithNext -> if (message.isMe()) OUT_MIDDLE else IN_MIDDLE
canMediaGroupWithPrevious && !canMediaGroupWithNext -> if (message.isMe()) OUT_LAST else IN_LAST
else -> ONLY
}

GlideApp.with(context).load(part.getUri()).fitCenter().into(mediaView.thumbnail)
view.attachments.addView(mediaView)
when {
part.isImage() || part.isVideo() -> {
val mediaView = View.inflate(view.context, R.layout.mms_preview_list_item, view.attachments)
mediaView.video.setVisible(part.isVideo())
mediaView.forwardTouches(view)
mediaView.setOnClickListener { navigator.showMedia(part.id) }

mediaView.thumbnail.bubbleStyle = when {
!canMediaGroupWithPrevious && canMediaGroupWithNext -> if (message.isMe()) OUT_FIRST else IN_FIRST
canMediaGroupWithPrevious && canMediaGroupWithNext -> if (message.isMe()) OUT_MIDDLE else IN_MIDDLE
canMediaGroupWithPrevious && !canMediaGroupWithNext -> if (message.isMe()) OUT_LAST else IN_LAST
else -> ONLY
}

GlideApp.with(context).load(part.getUri()).fitCenter().into(mediaView.thumbnail)
}

part.isVCard() -> {
val uri = ContentUris.withAppendedId(CursorToPartImpl.CONTENT_URI, part.id)
val vcard = context.contentResolver.openInputStream(uri).use { Ezvcard.parse(it).first() }

val mediaView = View.inflate(view.context, R.layout.mms_vcard_list_item, view.attachments)
mediaView.forwardTouches(view)
mediaView.setOnClickListener { navigator.saveVcard(uri) }
mediaView.name.text = vcard?.formattedName?.value
mediaView.vCardBackground.setBackgroundResource(getBubble(canMediaGroupWithPrevious, canMediaGroupWithNext, message.isMe()))

if (!message.isMe()) {
mediaView.vCardBackground.setBackgroundTint(theme.theme)
mediaView.vCardAvatar.setTint(theme.textPrimary)
mediaView.name.setTextColor(theme.textPrimary)
mediaView.label.setTextColor(theme.textTertiary)
}
}
}
}
}

Expand Down Expand Up @@ -314,6 +333,15 @@ class MessagesAdapter @Inject constructor(
})
}

private fun getBubble(canGroupWithPrevious: Boolean, canGroupWithNext: Boolean, isMe: Boolean): Int {
return when {
!canGroupWithPrevious && canGroupWithNext -> if (isMe) R.drawable.message_out_first else R.drawable.message_in_first
canGroupWithPrevious && canGroupWithNext -> if (isMe) R.drawable.message_out_middle else R.drawable.message_in_middle
canGroupWithPrevious && !canGroupWithNext -> if (isMe) R.drawable.message_out_last else R.drawable.message_in_last
else -> R.drawable.message_only
}
}

private fun canGroup(message: Message, other: Message?): Boolean {
if (other == null) return false
val diff = TimeUnit.MILLISECONDS.toMinutes(Math.abs(message.date - other.date))
Expand Down
2 changes: 1 addition & 1 deletion presentation/src/main/res/layout/mms_preview_list_item.xml
Expand Up @@ -21,7 +21,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="2dp">
android:layout_marginTop="2dp">

<common.widget.BubbleImageView
android:id="@+id/thumbnail"
Expand Down
72 changes: 72 additions & 0 deletions presentation/src/main/res/layout/mms_vcard_list_item.xml
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com>
~
~ This file is part of QKSMS.
~
~ QKSMS is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ QKSMS is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with QKSMS. If not, see <http://www.gnu.org/licenses/>.
-->
<android.support.constraint.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:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp">

<View
android:id="@+id/vCardBackground"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="@id/label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/name" />

<ImageView
android:id="@+id/vCardAvatar"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="12dp"
android:src="@drawable/ic_person_black_24dp"
android:tint="?android:attr/textColorSecondary"
app:layout_constraintBottom_toBottomOf="@id/vCardBackground"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/vCardBackground" />

<common.widget.QkTextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingEnd="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:textColor="?android:attr/textColorPrimary"
app:layout_constraintStart_toEndOf="@id/vCardAvatar"
app:textSize="primary"
tools:text="@tools:sample/full_names" />

<common.widget.QkTextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingStart="12dp"
android:text="@string/compose_vcard_label"
android:textColor="?android:attr/textColorTertiary"
app:layout_constraintStart_toStartOf="@id/name"
app:layout_constraintTop_toBottomOf="@id/name"
app:textSize="secondary" />

</android.support.constraint.ConstraintLayout>
1 change: 1 addition & 0 deletions presentation/src/main/res/values/strings.xml
Expand Up @@ -86,6 +86,7 @@
<string name="compose_send_group_title">Send as group message</string>
<string name="compose_send_group_summary">Recipients and replies will be visible to everyone</string>
<string name="compose_messages_empty">This is the start of your conversation. Say something nice!</string>
<string name="compose_vcard_label">Contact card</string>
<string name="compose_hint">Write a message…</string>
<string name="compose_menu_copy">Copy text</string>
<string name="compose_menu_forward">Forward</string>
Expand Down

0 comments on commit 69392e1

Please sign in to comment.