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 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.events.model.Event
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent 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.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotx.features.attachments.Attachment
sealed class RoomDetailActions { sealed class RoomDetailActions {
data class SaveDraft(val draft: String) : RoomDetailActions() data class SaveDraft(val draft: String) : RoomDetailActions()
data class SendMessage(val text: String, val autoMarkdown: Boolean) : 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 TimelineEventTurnsVisible(val event: TimelineEvent) : RoomDetailActions()
data class TimelineEventTurnsInvisible(val event: TimelineEvent) : RoomDetailActions() data class TimelineEventTurnsInvisible(val event: TimelineEvent) : RoomDetailActions()
data class LoadMoreTimelineEvents(val direction: Timeline.Direction) : 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.github.piasy.biv.loader.ImageLoader
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.jaiselrahman.filepicker.activity.FilePickerActivity import com.jaiselrahman.filepicker.activity.FilePickerActivity
import com.jaiselrahman.filepicker.config.Configurations
import com.jaiselrahman.filepicker.model.MediaFile import com.jaiselrahman.filepicker.model.MediaFile
import com.otaliastudios.autocomplete.Autocomplete import com.otaliastudios.autocomplete.Autocomplete
import com.otaliastudios.autocomplete.AutocompleteCallback 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.matrix.android.api.session.user.model.User
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent 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.epoxy.LayoutManagerStateRestorer
import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.extensions.hideKeyboard 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.*
import im.vector.riotx.core.utils.Debouncer import im.vector.riotx.core.utils.Debouncer
import im.vector.riotx.core.utils.createUIHandler 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.AutocompleteCommandPresenter
import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy
import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter
@ -125,17 +125,18 @@ data class RoomDetailArgs(
) : Parcelable ) : Parcelable
private const val CAMERA_VALUE_TITLE = "attachment" private const val REQUEST_CODE_SELECT_FILE = 1
private const val REQUEST_FILES_REQUEST_CODE = 0 private const val REQUEST_CODE_SELECT_GALLERY = 2
private const val TAKE_IMAGE_REQUEST_CODE = 1 private const val REQUEST_CODE_OPEN_CAMERA = 3
private const val REACTION_SELECT_REQUEST_CODE = 2 private const val REACTION_SELECT_REQUEST_CODE = 4
class RoomDetailFragment : class RoomDetailFragment :
VectorBaseFragment(), VectorBaseFragment(),
TimelineEventController.Callback, TimelineEventController.Callback,
AutocompleteUserPresenter.Callback, AutocompleteUserPresenter.Callback,
VectorInviteView.Callback, VectorInviteView.Callback,
JumpToReadMarkerView.Callback { JumpToReadMarkerView.Callback,
AttachmentTypeSelectorView.Callback {
companion object { companion object {
@ -197,9 +198,11 @@ class RoomDetailFragment :
private lateinit var actionViewModel: ActionsHandler private lateinit var actionViewModel: ActionsHandler
private lateinit var layoutManager: LinearLayoutManager private lateinit var layoutManager: LinearLayoutManager
private lateinit var attachmentsHelper: AttachmentsHelper
@BindView(R.id.composerLayout) @BindView(R.id.composerLayout)
lateinit var composerLayout: TextComposerView lateinit var composerLayout: TextComposerView
private lateinit var attachmentTypeSelector: AttachmentTypeSelectorView
private var lockSendButton = false private var lockSendButton = false
@ -210,6 +213,7 @@ class RoomDetailFragment :
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
actionViewModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java) actionViewModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java)
attachmentsHelper = AttachmentsHelper((requireActivity()))
setupToolbar(roomToolbar) setupToolbar(roomToolbar)
setupRecyclerView() setupRecyclerView()
setupComposer() setupComposer()
@ -298,9 +302,9 @@ class RoomDetailFragment :
AlertDialog.Builder(requireActivity()) AlertDialog.Builder(requireActivity())
.setTitle(R.string.dialog_title_error) .setTitle(R.string.dialog_title_error)
.setMessage(getString(R.string.error_file_too_big, .setMessage(getString(R.string.error_file_too_big,
error.filename, error.filename,
TextUtils.formatFileSize(requireContext(), error.fileSizeInBytes), TextUtils.formatFileSize(requireContext(), error.fileSizeInBytes),
TextUtils.formatFileSize(requireContext(), error.homeServerLimitInBytes) TextUtils.formatFileSize(requireContext(), error.homeServerLimitInBytes)
)) ))
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)
.show() .show()
@ -362,7 +366,7 @@ class RoomDetailFragment :
private fun renderSpecialMode(event: TimelineEvent, private fun renderSpecialMode(event: TimelineEvent,
@DrawableRes iconRes: Int, @DrawableRes iconRes: Int,
descriptionRes: Int, descriptionRes: Int,
defaultContent: String) { defaultContent: String) {
commandAutocompletePolicy.enabled = false commandAutocompletePolicy.enabled = false
//switch to expanded bar //switch to expanded bar
@ -426,23 +430,31 @@ class RoomDetailFragment :
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) if (resultCode == RESULT_OK) {
if (resultCode == RESULT_OK && data != null) { if (requestCode == REQUEST_CODE_OPEN_CAMERA) {
when (requestCode) { val attachments = attachmentsHelper.handleOpenCameraResult()
REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data) roomDetailViewModel.process(RoomDetailActions.SendMedia(attachments))
REACTION_SELECT_REQUEST_CODE -> { } else if (data != null) {
val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID) when (requestCode) {
?: return REQUEST_CODE_SELECT_FILE,
val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT) REQUEST_CODE_SELECT_GALLERY -> {
?: return val attachments = attachmentsHelper.handleSelectResult(data)
//TODO check if already reacted with that? roomDetailViewModel.process(RoomDetailActions.SendMedia(attachments))
roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, eventId)) }
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() { private fun setupRecyclerView() {
@ -610,43 +622,10 @@ class RoomDetailFragment :
private fun setupAttachmentButton() { private fun setupAttachmentButton() {
composerLayout.attachmentButton.setOnClickListener { composerLayout.attachmentButton.setOnClickListener {
val intent = Intent(requireContext(), FilePickerActivity::class.java) if (!::attachmentTypeSelector.isInitialized) {
intent.putExtra(FilePickerActivity.CONFIGS, Configurations.Builder() attachmentTypeSelector = AttachmentTypeSelectorView(requireContext(), this)
.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)
} }
attachmentTypeSelector.show(it)
// 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()
*/
} }
} }
@ -654,38 +633,6 @@ class RoomDetailFragment :
inviteView.callback = this 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) { private fun renderState(state: RoomDetailViewState) {
readMarkerHelper.updateWith(state) readMarkerHelper.updateWith(state)
renderRoomSummary(state) renderRoomSummary(state)
@ -973,7 +920,7 @@ class RoomDetailFragment :
} }
// AutocompleteUserPresenter.Callback // AutocompleteUserPresenter.Callback
override fun onQueryUsers(query: CharSequence?) { override fun onQueryUsers(query: CharSequence?) {
textComposerViewModel.process(TextComposerActions.QueryUsers(query)) textComposerViewModel.process(TextComposerActions.QueryUsers(query))
@ -1139,7 +1086,7 @@ class RoomDetailFragment :
} }
// VectorInviteView.Callback // VectorInviteView.Callback
override fun onAcceptInvite() { override fun onAcceptInvite() {
notificationDrawerManager.clearMemberShipNotificationForRoom(roomDetailArgs.roomId) notificationDrawerManager.clearMemberShipNotificationForRoom(roomDetailArgs.roomId)
@ -1151,7 +1098,7 @@ class RoomDetailFragment :
roomDetailViewModel.process(RoomDetailActions.RejectInvite) roomDetailViewModel.process(RoomDetailActions.RejectInvite)
} }
// JumpToReadMarkerView.Callback // JumpToReadMarkerView.Callback
override fun onJumpToReadMarkerClicked(readMarkerId: String) { override fun onJumpToReadMarkerClicked(readMarkerId: String) {
roomDetailViewModel.process(RoomDetailActions.NavigateToEvent(readMarkerId, false)) roomDetailViewModel.process(RoomDetailActions.NavigateToEvent(readMarkerId, false))
@ -1161,4 +1108,14 @@ class RoomDetailFragment :
roomDetailViewModel.process(RoomDetailActions.MarkAllAsRead) 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.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings 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.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.attachments.toElementToDecrypt
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.rx.rx 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.command.ParsedCommand
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.settings.VectorPreferences
import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
import org.commonmark.parser.Parser import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer import org.commonmark.renderer.html.HtmlRenderer
@ -469,7 +466,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} }
private fun handleSendMedia(action: RoomDetailActions.SendMedia) { private fun handleSendMedia(action: RoomDetailActions.SendMedia) {
val attachments = action.mediaFiles.map { val attachments = action.attachments.map {
val nameWithExtension = getFilenameFromUri(null, Uri.parse(it.path)) val nameWithExtension = getFilenameFromUri(null, Uri.parse(it.path))
ContentAttachmentData( ContentAttachmentData(
@ -481,7 +478,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
name = nameWithExtension ?: it.name, name = nameWithExtension ?: it.name,
path = it.path, path = it.path,
mimeType = it.mimeType, 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 { } else {
when (val tooBigFile = attachments.find { it.size > maxUploadFileSize }) { when (val tooBigFile = attachments.find { it.size > maxUploadFileSize }) {
null -> room.sendMedias(attachments) 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 <cache-path
name="shared" name="shared"
path="/" /> path="/" />
<external-path
name="external_files"
path="." />
</paths> </paths>