Attachments: start working on new UI (using system file picker) [WIP]

This commit is contained in:
ganfra 2019-10-08 19:59:09 +02:00
parent ac6aff9175
commit 3073470c38
11 changed files with 590 additions and 102 deletions

View file

@ -0,0 +1,50 @@
/*
* 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 im.vector.riotx.core.resources.MIME_TYPE_ALL_CONTENT
data class Attachment(val path: String,
val mimeType: String,
val name: String? = "",
val width: Long? = 0,
val height: Long? = 0,
val size: Long = 0,
val duration: Long? = 0,
val date: Long = 0) {
val type: Int
get() {
if (mimeType == null) {
return TYPE_FILE
}
return when {
mimeType.startsWith("image/") -> TYPE_IMAGE
mimeType.startsWith("video/") -> TYPE_VIDEO
mimeType.startsWith("audio/")
-> TYPE_AUDIO
else -> TYPE_FILE
}
}
companion object {
val TYPE_FILE = 0
val TYPE_IMAGE = 1
val TYPE_AUDIO = 2
val TYPE_VIDEO = 3
}
}

View file

@ -0,0 +1,200 @@
/*
* 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.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.annotation.TargetApi
import android.content.Context
import android.graphics.drawable.BitmapDrawable
import android.os.Build
import android.util.Pair
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewAnimationUtils
import android.view.animation.Animation
import android.view.animation.AnimationSet
import android.view.animation.OvershootInterpolator
import android.view.animation.ScaleAnimation
import android.view.animation.TranslateAnimation
import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.PopupWindow
import androidx.core.view.doOnNextLayout
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import im.vector.riotx.R
import kotlin.math.max
class AttachmentTypeSelectorView(context: Context, var callback: Callback?)
: PopupWindow(context) {
interface Callback {
fun onTypeSelected(type: Int)
}
private val iconColorGenerator = ColorGenerator.MATERIAL
private var galleryButton: ImageButton
private var cameraButton: ImageButton
private var fileButton: ImageButton
private var stickersButton: ImageButton
private var anchor: View? = null
init {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val layout = inflater.inflate(R.layout.attachment_type_selector, null, true)
galleryButton = layout.findViewById<ImageButton>(R.id.attachmentGalleryButton).configure(TYPE_GALLERY)
cameraButton = layout.findViewById<ImageButton>(R.id.attachmentCameraButton).configure(TYPE_CAMERA)
fileButton = layout.findViewById<ImageButton>(R.id.attachmentFileButton).configure(TYPE_FILE)
stickersButton = layout.findViewById<ImageButton>(R.id.attachmentStickersButton).configure(TYPE_STICKER)
contentView = layout
width = LinearLayout.LayoutParams.MATCH_PARENT
height = LinearLayout.LayoutParams.WRAP_CONTENT
setBackgroundDrawable(BitmapDrawable())
animationStyle = 0
inputMethodMode = INPUT_METHOD_NOT_NEEDED
isFocusable = true
isTouchable = true
}
fun show(anchor: View) {
showAtLocation(anchor, Gravity.BOTTOM, 0, 0)
contentView.doOnNextLayout {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
animateWindowInCircular(anchor, contentView)
} else {
animateWindowInTranslate(contentView)
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
animateButtonIn(galleryButton, ANIMATION_DURATION / 2)
animateButtonIn(cameraButton, ANIMATION_DURATION / 2)
animateButtonIn(fileButton, ANIMATION_DURATION / 4)
animateButtonIn(stickersButton, 0)
}
}
override fun dismiss() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
animateWindowOutCircular(anchor, contentView)
} else {
animateWindowOutTranslate(contentView)
}
}
private fun animateButtonIn(button: View, delay: Int) {
val animation = AnimationSet(true)
val scale = ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.0f)
animation.addAnimation(scale)
animation.interpolator = OvershootInterpolator(1f)
animation.duration = ANIMATION_DURATION.toLong()
animation.startOffset = delay.toLong()
button.startAnimation(animation)
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private fun animateWindowInCircular(anchor: View?, contentView: View) {
val coordinates = getClickCoordinates(anchor, contentView)
val animator = ViewAnimationUtils.createCircularReveal(contentView,
coordinates.first,
coordinates.second,
0f,
max(contentView.width, contentView.height).toFloat())
animator.duration = ANIMATION_DURATION.toLong()
animator.start()
}
private fun animateWindowInTranslate(contentView: View) {
val animation = TranslateAnimation(0f, 0f, contentView.height.toFloat(), 0f)
animation.duration = ANIMATION_DURATION.toLong()
getContentView().startAnimation(animation)
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private fun animateWindowOutCircular(anchor: View?, contentView: View) {
val coordinates = getClickCoordinates(anchor, contentView)
val animator = ViewAnimationUtils.createCircularReveal(getContentView(),
coordinates.first,
coordinates.second,
max(getContentView().width, getContentView().height).toFloat(),
0f)
animator.duration = ANIMATION_DURATION.toLong()
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super@AttachmentTypeSelectorView.dismiss()
}
})
animator.start()
}
private fun animateWindowOutTranslate(contentView: View) {
val animation = TranslateAnimation(0f, 0f, 0f, (contentView.top + contentView.height).toFloat())
animation.duration = ANIMATION_DURATION.toLong()
animation.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation) {}
override fun onAnimationEnd(animation: Animation) {
super@AttachmentTypeSelectorView.dismiss()
}
override fun onAnimationRepeat(animation: Animation) {}
})
getContentView().startAnimation(animation)
}
private fun getClickCoordinates(anchor: View?, contentView: View): Pair<Int, Int> {
val anchorCoordinates = IntArray(2)
anchor?.getLocationOnScreen(anchorCoordinates)
val contentCoordinates = IntArray(2)
contentView.getLocationOnScreen(contentCoordinates)
val x = anchorCoordinates[0] - contentCoordinates[0]
val y = anchorCoordinates[1] - contentCoordinates[1]
return Pair(x, y)
}
private fun ImageButton.configure(type: Int): ImageButton {
this.background = TextDrawable.builder().buildRound("", iconColorGenerator.getColor(type))
this.setOnClickListener(TypeClickListener(type))
return this
}
private inner class TypeClickListener(private val type: Int) : View.OnClickListener {
override fun onClick(v: View) {
dismiss()
callback?.onTypeSelected(type)
}
}
companion object {
const val TYPE_CAMERA = 0
const val TYPE_GALLERY = 1
const val TYPE_FILE = 2
const val TYPE_STICKER = 3
private const val ANIMATION_DURATION = 250
}
}

View file

@ -0,0 +1,152 @@
/*
* 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.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Environment
import android.provider.MediaStore
import android.provider.OpenableColumns
import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
import im.vector.riotx.BuildConfig
import im.vector.riotx.core.resources.MIME_TYPE_ALL_CONTENT
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*
class AttachmentsHelper(private val context: Context) {
private var capturePath: String? = null
fun selectFile(fragment: Fragment, requestCode: Int) {
selectMediaType(fragment, "*/*", null, requestCode)
}
fun selectGallery(fragment: Fragment, requestCode: Int) {
selectMediaType(fragment, "image/*", arrayOf("image/*", "video/*"), requestCode)
}
fun openCamera(fragment: Fragment, requestCode: Int) {
dispatchTakePictureIntent(fragment, requestCode)
}
fun handleOpenCameraResult(): List<Attachment> {
val attachment = getAttachmentFromContentResolver(Uri.parse(capturePath))
return if (attachment == null) {
emptyList()
} else {
listOf(attachment)
}
}
fun handleSelectResult(data: Intent?): List<Attachment> {
val clipData = data?.clipData
if (clipData != null) {
return (0 until clipData.itemCount).map {
clipData.getItemAt(it)
}.mapNotNull {
getAttachmentFromContentResolver(it.uri)
}
} else {
val uri = data?.data ?: return emptyList()
val attachment = getAttachmentFromContentResolver(uri)
return if (attachment == null) {
emptyList()
} else {
listOf(attachment)
}
}
}
private fun selectMediaType(fragment: Fragment, type: String, extraMimeType: Array<String>?, requestCode: Int) {
val intent = Intent()
intent.type = type
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
if (extraMimeType != null) {
intent.putExtra(Intent.EXTRA_MIME_TYPES, extraMimeType)
}
intent.action = Intent.ACTION_OPEN_DOCUMENT
try {
fragment.startActivityForResult(intent, requestCode)
return
} catch (exception: ActivityNotFoundException) {
Timber.e(exception)
}
intent.action = Intent.ACTION_GET_CONTENT
try {
fragment.startActivityForResult(intent, requestCode)
} catch (exception: ActivityNotFoundException) {
Timber.e(exception)
}
}
private fun getAttachmentFromContentResolver(uri: Uri): Attachment? {
return context.contentResolver.query(uri, null, null, null, null)?.use {
if (it.moveToFirst()) {
val fileName = it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME))
val fileSize = it.getLong(it.getColumnIndex(OpenableColumns.SIZE))
val mimeType = context.contentResolver.getType(uri) ?: MIME_TYPE_ALL_CONTENT
Attachment(uri.toString(), mimeType, fileName, fileSize)
} else {
null
}
}
}
@Throws(IOException::class)
private fun createImageFile(context: Context): File {
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
val imageFileName = "JPEG_" + timeStamp + "_"
val storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
val image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
)
// Save a file: path for use with ACTION_VIEW intents
capturePath = image.absolutePath
return image
}
private fun dispatchTakePictureIntent(fragment: Fragment, requestCode: Int) {
val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(fragment.requireActivity().packageManager) != null) {
// Create the File where the photo should go
var photoFile: File? = null
try {
photoFile = createImageFile(fragment.requireContext())
} catch (ex: IOException) {
Timber.e(ex, "Couldn't create image file")
}
// Continue only if the File was successfully created
if (photoFile != null) {
val photoURI = FileProvider.getUriForFile(fragment.requireContext(), BuildConfig.APPLICATION_ID + ".fileProvider", photoFile)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
fragment.startActivityForResult(takePictureIntent, requestCode)
}
}
}
}

View file

@ -16,17 +16,17 @@
package im.vector.riotx.features.home.room.detail
import com.jaiselrahman.filepicker.model.MediaFile
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotx.features.attachments.Attachment
sealed class RoomDetailActions {
data class SaveDraft(val draft: String) : RoomDetailActions()
data class SendMessage(val text: String, val autoMarkdown: Boolean) : RoomDetailActions()
data class SendMedia(val mediaFiles: List<MediaFile>) : RoomDetailActions()
data class SendMedia(val attachments: List<Attachment>) : RoomDetailActions()
data class TimelineEventTurnsVisible(val event: TimelineEvent) : RoomDetailActions()
data class TimelineEventTurnsInvisible(val event: TimelineEvent) : RoomDetailActions()
data class LoadMoreTimelineEvents(val direction: Timeline.Direction) : RoomDetailActions()

View file

@ -50,7 +50,6 @@ import com.github.piasy.biv.BigImageViewer
import com.github.piasy.biv.loader.ImageLoader
import com.google.android.material.snackbar.Snackbar
import com.jaiselrahman.filepicker.activity.FilePickerActivity
import com.jaiselrahman.filepicker.config.Configurations
import com.jaiselrahman.filepicker.model.MediaFile
import com.otaliastudios.autocomplete.Autocomplete
import com.otaliastudios.autocomplete.AutocompleteCallback
@ -67,7 +66,6 @@ import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.dialogs.DialogListItem
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.extensions.hideKeyboard
@ -81,6 +79,8 @@ import im.vector.riotx.core.ui.views.NotificationAreaView
import im.vector.riotx.core.utils.*
import im.vector.riotx.core.utils.Debouncer
import im.vector.riotx.core.utils.createUIHandler
import im.vector.riotx.features.attachments.AttachmentTypeSelectorView
import im.vector.riotx.features.attachments.AttachmentsHelper
import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresenter
import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy
import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter
@ -125,17 +125,18 @@ data class RoomDetailArgs(
) : Parcelable
private const val CAMERA_VALUE_TITLE = "attachment"
private const val REQUEST_FILES_REQUEST_CODE = 0
private const val TAKE_IMAGE_REQUEST_CODE = 1
private const val REACTION_SELECT_REQUEST_CODE = 2
private const val REQUEST_CODE_SELECT_FILE = 1
private const val REQUEST_CODE_SELECT_GALLERY = 2
private const val REQUEST_CODE_OPEN_CAMERA = 3
private const val REACTION_SELECT_REQUEST_CODE = 4
class RoomDetailFragment :
VectorBaseFragment(),
TimelineEventController.Callback,
AutocompleteUserPresenter.Callback,
VectorInviteView.Callback,
JumpToReadMarkerView.Callback {
JumpToReadMarkerView.Callback,
AttachmentTypeSelectorView.Callback {
companion object {
@ -197,9 +198,11 @@ class RoomDetailFragment :
private lateinit var actionViewModel: ActionsHandler
private lateinit var layoutManager: LinearLayoutManager
private lateinit var attachmentsHelper: AttachmentsHelper
@BindView(R.id.composerLayout)
lateinit var composerLayout: TextComposerView
private lateinit var attachmentTypeSelector: AttachmentTypeSelectorView
private var lockSendButton = false
@ -210,6 +213,7 @@ class RoomDetailFragment :
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
actionViewModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java)
attachmentsHelper = AttachmentsHelper((requireActivity()))
setupToolbar(roomToolbar)
setupRecyclerView()
setupComposer()
@ -298,9 +302,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()
@ -362,7 +366,7 @@ class RoomDetailFragment :
private fun renderSpecialMode(event: TimelineEvent,
@DrawableRes iconRes: Int,
descriptionRes: Int,
descriptionRes: Int,
defaultContent: String) {
commandAutocompletePolicy.enabled = false
//switch to expanded bar
@ -426,23 +430,31 @@ class RoomDetailFragment :
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK && data != null) {
when (requestCode) {
REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data)
REACTION_SELECT_REQUEST_CODE -> {
val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID)
?: return
val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT)
?: return
//TODO check if already reacted with that?
roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, eventId))
if (resultCode == RESULT_OK) {
if (requestCode == REQUEST_CODE_OPEN_CAMERA) {
val attachments = attachmentsHelper.handleOpenCameraResult()
roomDetailViewModel.process(RoomDetailActions.SendMedia(attachments))
} else if (data != null) {
when (requestCode) {
REQUEST_CODE_SELECT_FILE,
REQUEST_CODE_SELECT_GALLERY -> {
val attachments = attachmentsHelper.handleSelectResult(data)
roomDetailViewModel.process(RoomDetailActions.SendMedia(attachments))
}
REACTION_SELECT_REQUEST_CODE -> {
val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID)
?: return
val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT)
?: return
//TODO check if already reacted with that?
roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, eventId))
}
}
}
}
}
// PRIVATE METHODS *****************************************************************************
// PRIVATE METHODS *****************************************************************************
private fun setupRecyclerView() {
@ -610,43 +622,10 @@ class RoomDetailFragment :
private fun setupAttachmentButton() {
composerLayout.attachmentButton.setOnClickListener {
val intent = Intent(requireContext(), FilePickerActivity::class.java)
intent.putExtra(FilePickerActivity.CONFIGS, Configurations.Builder()
.setCheckPermission(true)
.setShowFiles(true)
.setShowAudios(true)
.setSkipZeroSizeFiles(true)
.build())
startActivityForResult(intent, REQUEST_FILES_REQUEST_CODE)
/*
val items = ArrayList<DialogListItem>()
// Send file
items.add(DialogListItem.SendFile)
// Send voice
if (vectorPreferences.isSendVoiceFeatureEnabled()) {
items.add(DialogListItem.SendVoice.INSTANCE)
if (!::attachmentTypeSelector.isInitialized) {
attachmentTypeSelector = AttachmentTypeSelectorView(requireContext(), this)
}
// Send sticker
//items.add(DialogListItem.SendSticker)
// Camera
//if (vectorPreferences.useNativeCamera()) {
items.add(DialogListItem.TakePhoto)
items.add(DialogListItem.TakeVideo)
//} else {
// items.add(DialogListItem.TakePhotoVideo.INSTANCE)
// }
val adapter = DialogSendItemAdapter(requireContext(), items)
AlertDialog.Builder(requireContext())
.setAdapter(adapter) { _, position ->
onSendChoiceClicked(items[position])
}
.setNegativeButton(R.string.cancel, null)
.show()
*/
attachmentTypeSelector.show(it)
}
}
@ -654,38 +633,6 @@ class RoomDetailFragment :
inviteView.callback = this
}
private fun onSendChoiceClicked(dialogListItem: DialogListItem) {
Timber.v("On send choice clicked: $dialogListItem")
when (dialogListItem) {
is DialogListItem.SendFile -> {
// launchFileIntent
}
is DialogListItem.SendVoice -> {
//launchAudioRecorderIntent()
}
is DialogListItem.SendSticker -> {
//startStickerPickerActivity()
}
is DialogListItem.TakePhotoVideo ->
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
// launchCamera()
}
is DialogListItem.TakePhoto ->
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA)) {
openCamera(requireActivity(), CAMERA_VALUE_TITLE, TAKE_IMAGE_REQUEST_CODE)
}
is DialogListItem.TakeVideo ->
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA)) {
// launchNativeVideoRecorder()
}
}
}
private fun handleMediaIntent(data: Intent) {
val files: ArrayList<MediaFile> = data.getParcelableArrayListExtra(FilePickerActivity.MEDIA_FILES)
roomDetailViewModel.process(RoomDetailActions.SendMedia(files))
}
private fun renderState(state: RoomDetailViewState) {
readMarkerHelper.updateWith(state)
renderRoomSummary(state)
@ -973,7 +920,7 @@ class RoomDetailFragment :
}
// AutocompleteUserPresenter.Callback
// AutocompleteUserPresenter.Callback
override fun onQueryUsers(query: CharSequence?) {
textComposerViewModel.process(TextComposerActions.QueryUsers(query))
@ -1139,7 +1086,7 @@ class RoomDetailFragment :
}
// VectorInviteView.Callback
// VectorInviteView.Callback
override fun onAcceptInvite() {
notificationDrawerManager.clearMemberShipNotificationForRoom(roomDetailArgs.roomId)
@ -1151,7 +1098,7 @@ class RoomDetailFragment :
roomDetailViewModel.process(RoomDetailActions.RejectInvite)
}
// JumpToReadMarkerView.Callback
// JumpToReadMarkerView.Callback
override fun onJumpToReadMarkerClicked(readMarkerId: String) {
roomDetailViewModel.process(RoomDetailActions.NavigateToEvent(readMarkerId, false))
@ -1161,4 +1108,14 @@ class RoomDetailFragment :
roomDetailViewModel.process(RoomDetailActions.MarkAllAsRead)
}
// AttachmentTypeSelectorView.Callback *********************************************************
override fun onTypeSelected(type: Int) {
when (type) {
AttachmentTypeSelectorView.TYPE_CAMERA -> attachmentsHelper.openCamera(this, REQUEST_CODE_OPEN_CAMERA)
AttachmentTypeSelectorView.TYPE_FILE -> attachmentsHelper.selectFile(this, REQUEST_CODE_SELECT_FILE)
AttachmentTypeSelectorView.TYPE_GALLERY -> attachmentsHelper.selectGallery(this, REQUEST_CODE_SELECT_GALLERY)
AttachmentTypeSelectorView.TYPE_STICKER -> vectorBaseActivity.notImplemented("Adding stickers")
}
}
}

View file

@ -46,7 +46,6 @@ import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneCo
import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.rx.rx
@ -63,8 +62,6 @@ import im.vector.riotx.features.command.CommandParser
import im.vector.riotx.features.command.ParsedCommand
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
import im.vector.riotx.features.settings.VectorPreferences
import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import io.reactivex.rxkotlin.subscribeBy
import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer
@ -469,7 +466,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
}
private fun handleSendMedia(action: RoomDetailActions.SendMedia) {
val attachments = action.mediaFiles.map {
val attachments = action.attachments.map {
val nameWithExtension = getFilenameFromUri(null, Uri.parse(it.path))
ContentAttachmentData(
@ -481,7 +478,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
name = nameWithExtension ?: it.name,
path = it.path,
mimeType = it.mimeType,
type = ContentAttachmentData.Type.values()[it.mediaType]
type = ContentAttachmentData.Type.values()[it.type]
)
}
@ -495,7 +492,8 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} else {
when (val tooBigFile = attachments.find { it.size > maxUploadFileSize }) {
null -> room.sendMedias(attachments)
else -> _fileTooBigEvent.postValue(LiveEvent(FileTooBigError(tooBigFile.name ?: tooBigFile.path, tooBigFile.size, maxUploadFileSize)))
else -> _fileTooBigEvent.postValue(LiveEvent(FileTooBigError(tooBigFile.name
?: tooBigFile.path, tooBigFile.size, maxUploadFileSize)))
}
}
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="24dp">
<path android:fillColor="#FFFFFF" android:pathData="M9.4,10.5l4.77,-8.26C13.47,2.09 12.75,2 12,2c-2.4,0 -4.6,0.85 -6.32,2.25l3.66,6.35 0.06,-0.1zM21.54,9c-0.92,-2.92 -3.15,-5.26 -6,-6.34L11.88,9h9.66zM21.8,10h-7.49l0.29,0.5 4.76,8.25C21,16.97 22,14.61 22,12c0,-0.69 -0.07,-1.35 -0.2,-2zM8.54,12l-3.9,-6.75C3.01,7.03 2,9.39 2,12c0,0.69 0.07,1.35 0.2,2h7.49l-1.15,-2zM2.46,15c0.92,2.92 3.15,5.26 6,6.34L12.12,15L2.46,15zM13.73,15l-3.9,6.76c0.7,0.15 1.42,0.24 2.17,0.24 2.4,0 4.6,-0.85 6.32,-2.25l-3.66,-6.35 -0.93,1.6z"/>
</vector>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="24dp">
<path android:fillColor="#FFFFFF" android:pathData="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6L6,2zM13,9L13,3.5L18.5,9L13,9z"/>
</vector>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="24dp">
<path android:fillColor="#FFFFFF" android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,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,114 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/riotx_background_light">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp"
android:baselineAligned="false"
android:orientation="horizontal"
android:weightSum="4">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageButton
android:id="@+id/attachmentCameraButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:scaleType="center"
android:src="@drawable/ic_attachment_camera_white_24dp"
tools:background="@color/colorAccent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Camera" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageButton
android:id="@+id/attachmentGalleryButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:scaleType="center"
android:src="@drawable/ic_attachment_gallery_white_24dp"
tools:background="@color/colorAccent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Gallery" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageButton
android:id="@+id/attachmentFileButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:scaleType="center"
android:src="@drawable/ic_attachment_file_white_24dp"
tools:background="@color/colorAccent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="File" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageButton
android:id="@+id/attachmentStickersButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:scaleType="center"
android:src="@drawable/ic_send_sticker"
tools:background="@color/colorAccent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Stickers" />
</LinearLayout>
</LinearLayout>
</FrameLayout>

View file

@ -3,4 +3,9 @@
<cache-path
name="shared"
path="/" />
<external-path
name="external_files"
path="." />
</paths>