Attachments: handle rich content from keyboard

This commit is contained in:
ganfra 2019-10-22 12:37:59 +02:00
parent 2c8cd89533
commit c7a4d34192
8 changed files with 59 additions and 27 deletions

View File

@ -16,12 +16,12 @@
package im.vector.riotx.features.attachments package im.vector.riotx.features.attachments
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.kbeanie.multipicker.api.Picker.* import com.kbeanie.multipicker.api.Picker.*
import com.kbeanie.multipicker.core.PickerManager import com.kbeanie.multipicker.core.PickerManager
import com.kbeanie.multipicker.utils.IntentUtils
import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.riotx.core.platform.Restorable import im.vector.riotx.core.platform.Restorable
@ -34,15 +34,16 @@ private const val PENDING_TYPE_KEY = "PENDING_TYPE_KEY"
* This class helps to handle attachments by providing simple methods. * This class helps to handle attachments by providing simple methods.
* The process is asynchronous and you must implement [Callback] methods to get the data or a failure. * The process is asynchronous and you must implement [Callback] methods to get the data or a failure.
*/ */
class AttachmentsHelper private constructor(private val pickerManagerFactory: PickerManagerFactory) : Restorable { class AttachmentsHelper private constructor(private val context: Context,
private val pickerManagerFactory: PickerManagerFactory) : Restorable {
companion object { companion object {
fun create(fragment: Fragment, callback: Callback): AttachmentsHelper { fun create(fragment: Fragment, callback: Callback): AttachmentsHelper {
return AttachmentsHelper(FragmentPickerManagerFactory(fragment, callback)) return AttachmentsHelper(fragment.requireContext(), FragmentPickerManagerFactory(fragment, callback))
} }
fun create(activity: Activity, callback: Callback): AttachmentsHelper { fun create(activity: Activity, callback: Callback): AttachmentsHelper {
return AttachmentsHelper(ActivityPickerManagerFactory(activity, callback)) return AttachmentsHelper(activity, ActivityPickerManagerFactory(activity, callback))
} }
} }
@ -163,16 +164,16 @@ class AttachmentsHelper private constructor(private val pickerManagerFactory: Pi
* *
* @return true if it can handle the intent data, false otherwise * @return true if it can handle the intent data, false otherwise
*/ */
fun handleShare(intent: Intent): Boolean { fun handleShareIntent(intent: Intent): Boolean {
val type = intent.type ?: return false val type = intent.resolveType(context) ?: return false
if (type.startsWith("image")) { if (type.startsWith("image")) {
imagePicker.submit(IntentUtils.getPickerIntentForSharing(intent)) imagePicker.submit(intent)
} else if (type.startsWith("video")) { } else if (type.startsWith("video")) {
videoPicker.submit(IntentUtils.getPickerIntentForSharing(intent)) videoPicker.submit(intent)
} else if (type.startsWith("audio")) { } else if (type.startsWith("audio")) {
videoPicker.submit(IntentUtils.getPickerIntentForSharing(intent)) videoPicker.submit(intent)
} else if (type.startsWith("application") || type.startsWith("file") || type.startsWith("*")) { } else if (type.startsWith("application") || type.startsWith("file") || type.startsWith("*")) {
filePicker.submit(IntentUtils.getPickerIntentForSharing(intent)) filePicker.submit(intent)
} else { } else {
return false return false
} }

View File

@ -605,6 +605,19 @@ class RoomDetailFragment :
composerLayout.composerRelatedMessageCloseButton.setOnClickListener { composerLayout.composerRelatedMessageCloseButton.setOnClickListener {
roomDetailViewModel.process(RoomDetailActions.ExitSpecialMode(composerLayout.composerEditText.text.toString())) roomDetailViewModel.process(RoomDetailActions.ExitSpecialMode(composerLayout.composerEditText.text.toString()))
} }
composerLayout.callback = object : TextComposerView.Callback {
override fun onRichContentSelected(contentUri: Uri): Boolean {
val shareIntent = Intent().apply {
action = Intent.ACTION_SEND
data = contentUri
}
val isHandled = attachmentsHelper.handleShareIntent(shareIntent)
if (!isHandled) {
Toast.makeText(requireContext(), R.string.error_handling_incoming_share, Toast.LENGTH_SHORT).show()
}
return isHandled
}
}
} }
private fun setupAttachmentButton() { private fun setupAttachmentButton() {

View File

@ -18,38 +18,42 @@
package im.vector.riotx.features.home.room.detail.composer package im.vector.riotx.features.home.room.detail.composer
import android.content.Context import android.content.Context
import android.net.Uri
import android.os.Build import android.os.Build
import android.util.AttributeSet import android.util.AttributeSet
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection import android.view.inputmethod.InputConnection
import androidx.appcompat.widget.AppCompatEditText import android.widget.EditText
import androidx.core.view.inputmethod.EditorInfoCompat import androidx.core.view.inputmethod.EditorInfoCompat
import androidx.core.view.inputmethod.InputConnectionCompat import androidx.core.view.inputmethod.InputConnectionCompat
class TextComposerEditText @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) class ComposerEditText @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.R.attr.editTextStyle)
: AppCompatEditText(context, attrs, defStyleAttr) { : EditText(context, attrs, defStyleAttr) {
interface Callback {
fun onRichContentSelected(contentUri: Uri): Boolean
}
var callback: Callback? = null
override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection { override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection {
val ic: InputConnection = super.onCreateInputConnection(editorInfo) val ic: InputConnection = super.onCreateInputConnection(editorInfo)
EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/png")) EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("*/*"))
val callback = val callback =
InputConnectionCompat.OnCommitContentListener { inputContentInfo, flags, opts -> InputConnectionCompat.OnCommitContentListener { inputContentInfo, flags, _ ->
val lacksPermission = (flags and val lacksPermission = (flags and
InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0 InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0
// read and display inputContentInfo asynchronously
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 && lacksPermission) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 && lacksPermission) {
try { try {
inputContentInfo.requestPermission() inputContentInfo.requestPermission()
} catch (e: Exception) { } catch (e: Exception) {
return@OnCommitContentListener false // return false if failed return@OnCommitContentListener false
} }
} }
// read and display inputContentInfo asynchronously. callback?.onRichContentSelected(inputContentInfo.contentUri) ?: false
// call inputContentInfo.releasePermission() as needed.
true // return true if succeeded
} }
return InputConnectionCompat.createWrapper(ic, editorInfo, callback) return InputConnectionCompat.createWrapper(ic, editorInfo, callback)
} }
} }

View File

@ -17,9 +17,9 @@
package im.vector.riotx.features.home.room.detail.composer package im.vector.riotx.features.home.room.detail.composer
import android.content.Context import android.content.Context
import android.net.Uri
import android.util.AttributeSet import android.util.AttributeSet
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.EditText
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
@ -39,6 +39,12 @@ import im.vector.riotx.R
class TextComposerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, class TextComposerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = 0) : ConstraintLayout(context, attrs, defStyleAttr) { defStyleAttr: Int = 0) : ConstraintLayout(context, attrs, defStyleAttr) {
interface Callback {
fun onRichContentSelected(contentUri: Uri): Boolean
}
var callback: Callback? = null
@BindView(R.id.composer_related_message_sender) @BindView(R.id.composer_related_message_sender)
lateinit var composerRelatedMessageTitle: TextView lateinit var composerRelatedMessageTitle: TextView
@BindView(R.id.composer_related_message_preview) @BindView(R.id.composer_related_message_preview)
@ -50,7 +56,7 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib
@BindView(R.id.composer_related_message_close) @BindView(R.id.composer_related_message_close)
lateinit var composerRelatedMessageCloseButton: ImageButton lateinit var composerRelatedMessageCloseButton: ImageButton
@BindView(R.id.composerEditText) @BindView(R.id.composerEditText)
lateinit var composerEditText: EditText lateinit var composerEditText: ComposerEditText
@BindView(R.id.composer_avatar_view) @BindView(R.id.composer_avatar_view)
lateinit var composerAvatarImageView: ImageView lateinit var composerAvatarImageView: ImageView
@ -62,6 +68,11 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib
inflate(context, R.layout.merge_composer_layout, this) inflate(context, R.layout.merge_composer_layout, this)
ButterKnife.bind(this) ButterKnife.bind(this)
collapse(false) collapse(false)
composerEditText.callback = object : Callback, ComposerEditText.Callback {
override fun onRichContentSelected(contentUri: Uri): Boolean {
return callback?.onRichContentSelected(contentUri) ?: false
}
}
} }
fun collapse(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) { fun collapse(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) {

View File

@ -20,6 +20,7 @@ import android.content.ClipDescription
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import com.kbeanie.multipicker.utils.IntentUtils
import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
@ -64,7 +65,9 @@ class IncomingShareActivity :
} }
attachmentsHelper = AttachmentsHelper.create(this, this).register() attachmentsHelper = AttachmentsHelper.create(this, this).register()
if (intent?.action == Intent.ACTION_SEND || intent?.action == Intent.ACTION_SEND_MULTIPLE) { if (intent?.action == Intent.ACTION_SEND || intent?.action == Intent.ACTION_SEND_MULTIPLE) {
var isShareManaged = attachmentsHelper.handleShare(intent) var isShareManaged = attachmentsHelper.handleShareIntent(
IntentUtils.getPickerIntentForSharing(intent)
)
if (!isShareManaged) { if (!isShareManaged) {
isShareManaged = handleTextShare(intent) isShareManaged = handleTextShare(intent)
} }

View File

@ -143,7 +143,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/attachmentButton" /> app:layout_constraintStart_toEndOf="@id/attachmentButton" />
<im.vector.riotx.features.home.room.detail.composer.TextComposerEditText <im.vector.riotx.features.home.room.detail.composer.ComposerEditText
android:id="@+id/composerEditText" android:id="@+id/composerEditText"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -153,7 +153,7 @@
app:layout_constraintTop_toBottomOf="@id/composer_preview_barrier" app:layout_constraintTop_toBottomOf="@id/composer_preview_barrier"
app:layout_constraintVertical_bias="1" /> app:layout_constraintVertical_bias="1" />
<im.vector.riotx.features.home.room.detail.composer.TextComposerEditText <im.vector.riotx.features.home.room.detail.composer.ComposerEditText
android:id="@+id/composerEditText" android:id="@+id/composerEditText"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -114,7 +114,7 @@
android:tint="?attr/colorAccent" android:tint="?attr/colorAccent"
tools:ignore="MissingConstraints" /> tools:ignore="MissingConstraints" />
<im.vector.riotx.features.home.room.detail.composer.TextComposerEditText <im.vector.riotx.features.home.room.detail.composer.ComposerEditText
android:id="@+id/composerEditText" android:id="@+id/composerEditText"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"