Attachment: continue working on preview screen

This commit is contained in:
Ganard 2020-02-06 18:42:18 +01:00 committed by Benoit Marty
parent 6471787232
commit b7a7aa2f15
17 changed files with 385 additions and 46 deletions

View file

@ -29,6 +29,7 @@ data class ContentAttachmentData(
val width: Long? = 0, val width: Long? = 0,
val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED, val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
val name: String? = null, val name: String? = null,
val queryUri: String,
val path: String, val path: String,
val mimeType: String?, val mimeType: String?,
val type: Type val type: Type

View file

@ -143,7 +143,10 @@
<activity <activity
android:name="com.yalantis.ucrop.UCropActivity" android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
<activity android:name=".features.attachments.preview.AttachmentsPreviewActivity" />
<activity
android:name=".features.attachments.preview.AttachmentsPreviewActivity"
android:theme="@style/AppTheme.AttachmentsPreview" />
<!-- Services --> <!-- Services -->

View file

@ -0,0 +1,76 @@
/*
* Copyright (c) 2020 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.core.utils
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SnapHelper
interface OnSnapPositionChangeListener {
fun onSnapPositionChange(position: Int)
}
fun RecyclerView.attachSnapHelperWithListener(
snapHelper: SnapHelper,
behavior: SnapOnScrollListener.Behavior = SnapOnScrollListener.Behavior.NOTIFY_ON_SCROLL_STATE_IDLE,
onSnapPositionChangeListener: OnSnapPositionChangeListener) {
snapHelper.attachToRecyclerView(this)
val snapOnScrollListener = SnapOnScrollListener(snapHelper, behavior, onSnapPositionChangeListener)
addOnScrollListener(snapOnScrollListener)
}
fun SnapHelper.getSnapPosition(recyclerView: RecyclerView): Int {
val layoutManager = recyclerView.layoutManager ?: return RecyclerView.NO_POSITION
val snapView = findSnapView(layoutManager) ?: return RecyclerView.NO_POSITION
return layoutManager.getPosition(snapView)
}
class SnapOnScrollListener(
private val snapHelper: SnapHelper,
var behavior: Behavior = Behavior.NOTIFY_ON_SCROLL,
var onSnapPositionChangeListener: OnSnapPositionChangeListener? = null
) : RecyclerView.OnScrollListener() {
enum class Behavior {
NOTIFY_ON_SCROLL,
NOTIFY_ON_SCROLL_STATE_IDLE
}
private var snapPosition = RecyclerView.NO_POSITION
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (behavior == Behavior.NOTIFY_ON_SCROLL) {
maybeNotifySnapPositionChange(recyclerView)
}
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (behavior == Behavior.NOTIFY_ON_SCROLL_STATE_IDLE
&& newState == RecyclerView.SCROLL_STATE_IDLE) {
maybeNotifySnapPositionChange(recyclerView)
}
}
private fun maybeNotifySnapPositionChange(recyclerView: RecyclerView) {
val snapPosition = snapHelper.getSnapPosition(recyclerView)
val snapPositionChanged = this.snapPosition != snapPosition
if (snapPositionChanged) {
onSnapPositionChangeListener?.onSnapPositionChange(snapPosition)
this.snapPosition = snapPosition
}
}
}

View file

@ -37,7 +37,8 @@ fun ChosenFile.toContentAttachmentData(): ContentAttachmentData {
type = mapType(), type = mapType(),
size = size, size = size,
date = createdAt?.time ?: System.currentTimeMillis(), date = createdAt?.time ?: System.currentTimeMillis(),
name = displayName name = displayName,
queryUri = queryUri
) )
} }
@ -50,7 +51,8 @@ fun ChosenAudio.toContentAttachmentData(): ContentAttachmentData {
size = size, size = size,
date = createdAt?.time ?: System.currentTimeMillis(), date = createdAt?.time ?: System.currentTimeMillis(),
name = displayName, name = displayName,
duration = duration duration = duration,
queryUri = queryUri
) )
} }
@ -74,7 +76,8 @@ fun ChosenImage.toContentAttachmentData(): ContentAttachmentData {
height = height.toLong(), height = height.toLong(),
width = width.toLong(), width = width.toLong(),
exifOrientation = orientation, exifOrientation = orientation,
date = createdAt?.time ?: System.currentTimeMillis() date = createdAt?.time ?: System.currentTimeMillis(),
queryUri = queryUri
) )
} }
@ -89,6 +92,7 @@ fun ChosenVideo.toContentAttachmentData(): ContentAttachmentData {
height = height.toLong(), height = height.toLong(),
width = width.toLong(), width = width.toLong(),
duration = duration, duration = duration,
name = displayName name = displayName,
queryUri = queryUri
) )
} }

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2020 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.matrix.android.api.session.content.ContentAttachmentData
fun ContentAttachmentData.isPreviewable(): Boolean {
return type == ContentAttachmentData.Type.IMAGE || type == ContentAttachmentData.Type.VIDEO
}
fun List<ContentAttachmentData>.filterPreviewables(): List<ContentAttachmentData> {
return filter { it.isPreviewable() }
}
fun List<ContentAttachmentData>.filterNonPreviewables(): List<ContentAttachmentData> {
return filter { it.isPreviewable().not() }
}

View file

@ -35,18 +35,18 @@ class AttachmentBigPreviewController @Inject constructor() : TypedEpoxyControlle
class AttachmentMiniaturePreviewController @Inject constructor() : TypedEpoxyController<AttachmentsPreviewViewState>() { class AttachmentMiniaturePreviewController @Inject constructor() : TypedEpoxyController<AttachmentsPreviewViewState>() {
interface Callback { interface Callback {
fun onAttachmentClicked(contentAttachmentData: ContentAttachmentData) fun onAttachmentClicked(position: Int, contentAttachmentData: ContentAttachmentData)
} }
var callback: Callback? = null var callback: Callback? = null
override fun buildModels(data: AttachmentsPreviewViewState) { override fun buildModels(data: AttachmentsPreviewViewState) {
data.attachments.forEach { data.attachments.forEachIndexed { index, contentAttachmentData ->
attachmentMiniaturePreviewItem { attachmentMiniaturePreviewItem {
id(it.path) id(contentAttachmentData.path)
attachment(it) attachment(contentAttachmentData)
clickListener { _ -> clickListener { _ ->
callback?.onAttachmentClicked(it) callback?.onAttachmentClicked(index, contentAttachmentData)
} }
} }
} }

View file

@ -19,4 +19,10 @@ package im.vector.riotx.features.attachments.preview
import im.vector.riotx.core.platform.VectorViewModelAction import im.vector.riotx.core.platform.VectorViewModelAction
sealed class AttachmentsPreviewAction : VectorViewModelAction sealed class AttachmentsPreviewAction : VectorViewModelAction {
object RemoveCurrentAttachment : AttachmentsPreviewAction()
data class SetCurrentAttachment(val index: Int): AttachmentsPreviewAction()
data class UpdatePathOfCurrentAttachment(val newPath: String): AttachmentsPreviewAction()
}

View file

@ -19,19 +19,20 @@ package im.vector.riotx.features.attachments.preview
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle
import android.view.WindowManager
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.addFragment import im.vector.riotx.core.extensions.addFragment
import im.vector.riotx.core.platform.ToolbarConfigurable import im.vector.riotx.core.platform.ToolbarConfigurable
import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.features.themes.ActivityOtherThemes
class AttachmentsPreviewActivity : VectorBaseActivity(), ToolbarConfigurable { class AttachmentsPreviewActivity : VectorBaseActivity(), ToolbarConfigurable {
companion object { companion object {
private const val EXTRA_FRAGMENT_ARGS = "EXTRA_FRAGMENT_ARGS" private const val EXTRA_FRAGMENT_ARGS = "EXTRA_FRAGMENT_ARGS"
const val RESULT_NAME = "ATTACHMENTS_PREVIEW_RESULT"
const val REQUEST_CODE = 55 const val REQUEST_CODE = 55
fun newIntent(context: Context, args: AttachmentsPreviewArgs): Intent { fun newIntent(context: Context, args: AttachmentsPreviewArgs): Intent {
@ -39,15 +40,16 @@ class AttachmentsPreviewActivity : VectorBaseActivity(), ToolbarConfigurable {
putExtra(EXTRA_FRAGMENT_ARGS, args) putExtra(EXTRA_FRAGMENT_ARGS, args)
} }
} }
fun getOutput(intent: Intent): List<ContentAttachmentData> {
return intent.getParcelableArrayListExtra(RESULT_NAME)
}
} }
override fun getOtherThemes() = ActivityOtherThemes.AttachmentsPreview
override fun getLayoutRes() = R.layout.activity_simple override fun getLayoutRes() = R.layout.activity_simple
override fun onCreate(savedInstanceState: Bundle?) {
window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
super.onCreate(savedInstanceState)
}
override fun initUiAndData() { override fun initUiAndData() {
if (isFirstCreation()) { if (isFirstCreation()) {
val fragmentArgs: AttachmentsPreviewArgs = intent?.extras?.getParcelable(EXTRA_FRAGMENT_ARGS) val fragmentArgs: AttachmentsPreviewArgs = intent?.extras?.getParcelable(EXTRA_FRAGMENT_ARGS)

View file

@ -17,24 +17,41 @@
package im.vector.riotx.features.attachments.preview package im.vector.riotx.features.attachments.preview
import android.app.Activity.RESULT_CANCELED
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.net.toUri
import androidx.core.view.ViewCompat
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper import androidx.recyclerview.widget.PagerSnapHelper
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.yalantis.ucrop.UCrop
import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.utils.OnSnapPositionChangeListener
import im.vector.riotx.core.utils.SnapOnScrollListener
import im.vector.riotx.core.utils.attachSnapHelperWithListener
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_attachments_preview.* import kotlinx.android.synthetic.main.fragment_attachments_preview.*
import timber.log.Timber
import java.io.File
import javax.inject.Inject import javax.inject.Inject
@Parcelize @Parcelize
data class AttachmentsPreviewArgs( data class AttachmentsPreviewArgs(
val roomId: String,
val attachments: List<ContentAttachmentData> val attachments: List<ContentAttachmentData>
) : Parcelable ) : Parcelable
@ -51,13 +68,107 @@ class AttachmentsPreviewFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
applyInsets()
setupRecyclerViews() setupRecyclerViews()
setupToolbar(attachmentPreviewerToolbar) setupToolbar(attachmentPreviewerToolbar)
attachmentPreviewerSendButton.setOnClickListener {
setResultAndFinish()
}
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == RESULT_OK) {
if (requestCode == UCrop.REQUEST_CROP && data != null) {
Timber.v("Crop success")
handleCropResult(data)
}
}
if (resultCode == UCrop.RESULT_ERROR) {
Timber.v("Crop error")
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.attachmentsPreviewRemoveAction -> {
handleRemoveAction()
true
}
R.id.attachmentsPreviewEditAction -> {
handleEditAction()
true
}
else -> {
super.onOptionsItemSelected(item)
}
}
}
override fun getMenuRes() = R.menu.vector_attachments_preview
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
attachmentPreviewerMiniatureList.cleanup() attachmentPreviewerMiniatureList.cleanup()
attachmentPreviewerBigList.cleanup()
}
override fun invalidate() = withState(viewModel) { state ->
if (state.attachments.isEmpty()) {
requireActivity().setResult(RESULT_CANCELED)
requireActivity().finish()
} else {
attachmentMiniaturePreviewController.setData(state)
attachmentBigPreviewController.setData(state)
attachmentPreviewerBigList.scrollToPosition(state.currentAttachmentIndex)
attachmentPreviewerMiniatureList.scrollToPosition(state.currentAttachmentIndex)
}
}
override fun onAttachmentClicked(position: Int, contentAttachmentData: ContentAttachmentData) {
viewModel.handle(AttachmentsPreviewAction.SetCurrentAttachment(position))
}
private fun setResultAndFinish() = withState(viewModel) {
val resultIntent = Intent().apply {
putParcelableArrayListExtra(AttachmentsPreviewActivity.RESULT_NAME, ArrayList(it.attachments))
}
requireActivity().setResult(RESULT_OK, resultIntent)
requireActivity().finish()
}
private fun applyInsets() {
view?.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
ViewCompat.setOnApplyWindowInsetsListener(attachmentPreviewerBottomContainer) { v, insets ->
v.updatePadding(bottom = insets.systemWindowInsetBottom)
insets
}
ViewCompat.setOnApplyWindowInsetsListener(attachmentPreviewerToolbar) { v, insets ->
v.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = insets.systemWindowInsetTop
}
insets
}
}
private fun handleCropResult(result: Intent) {
val resultPath = UCrop.getOutput(result)?.path
if (resultPath != null) {
viewModel.handle(AttachmentsPreviewAction.UpdatePathOfCurrentAttachment(resultPath))
} else {
Toast.makeText(requireContext(), "Cannot retrieve cropped value", Toast.LENGTH_SHORT).show()
}
}
private fun handleRemoveAction() {
viewModel.handle(AttachmentsPreviewAction.RemoveCurrentAttachment)
}
private fun handleEditAction() = withState(viewModel) {
val currentAttachment = it.attachments.getOrNull(it.currentAttachmentIndex) ?: return@withState
val destinationFile = File(requireContext().cacheDir, "${currentAttachment.name}_edited_image_${System.currentTimeMillis()}")
UCrop.of(currentAttachment.queryUri.toUri(), destinationFile.toUri())
.withOptions(UCrop.Options())
.start(requireContext(), this)
} }
private fun setupRecyclerViews() { private fun setupRecyclerViews() {
@ -67,20 +178,12 @@ class AttachmentsPreviewFragment @Inject constructor(
attachmentMiniaturePreviewController.callback = this attachmentMiniaturePreviewController.callback = this
attachmentPreviewerBigList.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) attachmentPreviewerBigList.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
val snapHelper = PagerSnapHelper() attachmentPreviewerBigList.attachSnapHelperWithListener(PagerSnapHelper(), SnapOnScrollListener.Behavior.NOTIFY_ON_SCROLL_STATE_IDLE, object : OnSnapPositionChangeListener {
snapHelper.attachToRecyclerView(attachmentPreviewerBigList) override fun onSnapPositionChange(position: Int) {
viewModel.handle(AttachmentsPreviewAction.SetCurrentAttachment(position))
}
})
attachmentPreviewerBigList.setHasFixedSize(true) attachmentPreviewerBigList.setHasFixedSize(true)
attachmentPreviewerBigList.adapter = attachmentBigPreviewController.adapter attachmentPreviewerBigList.adapter = attachmentBigPreviewController.adapter
} }
override fun invalidate() = withState(viewModel) { state ->
attachmentMiniaturePreviewController.setData(state)
attachmentBigPreviewController.setData(state)
}
override fun onAttachmentClicked(contentAttachmentData: ContentAttachmentData) {
}
} }

View file

@ -17,12 +17,15 @@
package im.vector.riotx.features.attachments.preview package im.vector.riotx.features.attachments.preview
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import kotlinx.coroutines.launch
class AttachmentsPreviewViewModel @AssistedInject constructor(@Assisted initialState: AttachmentsPreviewViewState) class AttachmentsPreviewViewModel @AssistedInject constructor(@Assisted initialState: AttachmentsPreviewViewState)
: VectorViewModel<AttachmentsPreviewViewState, AttachmentsPreviewAction, AttachmentsPreviewViewEvents>(initialState) { : VectorViewModel<AttachmentsPreviewViewState, AttachmentsPreviewAction, AttachmentsPreviewViewEvents>(initialState) {
@ -42,6 +45,36 @@ class AttachmentsPreviewViewModel @AssistedInject constructor(@Assisted initialS
} }
override fun handle(action: AttachmentsPreviewAction) { override fun handle(action: AttachmentsPreviewAction) {
//TODO when (action) {
is AttachmentsPreviewAction.SetCurrentAttachment -> handleSetCurrentAttachment(action)
is AttachmentsPreviewAction.UpdatePathOfCurrentAttachment -> handleUpdatePathOfCurrentAttachment(action)
AttachmentsPreviewAction.RemoveCurrentAttachment -> handleRemoveCurrentAttachment()
}.exhaustive
}
private fun handleRemoveCurrentAttachment() = withState {
val currentAttachment = it.attachments.getOrNull(it.currentAttachmentIndex) ?: return@withState
val attachments = it.attachments.minusElement(currentAttachment)
val newAttachmentIndex = it.currentAttachmentIndex.coerceAtMost(attachments.size - 1)
setState {
copy(attachments = attachments, currentAttachmentIndex = newAttachmentIndex)
}
}
private fun handleUpdatePathOfCurrentAttachment(action: AttachmentsPreviewAction.UpdatePathOfCurrentAttachment) = withState {
val attachments = it.attachments.mapIndexed { index, contentAttachmentData ->
if (index == it.currentAttachmentIndex) {
contentAttachmentData.copy(path = action.newPath)
} else {
contentAttachmentData
}
}
setState {
copy(attachments = attachments)
}
}
private fun handleSetCurrentAttachment(action: AttachmentsPreviewAction.SetCurrentAttachment) = setState {
copy(currentAttachmentIndex = action.index)
} }
} }

View file

@ -21,7 +21,8 @@ import com.airbnb.mvrx.MvRxState
import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.content.ContentAttachmentData
data class AttachmentsPreviewViewState( data class AttachmentsPreviewViewState(
val attachments: List<ContentAttachmentData> val attachments: List<ContentAttachmentData>,
val currentAttachmentIndex: Int = 0
) : MvRxState { ) : MvRxState {
constructor(args: AttachmentsPreviewArgs) : this(attachments = args.attachments) constructor(args: AttachmentsPreviewArgs) : this(attachments = args.attachments)

View file

@ -116,6 +116,8 @@ import im.vector.riotx.core.utils.toast
import im.vector.riotx.features.attachments.AttachmentTypeSelectorView import im.vector.riotx.features.attachments.AttachmentTypeSelectorView
import im.vector.riotx.features.attachments.AttachmentsHelper import im.vector.riotx.features.attachments.AttachmentsHelper
import im.vector.riotx.features.attachments.ContactAttachment import im.vector.riotx.features.attachments.ContactAttachment
import im.vector.riotx.features.attachments.filterNonPreviewables
import im.vector.riotx.features.attachments.filterPreviewables
import im.vector.riotx.features.attachments.preview.AttachmentsPreviewActivity import im.vector.riotx.features.attachments.preview.AttachmentsPreviewActivity
import im.vector.riotx.features.attachments.preview.AttachmentsPreviewArgs import im.vector.riotx.features.attachments.preview.AttachmentsPreviewArgs
import im.vector.riotx.features.command.Command import im.vector.riotx.features.command.Command
@ -502,7 +504,11 @@ class RoomDetailFragment @Inject constructor(
val hasBeenHandled = attachmentsHelper.onActivityResult(requestCode, resultCode, data) val hasBeenHandled = attachmentsHelper.onActivityResult(requestCode, resultCode, data)
if (!hasBeenHandled && resultCode == RESULT_OK && data != null) { if (!hasBeenHandled && resultCode == RESULT_OK && data != null) {
when (requestCode) { when (requestCode) {
REACTION_SELECT_REQUEST_CODE -> { AttachmentsPreviewActivity.REQUEST_CODE -> {
val sendData = AttachmentsPreviewActivity.getOutput(data)
roomDetailViewModel.handle(RoomDetailAction.SendMedia(sendData))
}
REACTION_SELECT_REQUEST_CODE -> {
val (eventId, reaction) = EmojiReactionPickerActivity.getOutput(data) ?: return val (eventId, reaction) = EmojiReactionPickerActivity.getOutput(data) ?: return
roomDetailViewModel.handle(RoomDetailAction.SendReaction(eventId, reaction)) roomDetailViewModel.handle(RoomDetailAction.SendReaction(eventId, reaction))
} }
@ -1342,8 +1348,15 @@ class RoomDetailFragment @Inject constructor(
// AttachmentsHelper.Callback // AttachmentsHelper.Callback
override fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>) { override fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>) {
val intent = AttachmentsPreviewActivity.newIntent(requireContext(), AttachmentsPreviewArgs(attachments)) val previewable = attachments.filterPreviewables()
startActivityForResult(intent, AttachmentsPreviewActivity.REQUEST_CODE) val nonPreviewable = attachments.filterNonPreviewables()
if (nonPreviewable.isNotEmpty()) {
roomDetailViewModel.handle(RoomDetailAction.SendMedia(nonPreviewable))
}
if (previewable.isNotEmpty()) {
val intent = AttachmentsPreviewActivity.newIntent(requireContext(), AttachmentsPreviewArgs(roomDetailArgs.roomId, previewable))
startActivityForResult(intent, AttachmentsPreviewActivity.REQUEST_CODE)
}
} }
override fun onAttachmentsProcessFailed() { override fun onAttachmentsProcessFailed() {

View file

@ -32,4 +32,10 @@ sealed class ActivityOtherThemes(@StyleRes val dark: Int,
R.style.AppTheme_Black, R.style.AppTheme_Black,
R.style.AppTheme_Status R.style.AppTheme_Status
) )
object AttachmentsPreview : ActivityOtherThemes(
R.style.AppTheme_AttachmentsPreview,
R.style.AppTheme_AttachmentsPreview,
R.style.AppTheme_AttachmentsPreview
)
} }

View file

@ -1,37 +1,59 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
xmlns:tools="http://schemas.android.com/tools">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/attachmentPreviewerBigList" android:id="@+id/attachmentPreviewerBigList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:listitem="@layout/item_attachment_big_preview"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/item_attachment_big_preview" />
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/attachmentPreviewerToolbar" android:id="@+id/attachmentPreviewerToolbar"
style="@style/VectorToolbarStyle" style="@style/VectorToolbarStyle"
android:background="@android:color/transparent"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:background="@android:color/transparent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/attachmentPreviewerMiniatureList" <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/attachmentPreviewerBottomContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="200dp"
android:padding="8dp" android:background="#80000000"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/attachmentPreviewerMiniatureList"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/attachmentPreviewerSendButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:src="@drawable/ic_send"
app:layout_constraintTop_toTopOf="@id/attachmentPreviewerBottomContainer"
app:layout_constraintBottom_toTopOf="@id/attachmentPreviewerBottomContainer"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".features.attachments.preview.AttachmentsPreviewActivity">
<item
android:id="@+id/attachmentsPreviewRemoveAction"
android:icon="@drawable/ic_delete"
android:title="Delete"
app:showAsAction="always" />
<item
android:id="@+id/attachmentsPreviewEditAction"
android:icon="@drawable/ic_edit"
android:title="Edit"
app:showAsAction="always" />
</menu>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Launcher Theme, only used for VectorLauncherActivity (will be use even before the Activity is started) -->
<style name="AppTheme.Launcher" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="android:windowBackground">@drawable/splash</item>
<item name="colorPrimaryDark">@color/primary_color_dark</item>
</style>
<style name="AppTheme.AttachmentsPreview" parent="AppTheme.Base.Black">
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
</resources>

View file

@ -8,4 +8,6 @@
<item name="colorPrimaryDark">@color/primary_color_dark</item> <item name="colorPrimaryDark">@color/primary_color_dark</item>
</style> </style>
<style name="AppTheme.AttachmentsPreview" parent="AppTheme.Base.Black"/>
</resources> </resources>