Attachments/Sharing: refact a bit and handle more data.
This commit is contained in:
parent
6e39164b20
commit
ae5b6bd2b9
@ -65,6 +65,7 @@ import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment
|
||||
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment
|
||||
import im.vector.riotx.features.settings.*
|
||||
import im.vector.riotx.features.settings.push.PushGatewaysFragment
|
||||
import im.vector.riotx.features.share.IncomingShareActivity
|
||||
import im.vector.riotx.features.ui.UiStateRepository
|
||||
|
||||
@Component(dependencies = [VectorComponent::class], modules = [AssistedInjectModule::class, ViewModelModule::class, HomeModule::class])
|
||||
@ -185,6 +186,8 @@ interface ScreenComponent {
|
||||
|
||||
fun inject(reactionButton: ReactionButton)
|
||||
|
||||
fun inject(incomingShareActivity: IncomingShareActivity)
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
fun create(vectorComponent: VectorComponent,
|
||||
|
@ -19,50 +19,55 @@ import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.kbeanie.multipicker.api.CameraImagePicker
|
||||
import com.kbeanie.multipicker.api.FilePicker
|
||||
import com.kbeanie.multipicker.api.ImagePicker
|
||||
import com.kbeanie.multipicker.api.Picker.*
|
||||
import com.kbeanie.multipicker.api.callbacks.FilePickerCallback
|
||||
import com.kbeanie.multipicker.api.callbacks.ImagePickerCallback
|
||||
import com.kbeanie.multipicker.api.entity.ChosenFile
|
||||
import com.kbeanie.multipicker.api.entity.ChosenImage
|
||||
import com.kbeanie.multipicker.api.entity.ChosenVideo
|
||||
import com.kbeanie.multipicker.core.PickerManager
|
||||
import com.kbeanie.multipicker.utils.IntentUtils
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.riotx.core.platform.Restorable
|
||||
|
||||
private const val CAPTURE_PATH_KEY = "CAPTURE_PATH_KEY"
|
||||
|
||||
class AttachmentsHelper(private val fragment: Fragment, private val callback: Callback) : Restorable {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
class AttachmentsHelper private constructor(private val pickerManagerFactory: PickerManagerFactory) : Restorable {
|
||||
|
||||
companion object {
|
||||
fun create(fragment: Fragment, callback: Callback): AttachmentsHelper {
|
||||
return AttachmentsHelper(FragmentPickerManagerFactory(fragment, callback))
|
||||
}
|
||||
|
||||
fun create(activity: Activity, callback: Callback): AttachmentsHelper {
|
||||
return AttachmentsHelper(ActivityPickerManagerFactory(activity, callback))
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onAttachmentsReady(attachments: List<ContentAttachmentData>)
|
||||
fun onAttachmentsProcessFailed()
|
||||
}
|
||||
|
||||
private val attachmentsPickerCallback = AttachmentsPickerCallback(callback)
|
||||
private var capturePath: String? = null
|
||||
|
||||
private val imagePicker by lazy {
|
||||
ImagePicker(fragment).also {
|
||||
it.setImagePickerCallback(attachmentsPickerCallback)
|
||||
it.allowMultiple()
|
||||
}
|
||||
pickerManagerFactory.createImagePicker()
|
||||
}
|
||||
|
||||
private val videoPicker by lazy {
|
||||
pickerManagerFactory.createVideoPicker()
|
||||
}
|
||||
|
||||
private val cameraImagePicker by lazy {
|
||||
CameraImagePicker(fragment).also {
|
||||
it.setImagePickerCallback(attachmentsPickerCallback)
|
||||
}
|
||||
pickerManagerFactory.createCameraImagePicker()
|
||||
}
|
||||
|
||||
private val filePicker by lazy {
|
||||
FilePicker(fragment).also {
|
||||
it.allowMultiple()
|
||||
it.setFilePickerCallback(attachmentsPickerCallback)
|
||||
}
|
||||
pickerManagerFactory.createFilePicker()
|
||||
}
|
||||
|
||||
// Restorable
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
capturePath?.also {
|
||||
outState.putString(CAPTURE_PATH_KEY, it)
|
||||
@ -71,26 +76,42 @@ class AttachmentsHelper(private val fragment: Fragment, private val callback: Ca
|
||||
|
||||
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
|
||||
capturePath = savedInstanceState?.getString(CAPTURE_PATH_KEY)
|
||||
if (capturePath != null) {
|
||||
cameraImagePicker.reinitialize(capturePath)
|
||||
}
|
||||
}
|
||||
|
||||
var capturePath: String? = null
|
||||
private set
|
||||
// Public Methods
|
||||
|
||||
/**
|
||||
* Starts the process for handling file picking
|
||||
*/
|
||||
fun selectFile() {
|
||||
filePicker.pickFile()
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the process for handling image picking
|
||||
*/
|
||||
fun selectGallery() {
|
||||
imagePicker.pickImage()
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the process for handling capture image picking
|
||||
*/
|
||||
fun openCamera() {
|
||||
capturePath = cameraImagePicker.pickImage()
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods aims to handle on activity result data.
|
||||
*
|
||||
* @return true if it can handle the data, false otherwise
|
||||
*/
|
||||
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
val pickerManager = getPickerManager(requestCode)
|
||||
val pickerManager = getPickerManagerForRequestCode(requestCode)
|
||||
if (pickerManager != null) {
|
||||
pickerManager.submit(data)
|
||||
return true
|
||||
@ -99,7 +120,26 @@ class AttachmentsHelper(private val fragment: Fragment, private val callback: Ca
|
||||
return false
|
||||
}
|
||||
|
||||
private fun getPickerManager(requestCode: Int): PickerManager? {
|
||||
/**
|
||||
* This methods aims to handle share intent.
|
||||
*
|
||||
* @return true if it can handle the intent data, false otherwise
|
||||
*/
|
||||
fun handleShare(intent: Intent): Boolean {
|
||||
val type = intent.type ?: return false
|
||||
if (type.startsWith("image")) {
|
||||
imagePicker.submit(IntentUtils.getPickerIntentForSharing(intent))
|
||||
} else if (type.startsWith("video")) {
|
||||
videoPicker.submit(IntentUtils.getPickerIntentForSharing(intent))
|
||||
} else if (type.startsWith("application") || type.startsWith("file") || type.startsWith("*")) {
|
||||
filePicker.submit(IntentUtils.getPickerIntentForSharing(intent))
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getPickerManagerForRequestCode(requestCode: Int): PickerManager? {
|
||||
return when (requestCode) {
|
||||
PICK_IMAGE_DEVICE -> imagePicker
|
||||
PICK_IMAGE_CAMERA -> cameraImagePicker
|
||||
@ -108,81 +148,4 @@ class AttachmentsHelper(private val fragment: Fragment, private val callback: Ca
|
||||
}
|
||||
}
|
||||
|
||||
private inner class AttachmentsPickerCallback(private val callback: Callback) : ImagePickerCallback, FilePickerCallback {
|
||||
|
||||
override fun onFilesChosen(files: MutableList<ChosenFile>?) {
|
||||
if (files.isNullOrEmpty()) {
|
||||
callback.onAttachmentsProcessFailed()
|
||||
} else {
|
||||
val attachments = files.map {
|
||||
it.toContentAttachmentData()
|
||||
}
|
||||
callback.onAttachmentsReady(attachments)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onImagesChosen(images: MutableList<ChosenImage>?) {
|
||||
if (images.isNullOrEmpty()) {
|
||||
callback.onAttachmentsProcessFailed()
|
||||
} else {
|
||||
val attachments = images.map {
|
||||
it.toContentAttachmentData()
|
||||
}
|
||||
callback.onAttachmentsReady(attachments)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(error: String?) {
|
||||
callback.onAttachmentsProcessFailed()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun ChosenFile.toContentAttachmentData(): ContentAttachmentData {
|
||||
return ContentAttachmentData(
|
||||
path = originalPath,
|
||||
mimeType = mimeType,
|
||||
type = mapType(),
|
||||
size = size,
|
||||
date = createdAt.time,
|
||||
name = displayName
|
||||
)
|
||||
}
|
||||
|
||||
private fun ChosenFile.mapType(): ContentAttachmentData.Type {
|
||||
return when {
|
||||
mimeType.startsWith("image/") -> ContentAttachmentData.Type.IMAGE
|
||||
mimeType.startsWith("video/") -> ContentAttachmentData.Type.VIDEO
|
||||
mimeType.startsWith("audio/") -> ContentAttachmentData.Type.AUDIO
|
||||
else -> ContentAttachmentData.Type.FILE
|
||||
}
|
||||
}
|
||||
|
||||
private fun ChosenImage.toContentAttachmentData(): ContentAttachmentData {
|
||||
return ContentAttachmentData(
|
||||
path = originalPath,
|
||||
mimeType = mimeType,
|
||||
type = mapType(),
|
||||
name = displayName,
|
||||
size = size,
|
||||
height = height.toLong(),
|
||||
width = width.toLong(),
|
||||
date = createdAt.time
|
||||
)
|
||||
}
|
||||
|
||||
private fun ChosenVideo.toContentAttachmentData(): ContentAttachmentData {
|
||||
return ContentAttachmentData(
|
||||
path = originalPath,
|
||||
mimeType = mimeType,
|
||||
type = ContentAttachmentData.Type.VIDEO,
|
||||
size = size,
|
||||
date = createdAt.time,
|
||||
height = height.toLong(),
|
||||
width = width.toLong(),
|
||||
duration = duration,
|
||||
name = displayName
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.attachments
|
||||
|
||||
import com.kbeanie.multipicker.api.entity.ChosenFile
|
||||
import com.kbeanie.multipicker.api.entity.ChosenImage
|
||||
import com.kbeanie.multipicker.api.entity.ChosenVideo
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
|
||||
fun ChosenFile.toContentAttachmentData(): ContentAttachmentData {
|
||||
return ContentAttachmentData(
|
||||
path = originalPath,
|
||||
mimeType = mimeType,
|
||||
type = mapType(),
|
||||
size = size,
|
||||
date = createdAt.time,
|
||||
name = displayName
|
||||
)
|
||||
}
|
||||
|
||||
fun ChosenFile.mapType(): ContentAttachmentData.Type {
|
||||
return when {
|
||||
mimeType.startsWith("image/") -> ContentAttachmentData.Type.IMAGE
|
||||
mimeType.startsWith("video/") -> ContentAttachmentData.Type.VIDEO
|
||||
mimeType.startsWith("audio/") -> ContentAttachmentData.Type.AUDIO
|
||||
else -> ContentAttachmentData.Type.FILE
|
||||
}
|
||||
}
|
||||
|
||||
fun ChosenImage.toContentAttachmentData(): ContentAttachmentData {
|
||||
return ContentAttachmentData(
|
||||
path = originalPath,
|
||||
mimeType = mimeType,
|
||||
type = mapType(),
|
||||
name = displayName,
|
||||
size = size,
|
||||
height = height.toLong(),
|
||||
width = width.toLong(),
|
||||
date = createdAt.time
|
||||
)
|
||||
}
|
||||
|
||||
fun ChosenVideo.toContentAttachmentData(): ContentAttachmentData {
|
||||
return ContentAttachmentData(
|
||||
path = originalPath,
|
||||
mimeType = mimeType,
|
||||
type = ContentAttachmentData.Type.VIDEO,
|
||||
size = size,
|
||||
date = createdAt.time,
|
||||
height = height.toLong(),
|
||||
width = width.toLong(),
|
||||
duration = duration,
|
||||
name = displayName
|
||||
)
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.attachments
|
||||
|
||||
import com.kbeanie.multipicker.api.callbacks.FilePickerCallback
|
||||
import com.kbeanie.multipicker.api.callbacks.ImagePickerCallback
|
||||
import com.kbeanie.multipicker.api.callbacks.VideoPickerCallback
|
||||
import com.kbeanie.multipicker.api.entity.ChosenFile
|
||||
import com.kbeanie.multipicker.api.entity.ChosenImage
|
||||
import com.kbeanie.multipicker.api.entity.ChosenVideo
|
||||
|
||||
/**
|
||||
* This class delegates the PickerManager callbacks to an [AttachmentsHelper.Callback]
|
||||
*/
|
||||
class AttachmentsPickerCallback(private val callback: AttachmentsHelper.Callback) : ImagePickerCallback, FilePickerCallback, VideoPickerCallback {
|
||||
|
||||
override fun onFilesChosen(files: MutableList<ChosenFile>?) {
|
||||
if (files.isNullOrEmpty()) {
|
||||
callback.onAttachmentsProcessFailed()
|
||||
} else {
|
||||
val attachments = files.map {
|
||||
it.toContentAttachmentData()
|
||||
}
|
||||
callback.onAttachmentsReady(attachments)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onImagesChosen(images: MutableList<ChosenImage>?) {
|
||||
if (images.isNullOrEmpty()) {
|
||||
callback.onAttachmentsProcessFailed()
|
||||
} else {
|
||||
val attachments = images.map {
|
||||
it.toContentAttachmentData()
|
||||
}
|
||||
callback.onAttachmentsReady(attachments)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onVideosChosen(videos: MutableList<ChosenVideo>?) {
|
||||
if (videos.isNullOrEmpty()) {
|
||||
callback.onAttachmentsProcessFailed()
|
||||
} else {
|
||||
val attachments = videos.map {
|
||||
it.toContentAttachmentData()
|
||||
}
|
||||
callback.onAttachmentsReady(attachments)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(error: String?) {
|
||||
callback.onAttachmentsProcessFailed()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.attachments
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.kbeanie.multipicker.api.CameraImagePicker
|
||||
import com.kbeanie.multipicker.api.FilePicker
|
||||
import com.kbeanie.multipicker.api.ImagePicker
|
||||
import com.kbeanie.multipicker.api.VideoPicker
|
||||
|
||||
interface PickerManagerFactory {
|
||||
|
||||
fun createImagePicker(): ImagePicker
|
||||
|
||||
fun createCameraImagePicker(): CameraImagePicker
|
||||
|
||||
fun createVideoPicker(): VideoPicker
|
||||
|
||||
fun createFilePicker(): FilePicker
|
||||
|
||||
|
||||
}
|
||||
|
||||
class ActivityPickerManagerFactory(private val activity: Activity, callback: AttachmentsHelper.Callback) : PickerManagerFactory {
|
||||
|
||||
private val attachmentsPickerCallback = AttachmentsPickerCallback(callback)
|
||||
|
||||
override fun createImagePicker(): ImagePicker {
|
||||
return ImagePicker(activity).also {
|
||||
it.setImagePickerCallback(attachmentsPickerCallback)
|
||||
it.allowMultiple()
|
||||
}
|
||||
}
|
||||
|
||||
override fun createCameraImagePicker(): CameraImagePicker {
|
||||
return CameraImagePicker(activity).also {
|
||||
it.setImagePickerCallback(attachmentsPickerCallback)
|
||||
}
|
||||
}
|
||||
|
||||
override fun createVideoPicker(): VideoPicker {
|
||||
return VideoPicker(activity).also {
|
||||
it.setVideoPickerCallback(attachmentsPickerCallback)
|
||||
it.allowMultiple()
|
||||
}
|
||||
}
|
||||
|
||||
override fun createFilePicker(): FilePicker {
|
||||
return FilePicker(activity).also {
|
||||
it.allowMultiple()
|
||||
it.setFilePickerCallback(attachmentsPickerCallback)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FragmentPickerManagerFactory(private val fragment: Fragment, callback: AttachmentsHelper.Callback) : PickerManagerFactory {
|
||||
|
||||
private val attachmentsPickerCallback = AttachmentsPickerCallback(callback)
|
||||
|
||||
override fun createImagePicker(): ImagePicker {
|
||||
return ImagePicker(fragment).also {
|
||||
it.setImagePickerCallback(attachmentsPickerCallback)
|
||||
it.allowMultiple()
|
||||
}
|
||||
}
|
||||
|
||||
override fun createCameraImagePicker(): CameraImagePicker {
|
||||
return CameraImagePicker(fragment).also {
|
||||
it.setImagePickerCallback(attachmentsPickerCallback)
|
||||
}
|
||||
}
|
||||
|
||||
override fun createVideoPicker(): VideoPicker {
|
||||
return VideoPicker(fragment).also {
|
||||
it.setVideoPickerCallback(attachmentsPickerCallback)
|
||||
it.allowMultiple()
|
||||
}
|
||||
}
|
||||
|
||||
override fun createFilePicker(): FilePicker {
|
||||
return FilePicker(fragment).also {
|
||||
it.allowMultiple()
|
||||
it.setFilePickerCallback(attachmentsPickerCallback)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -106,6 +106,7 @@ import im.vector.riotx.features.media.VideoMediaViewerActivity
|
||||
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
||||
import im.vector.riotx.features.reactions.EmojiReactionPickerActivity
|
||||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
import im.vector.riotx.features.share.SharedData
|
||||
import im.vector.riotx.features.themes.ThemeUtils
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.fragment_room_detail.*
|
||||
@ -120,7 +121,8 @@ import javax.inject.Inject
|
||||
@Parcelize
|
||||
data class RoomDetailArgs(
|
||||
val roomId: String,
|
||||
val eventId: String? = null
|
||||
val eventId: String? = null,
|
||||
val sharedData: SharedData? = null
|
||||
) : Parcelable
|
||||
|
||||
|
||||
@ -195,15 +197,7 @@ class RoomDetailFragment :
|
||||
|
||||
private lateinit var actionViewModel: ActionsHandler
|
||||
private lateinit var layoutManager: LinearLayoutManager
|
||||
|
||||
private lateinit var _attachmentsHelper: AttachmentsHelper
|
||||
private val attachmentsHelper: AttachmentsHelper
|
||||
get() {
|
||||
if (::_attachmentsHelper.isInitialized.not()) {
|
||||
_attachmentsHelper = AttachmentsHelper(this, this).register()
|
||||
}
|
||||
return _attachmentsHelper
|
||||
}
|
||||
private lateinit var attachmentsHelper: AttachmentsHelper
|
||||
|
||||
@BindView(R.id.composerLayout)
|
||||
lateinit var composerLayout: TextComposerView
|
||||
@ -218,6 +212,7 @@ class RoomDetailFragment :
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
actionViewModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java)
|
||||
attachmentsHelper = AttachmentsHelper.create(this, this).register()
|
||||
setupToolbar(roomToolbar)
|
||||
setupRecyclerView()
|
||||
setupComposer()
|
||||
@ -277,6 +272,15 @@ class RoomDetailFragment :
|
||||
roomDetailViewModel.selectSubscribe(RoomDetailViewState::syncState) { syncState ->
|
||||
syncStateView.render(syncState)
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
when (val sharedData = roomDetailArgs.sharedData) {
|
||||
is SharedData.Text -> roomDetailViewModel.process(RoomDetailActions.SendMessage(sharedData.text, false))
|
||||
is SharedData.Attachments -> roomDetailViewModel.process(RoomDetailActions.SendMedia(sharedData.attachmentData))
|
||||
null -> Timber.v("No share data to process")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@ -306,9 +310,9 @@ class RoomDetailFragment :
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.dialog_title_error)
|
||||
.setMessage(getString(R.string.error_file_too_big,
|
||||
error.filename,
|
||||
TextUtils.formatFileSize(requireContext(), error.fileSizeInBytes),
|
||||
TextUtils.formatFileSize(requireContext(), error.homeServerLimitInBytes)
|
||||
error.filename,
|
||||
TextUtils.formatFileSize(requireContext(), error.fileSizeInBytes),
|
||||
TextUtils.formatFileSize(requireContext(), error.homeServerLimitInBytes)
|
||||
))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
@ -385,11 +389,11 @@ class RoomDetailFragment :
|
||||
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
|
||||
val parser = Parser.builder().build()
|
||||
val document = parser.parse(messageContent.formattedBody
|
||||
?: messageContent.body)
|
||||
?: messageContent.body)
|
||||
formattedBody = eventHtmlRenderer.render(document)
|
||||
}
|
||||
composerLayout.composerRelatedMessageContent.text = formattedBody
|
||||
?: nonFormattedBody
|
||||
?: nonFormattedBody
|
||||
|
||||
updateComposerText(defaultContent)
|
||||
|
||||
@ -398,11 +402,11 @@ class RoomDetailFragment :
|
||||
|
||||
|
||||
avatarRenderer.render(event.senderAvatar, event.root.senderId
|
||||
?: "", event.senderName, composerLayout.composerRelatedMessageAvatar)
|
||||
?: "", event.senderName, composerLayout.composerRelatedMessageAvatar)
|
||||
avatarRenderer.render(event.senderAvatar,
|
||||
event.root.senderId ?: "",
|
||||
event.senderName,
|
||||
composerLayout.composerRelatedMessageAvatar)
|
||||
event.root.senderId ?: "",
|
||||
event.senderName,
|
||||
composerLayout.composerRelatedMessageAvatar)
|
||||
composerLayout.expand {
|
||||
//need to do it here also when not using quick reply
|
||||
focusComposerAndShowKeyboard()
|
||||
@ -439,9 +443,9 @@ class RoomDetailFragment :
|
||||
when (requestCode) {
|
||||
REACTION_SELECT_REQUEST_CODE -> {
|
||||
val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID)
|
||||
?: return
|
||||
?: return
|
||||
val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT)
|
||||
?: return
|
||||
?: return
|
||||
//TODO check if already reacted with that?
|
||||
roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, eventId))
|
||||
}
|
||||
|
@ -42,9 +42,7 @@ class FilteredRoomsActivity : VectorBaseActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
configureToolbar(filteredRoomsToolbar)
|
||||
|
||||
if (isFirstCreation()) {
|
||||
roomListFragment = RoomListFragment.newInstance(RoomListParams(RoomListFragment.DisplayMode.FILTERED))
|
||||
replaceFragment(roomListFragment, R.id.filteredRoomsFragmentContainer, FRAGMENT_TAG)
|
||||
@ -58,12 +56,10 @@ class FilteredRoomsActivity : VectorBaseActivity() {
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String): Boolean {
|
||||
// TODO Create a viewModel and remove this public fun
|
||||
roomListFragment.filterRoomsWith(newText)
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
// Open the keyboard immediately
|
||||
filteredRoomsSearchView.requestFocus()
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ class RoomListDisplayModeFilter(private val displayMode: RoomListFragment.Displa
|
||||
RoomListFragment.DisplayMode.PEOPLE -> roomSummary.isDirect && roomSummary.membership == Membership.JOIN
|
||||
RoomListFragment.DisplayMode.ROOMS -> !roomSummary.isDirect && roomSummary.membership == Membership.JOIN
|
||||
RoomListFragment.DisplayMode.FILTERED -> roomSummary.membership == Membership.JOIN
|
||||
RoomListFragment.DisplayMode.SHARE -> roomSummary.membership == Membership.JOIN
|
||||
}
|
||||
}
|
||||
}
|
@ -39,14 +39,15 @@ import im.vector.riotx.core.platform.StateView
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import im.vector.riotx.features.home.room.list.widget.FabMenuView
|
||||
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
||||
import im.vector.riotx.features.share.SharedData
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.fragment_room_list.*
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Parcelize
|
||||
data class RoomListParams(
|
||||
val displayMode: RoomListFragment.DisplayMode
|
||||
val displayMode: RoomListFragment.DisplayMode,
|
||||
val sharedData: SharedData? = null
|
||||
) : Parcelable
|
||||
|
||||
|
||||
@ -56,7 +57,8 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O
|
||||
HOME(R.string.bottom_action_home),
|
||||
PEOPLE(R.string.bottom_action_people_x),
|
||||
ROOMS(R.string.bottom_action_rooms),
|
||||
FILTERED(/* Not used */ R.string.bottom_action_rooms)
|
||||
FILTERED(/* Not used */ 0),
|
||||
SHARE(/* Not used */ 0)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ -86,7 +88,12 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O
|
||||
setupRecyclerView()
|
||||
roomListViewModel.subscribe { renderState(it) }
|
||||
roomListViewModel.openRoomLiveData.observeEventFirstThrottle(this, 800L) {
|
||||
navigator.openRoom(requireActivity(), it)
|
||||
if (roomListParams.displayMode == DisplayMode.SHARE) {
|
||||
val sharedData = roomListParams.sharedData ?: return@observeEventFirstThrottle
|
||||
navigator.openRoomForSharing(requireActivity(), it, sharedData)
|
||||
} else {
|
||||
navigator.openRoom(requireActivity(), it)
|
||||
}
|
||||
}
|
||||
|
||||
createChatFabMenu.listener = this
|
||||
@ -101,10 +108,10 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O
|
||||
|
||||
private fun setupCreateRoomButton() {
|
||||
when (roomListParams.displayMode) {
|
||||
DisplayMode.HOME -> createChatFabMenu.isVisible = true
|
||||
DisplayMode.PEOPLE -> createChatRoomButton.isVisible = true
|
||||
DisplayMode.ROOMS -> createGroupRoomButton.isVisible = true
|
||||
DisplayMode.FILTERED -> Unit // No button in this mode
|
||||
DisplayMode.HOME -> createChatFabMenu.isVisible = true
|
||||
DisplayMode.PEOPLE -> createChatRoomButton.isVisible = true
|
||||
DisplayMode.ROOMS -> createGroupRoomButton.isVisible = true
|
||||
else -> Unit // No button in this mode
|
||||
}
|
||||
|
||||
createChatRoomButton.setOnClickListener {
|
||||
@ -127,10 +134,10 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O
|
||||
RecyclerView.SCROLL_STATE_DRAGGING,
|
||||
RecyclerView.SCROLL_STATE_SETTLING -> {
|
||||
when (roomListParams.displayMode) {
|
||||
DisplayMode.HOME -> createChatFabMenu.hide()
|
||||
DisplayMode.PEOPLE -> createChatRoomButton.hide()
|
||||
DisplayMode.ROOMS -> createGroupRoomButton.hide()
|
||||
DisplayMode.FILTERED -> Unit
|
||||
DisplayMode.HOME -> createChatFabMenu.hide()
|
||||
DisplayMode.PEOPLE -> createChatRoomButton.hide()
|
||||
DisplayMode.ROOMS -> createGroupRoomButton.hide()
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -167,10 +174,10 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O
|
||||
private val showFabRunnable = Runnable {
|
||||
if (isAdded) {
|
||||
when (roomListParams.displayMode) {
|
||||
DisplayMode.HOME -> createChatFabMenu.show()
|
||||
DisplayMode.PEOPLE -> createChatRoomButton.show()
|
||||
DisplayMode.ROOMS -> createGroupRoomButton.show()
|
||||
DisplayMode.FILTERED -> Unit
|
||||
DisplayMode.HOME -> createChatFabMenu.show()
|
||||
DisplayMode.PEOPLE -> createChatRoomButton.show()
|
||||
DisplayMode.ROOMS -> createGroupRoomButton.show()
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -201,7 +208,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O
|
||||
}
|
||||
.isNullOrEmpty()
|
||||
val emptyState = when (roomListParams.displayMode) {
|
||||
DisplayMode.HOME -> {
|
||||
DisplayMode.HOME -> {
|
||||
if (hasNoRoom) {
|
||||
StateView.State.Empty(
|
||||
getString(R.string.room_list_catchup_welcome_title),
|
||||
@ -215,19 +222,19 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O
|
||||
getString(R.string.room_list_catchup_empty_body))
|
||||
}
|
||||
}
|
||||
DisplayMode.PEOPLE ->
|
||||
DisplayMode.PEOPLE ->
|
||||
StateView.State.Empty(
|
||||
getString(R.string.room_list_people_empty_title),
|
||||
ContextCompat.getDrawable(requireContext(), R.drawable.ic_home_bottom_chat),
|
||||
getString(R.string.room_list_people_empty_body)
|
||||
)
|
||||
DisplayMode.ROOMS ->
|
||||
DisplayMode.ROOMS ->
|
||||
StateView.State.Empty(
|
||||
getString(R.string.room_list_rooms_empty_title),
|
||||
ContextCompat.getDrawable(requireContext(), R.drawable.ic_home_bottom_group),
|
||||
getString(R.string.room_list_rooms_empty_body)
|
||||
)
|
||||
DisplayMode.FILTERED ->
|
||||
else ->
|
||||
// Always display the content in this mode, because if the footer
|
||||
StateView.State.Content
|
||||
}
|
||||
|
@ -218,6 +218,7 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
|
||||
RoomListFragment.DisplayMode.PEOPLE -> chronologicalRoomComparator
|
||||
RoomListFragment.DisplayMode.ROOMS -> chronologicalRoomComparator
|
||||
RoomListFragment.DisplayMode.FILTERED -> chronologicalRoomComparator
|
||||
RoomListFragment.DisplayMode.SHARE -> chronologicalRoomComparator
|
||||
}
|
||||
|
||||
return RoomSummaries().apply {
|
||||
|
@ -16,8 +16,10 @@
|
||||
|
||||
package im.vector.riotx.features.navigation
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
@ -33,6 +35,7 @@ import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
|
||||
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity
|
||||
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewActivity
|
||||
import im.vector.riotx.features.settings.VectorSettingsActivity
|
||||
import im.vector.riotx.features.share.SharedData
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -46,6 +49,13 @@ class DefaultNavigator @Inject constructor() : Navigator {
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
override fun openRoomForSharing(activity: Activity, roomId: String, sharedData: SharedData) {
|
||||
val args = RoomDetailArgs(roomId, null, sharedData)
|
||||
val intent = RoomDetailActivity.newIntent(activity, args)
|
||||
activity.startActivity(intent)
|
||||
activity.finish()
|
||||
}
|
||||
|
||||
override fun openNotJoinedRoom(context: Context, roomIdOrAlias: String, eventId: String?) {
|
||||
if (context is VectorBaseActivity) {
|
||||
context.notImplemented("Open not joined room")
|
||||
|
@ -16,13 +16,18 @@
|
||||
|
||||
package im.vector.riotx.features.navigation
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||
import im.vector.riotx.features.share.SharedData
|
||||
|
||||
interface Navigator {
|
||||
|
||||
fun openRoom(context: Context, roomId: String, eventId: String? = null)
|
||||
|
||||
fun openRoomForSharing(activity: Activity, roomId: String, sharedData: SharedData)
|
||||
|
||||
fun openNotJoinedRoom(context: Context, roomIdOrAlias: String, eventId: String? = null)
|
||||
|
||||
fun openRoomPreview(publicRoom: PublicRoom, context: Context)
|
||||
|
@ -1,85 +1,105 @@
|
||||
package im.vector.riotx.features.share
|
||||
|
||||
import android.content.ClipDescription
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import com.kbeanie.multipicker.api.AudioPicker
|
||||
import com.kbeanie.multipicker.api.FilePicker
|
||||
import com.kbeanie.multipicker.api.ImagePicker
|
||||
import com.kbeanie.multipicker.api.VideoPicker
|
||||
import com.kbeanie.multipicker.api.callbacks.AudioPickerCallback
|
||||
import com.kbeanie.multipicker.api.callbacks.FilePickerCallback
|
||||
import com.kbeanie.multipicker.api.callbacks.ImagePickerCallback
|
||||
import com.kbeanie.multipicker.api.callbacks.VideoPickerCallback
|
||||
import com.kbeanie.multipicker.api.entity.ChosenAudio
|
||||
import com.kbeanie.multipicker.api.entity.ChosenFile
|
||||
import com.kbeanie.multipicker.api.entity.ChosenImage
|
||||
import com.kbeanie.multipicker.api.entity.ChosenVideo
|
||||
import com.kbeanie.multipicker.utils.IntentUtils
|
||||
import android.widget.Toast
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.core.extensions.replaceFragment
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
import timber.log.Timber
|
||||
import im.vector.riotx.features.attachments.AttachmentsHelper
|
||||
import im.vector.riotx.features.home.HomeActivity
|
||||
import im.vector.riotx.features.home.LoadingFragment
|
||||
import im.vector.riotx.features.home.room.list.RoomListFragment
|
||||
import im.vector.riotx.features.home.room.list.RoomListParams
|
||||
import im.vector.riotx.features.login.LoginActivity
|
||||
import im.vector.riotx.features.login.LoginConfig
|
||||
import kotlinx.android.synthetic.main.activity_incoming_share.*
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class IncomingShareActivity :
|
||||
VectorBaseActivity(),
|
||||
ImagePickerCallback,
|
||||
VideoPickerCallback,
|
||||
FilePickerCallback,
|
||||
AudioPickerCallback {
|
||||
VectorBaseActivity(), AttachmentsHelper.Callback {
|
||||
|
||||
|
||||
@Inject lateinit var sessionHolder: ActiveSessionHolder
|
||||
private lateinit var roomListFragment: RoomListFragment
|
||||
private lateinit var attachmentsHelper: AttachmentsHelper
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.activity_incoming_share
|
||||
}
|
||||
|
||||
override fun injectWith(injector: ScreenComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (!sessionHolder.hasActiveSession()) {
|
||||
startLoginActivity()
|
||||
return
|
||||
}
|
||||
configureToolbar(incomingShareToolbar)
|
||||
if (isFirstCreation()) {
|
||||
val loadingDetail = LoadingFragment.newInstance()
|
||||
replaceFragment(loadingDetail, R.id.shareRoomListFragmentContainer)
|
||||
}
|
||||
attachmentsHelper = AttachmentsHelper.create(this, this).register()
|
||||
if (intent?.action == Intent.ACTION_SEND || intent?.action == Intent.ACTION_SEND_MULTIPLE) {
|
||||
val handled = handleShare(intent)
|
||||
if (!handled) {
|
||||
finish()
|
||||
var isShareManaged = attachmentsHelper.handleShare(intent)
|
||||
if (!isShareManaged) {
|
||||
isShareManaged = handleTextShare(intent)
|
||||
}
|
||||
if (!isShareManaged) {
|
||||
cantManageShare()
|
||||
}
|
||||
} else {
|
||||
finish()
|
||||
cantManageShare()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleShare(intent: Intent): Boolean {
|
||||
val type = intent.type ?: return false
|
||||
if (type.startsWith("image")) {
|
||||
val picker = ImagePicker(this)
|
||||
picker.setImagePickerCallback(this)
|
||||
picker.submit(IntentUtils.getPickerIntentForSharing(intent))
|
||||
} else if (type.startsWith("video")) {
|
||||
val picker = VideoPicker(this)
|
||||
picker.setVideoPickerCallback(this)
|
||||
picker.submit(IntentUtils.getPickerIntentForSharing(intent))
|
||||
} else if (type.startsWith("application") || type.startsWith("file") || type.startsWith("*")) {
|
||||
val picker = FilePicker(this)
|
||||
picker.setFilePickerCallback(this)
|
||||
picker.submit(IntentUtils.getPickerIntentForSharing(intent))
|
||||
} else if (type.startsWith("audio")) {
|
||||
val picker = AudioPicker(this)
|
||||
picker.setAudioPickerCallback(this)
|
||||
picker.submit(IntentUtils.getPickerIntentForSharing(intent))
|
||||
} else {
|
||||
return false
|
||||
override fun onAttachmentsReady(attachments: List<ContentAttachmentData>) {
|
||||
val roomListParams = RoomListParams(RoomListFragment.DisplayMode.SHARE, sharedData = SharedData.Attachments(attachments))
|
||||
roomListFragment = RoomListFragment.newInstance(roomListParams)
|
||||
replaceFragment(roomListFragment, R.id.shareRoomListFragmentContainer)
|
||||
}
|
||||
|
||||
override fun onAttachmentsProcessFailed() {
|
||||
cantManageShare()
|
||||
}
|
||||
|
||||
private fun cantManageShare() {
|
||||
Toast.makeText(this, "Couldn't handle share data", Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun handleTextShare(intent: Intent): Boolean {
|
||||
if (intent.type == ClipDescription.MIMETYPE_TEXT_PLAIN) {
|
||||
val sharedText = intent.getCharSequenceExtra(Intent.EXTRA_TEXT)?.toString()
|
||||
return if (sharedText.isNullOrEmpty()) {
|
||||
false
|
||||
} else {
|
||||
val roomListParams = RoomListParams(RoomListFragment.DisplayMode.SHARE, sharedData = SharedData.Text(sharedText))
|
||||
roomListFragment = RoomListFragment.newInstance(roomListParams)
|
||||
replaceFragment(roomListFragment, R.id.shareRoomListFragmentContainer)
|
||||
true
|
||||
}
|
||||
}
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onAudiosChosen(p0: MutableList<ChosenAudio>?) {
|
||||
Timber.v("On audios chosen $p0")
|
||||
/**
|
||||
* Start the login screen with identity server and home server pre-filled
|
||||
*/
|
||||
private fun startLoginActivity() {
|
||||
val intent = LoginActivity.newIntent(this, null)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onFilesChosen(p0: MutableList<ChosenFile>?) {
|
||||
Timber.v("On files chosen $p0")
|
||||
}
|
||||
|
||||
override fun onImagesChosen(p0: MutableList<ChosenImage>?) {
|
||||
Timber.v("On images chosen $p0")
|
||||
}
|
||||
|
||||
override fun onError(p0: String?) {
|
||||
Timber.v("On error")
|
||||
}
|
||||
|
||||
override fun onVideosChosen(p0: MutableList<ChosenVideo>?) {
|
||||
Timber.v("On videos chosen $p0")
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.share
|
||||
|
||||
import android.os.Parcelable
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
||||
sealed class SharedData: Parcelable {
|
||||
|
||||
@Parcelize
|
||||
data class Text(val text: String): SharedData()
|
||||
|
||||
@Parcelize
|
||||
data class Attachments(val attachmentData: List<ContentAttachmentData>): SharedData()
|
||||
|
||||
}
|
43
vector/src/main/res/layout/activity_incoming_share.xml
Normal file
43
vector/src/main/res/layout/activity_incoming_share.xml
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout 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="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/incomingShareToolbar"
|
||||
style="@style/VectorToolbarStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:elevation="4dp"
|
||||
app:contentInsetStart="0dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<androidx.appcompat.widget.SearchView
|
||||
android:id="@+id/incomingShareSearchView"
|
||||
style="@style/VectorSearchView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:queryHint="@string/room_filtering_filter_hint"
|
||||
app:searchIcon="@drawable/ic_filter" />
|
||||
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/shareRoomListFragmentContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/incomingShareToolbar" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
Loading…
x
Reference in New Issue
Block a user