mirror of
https://github.com/element-hq/element-android
synced 2024-11-25 02:45:37 +03:00
Attachment: continue working on preview screen
This commit is contained in:
parent
6471787232
commit
b7a7aa2f15
17 changed files with 385 additions and 46 deletions
|
@ -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
|
||||||
|
|
|
@ -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 -->
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() }
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,6 +504,10 @@ 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) {
|
||||||
|
AttachmentsPreviewActivity.REQUEST_CODE -> {
|
||||||
|
val sendData = AttachmentsPreviewActivity.getOutput(data)
|
||||||
|
roomDetailViewModel.handle(RoomDetailAction.SendMedia(sendData))
|
||||||
|
}
|
||||||
REACTION_SELECT_REQUEST_CODE -> {
|
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,9 +1348,16 @@ 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()
|
||||||
|
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)
|
startActivityForResult(intent, AttachmentsPreviewActivity.REQUEST_CODE)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onAttachmentsProcessFailed() {
|
override fun onAttachmentsProcessFailed() {
|
||||||
Toast.makeText(requireContext(), R.string.error_attachment, Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireContext(), R.string.error_attachment, Toast.LENGTH_SHORT).show()
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,43 @@
|
||||||
<?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.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/attachmentPreviewerBottomContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="200dp"
|
||||||
|
android:background="#80000000"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/attachmentPreviewerMiniatureList"
|
android:id="@+id/attachmentPreviewerMiniatureList"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
@ -35,3 +45,15 @@
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</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>
|
||||||
|
|
19
vector/src/main/res/menu/vector_attachments_preview.xml
Executable file
19
vector/src/main/res/menu/vector_attachments_preview.xml
Executable 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>
|
16
vector/src/main/res/values-v21/theme_common.xml
Normal file
16
vector/src/main/res/values-v21/theme_common.xml
Normal 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>
|
|
@ -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>
|
Loading…
Reference in a new issue