From 3073470c386d6b323e58beabd1f0b4fe949e6ca9 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 8 Oct 2019 19:59:09 +0200 Subject: [PATCH] Attachments: start working on new UI (using system file picker) [WIP] --- .../riotx/features/attachments/Attachment.kt | 50 +++++ .../attachments/AttachmentTypeSelectorView.kt | 200 ++++++++++++++++++ .../features/attachments/AttachmentsHelper.kt | 152 +++++++++++++ .../home/room/detail/RoomDetailActions.kt | 4 +- .../home/room/detail/RoomDetailFragment.kt | 145 +++++-------- .../home/room/detail/RoomDetailViewModel.kt | 10 +- .../ic_attachment_camera_white_24dp.xml | 4 + .../ic_attachment_file_white_24dp.xml | 4 + .../ic_attachment_gallery_white_24dp.xml | 4 + .../res/layout/attachment_type_selector.xml | 114 ++++++++++ .../src/main/res/xml/riotx_provider_paths.xml | 5 + 11 files changed, 590 insertions(+), 102 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/attachments/Attachment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/attachments/AttachmentTypeSelectorView.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt create mode 100644 vector/src/main/res/drawable/ic_attachment_camera_white_24dp.xml create mode 100644 vector/src/main/res/drawable/ic_attachment_file_white_24dp.xml create mode 100644 vector/src/main/res/drawable/ic_attachment_gallery_white_24dp.xml create mode 100644 vector/src/main/res/layout/attachment_type_selector.xml diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/Attachment.kt b/vector/src/main/java/im/vector/riotx/features/attachments/Attachment.kt new file mode 100644 index 0000000000..37640ad179 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/attachments/Attachment.kt @@ -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 + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentTypeSelectorView.kt b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentTypeSelectorView.kt new file mode 100644 index 0000000000..9acdd6ae1f --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentTypeSelectorView.kt @@ -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(R.id.attachmentGalleryButton).configure(TYPE_GALLERY) + cameraButton = layout.findViewById(R.id.attachmentCameraButton).configure(TYPE_CAMERA) + fileButton = layout.findViewById(R.id.attachmentFileButton).configure(TYPE_FILE) + stickersButton = layout.findViewById(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 { + 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 + } + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt new file mode 100644 index 0000000000..042ec7da79 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt @@ -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 { + val attachment = getAttachmentFromContentResolver(Uri.parse(capturePath)) + return if (attachment == null) { + emptyList() + } else { + listOf(attachment) + } + } + + fun handleSelectResult(data: Intent?): List { + 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?, 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) + } + } + } + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt index 886c3cdfaf..da11e0fbc3 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt @@ -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) : RoomDetailActions() + data class SendMedia(val attachments: List) : RoomDetailActions() data class TimelineEventTurnsVisible(val event: TimelineEvent) : RoomDetailActions() data class TimelineEventTurnsInvisible(val event: TimelineEvent) : RoomDetailActions() data class LoadMoreTimelineEvents(val direction: Timeline.Direction) : RoomDetailActions() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index ea5dc83997..f543868656 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -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() - // 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 = 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") + } + } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 4b8e46b0d9..e86238bd8b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -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))) } } } diff --git a/vector/src/main/res/drawable/ic_attachment_camera_white_24dp.xml b/vector/src/main/res/drawable/ic_attachment_camera_white_24dp.xml new file mode 100644 index 0000000000..5c2920d252 --- /dev/null +++ b/vector/src/main/res/drawable/ic_attachment_camera_white_24dp.xml @@ -0,0 +1,4 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_attachment_file_white_24dp.xml b/vector/src/main/res/drawable/ic_attachment_file_white_24dp.xml new file mode 100644 index 0000000000..4e6b9458f8 --- /dev/null +++ b/vector/src/main/res/drawable/ic_attachment_file_white_24dp.xml @@ -0,0 +1,4 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_attachment_gallery_white_24dp.xml b/vector/src/main/res/drawable/ic_attachment_gallery_white_24dp.xml new file mode 100644 index 0000000000..d4e68f125b --- /dev/null +++ b/vector/src/main/res/drawable/ic_attachment_gallery_white_24dp.xml @@ -0,0 +1,4 @@ + + + + diff --git a/vector/src/main/res/layout/attachment_type_selector.xml b/vector/src/main/res/layout/attachment_type_selector.xml new file mode 100644 index 0000000000..603cb9d72d --- /dev/null +++ b/vector/src/main/res/layout/attachment_type_selector.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/xml/riotx_provider_paths.xml b/vector/src/main/res/xml/riotx_provider_paths.xml index 7d3fcb2203..a802c0ff97 100644 --- a/vector/src/main/res/xml/riotx_provider_paths.xml +++ b/vector/src/main/res/xml/riotx_provider_paths.xml @@ -3,4 +3,9 @@ + + + \ No newline at end of file