Attachments: preview with pager mode

This commit is contained in:
ganfra 2020-02-05 11:24:18 +01:00 committed by Benoit Marty
parent a26e959430
commit 41f1ec5d88
10 changed files with 209 additions and 169 deletions

View file

@ -1,30 +0,0 @@
/*
* Copyright 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.preview
import com.airbnb.epoxy.TypedEpoxyController
import javax.inject.Inject
class AttachmentPreviewController @Inject constructor() : TypedEpoxyController<AttachmentsPreviewViewState>() {
override fun buildModels(data: AttachmentsPreviewViewState) {
data.attachments.forEach {
}
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright 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.preview
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import javax.inject.Inject
class AttachmentBigPreviewController @Inject constructor() : TypedEpoxyController<AttachmentsPreviewViewState>() {
override fun buildModels(data: AttachmentsPreviewViewState) {
data.attachments.forEach {
attachmentBigPreviewItem {
id(it.path)
attachment(it)
}
}
}
}
class AttachmentMiniaturePreviewController @Inject constructor() : TypedEpoxyController<AttachmentsPreviewViewState>() {
interface Callback {
fun onAttachmentClicked(contentAttachmentData: ContentAttachmentData)
}
var callback: Callback? = null
override fun buildModels(data: AttachmentsPreviewViewState) {
data.attachments.forEach {
attachmentMiniaturePreviewItem {
id(it.path)
attachment(it)
clickListener { _ ->
callback?.onAttachmentClicked(it)
}
}
}
}
}

View file

@ -1,58 +0,0 @@
/*
* Copyright 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.preview
import android.view.View
import android.widget.ImageView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
import kotlinx.android.synthetic.main.item_attachment_preview.view.*
@EpoxyModelClass(layout = R.layout.item_attachment_preview)
abstract class AttachmentPreviewItem : VectorEpoxyModel<AttachmentPreviewItem.Holder>() {
@EpoxyAttribute lateinit var attachment: ContentAttachmentData
@EpoxyAttribute var clickListener: View.OnClickListener? = null
override fun bind(holder: Holder) {
holder.view.setOnClickListener(clickListener)
// If name is empty, use userId as name and force it being centered
val mimeType = attachment.mimeType
val path = attachment.path
if (mimeType != null && (mimeType.startsWith("image") || mimeType.startsWith("video"))) {
Glide.with(holder.view.context)
.asBitmap()
.load(path)
.apply(RequestOptions().frame(0))
.into(holder.imageView)
} else {
holder.imageView.attachmentPreviewImageView.setImageResource(R.drawable.filetype_attachment)
holder.imageView.attachmentPreviewImageView.scaleType = ImageView.ScaleType.FIT_CENTER
}
}
class Holder : VectorEpoxyHolder() {
val imageView by bind<ImageView>(R.id.attachmentPreviewImageView)
}
}

View file

@ -0,0 +1,82 @@
/*
* Copyright 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.preview
import android.view.View
import android.widget.ImageView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
abstract class AttachmentPreviewItem<H : AttachmentPreviewItem.Holder> : VectorEpoxyModel<H>() {
abstract val attachment: ContentAttachmentData
override fun bind(holder: H) {
val path = attachment.path
if (attachment.type == ContentAttachmentData.Type.VIDEO || attachment.type == ContentAttachmentData.Type.IMAGE) {
Glide.with(holder.view.context)
.asBitmap()
.load(path)
.apply(RequestOptions().frame(0))
.into(holder.imageView)
} else {
holder.imageView.setImageResource(R.drawable.filetype_attachment)
holder.imageView.scaleType = ImageView.ScaleType.FIT_CENTER
}
}
abstract class Holder : VectorEpoxyHolder() {
abstract val imageView: ImageView
}
}
@EpoxyModelClass(layout = R.layout.item_attachment_miniature_preview)
abstract class AttachmentMiniaturePreviewItem : AttachmentPreviewItem<AttachmentMiniaturePreviewItem.Holder>() {
@EpoxyAttribute override lateinit var attachment: ContentAttachmentData
@EpoxyAttribute
var clickListener: View.OnClickListener? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.view.setOnClickListener(clickListener)
}
class Holder : AttachmentPreviewItem.Holder() {
override val imageView: ImageView
get() = miniatureImageView
private val miniatureImageView by bind<ImageView>(R.id.attachmentMiniatureImageView)
}
}
@EpoxyModelClass(layout = R.layout.item_attachment_big_preview)
abstract class AttachmentBigPreviewItem : AttachmentPreviewItem<AttachmentBigPreviewItem.Holder>() {
@EpoxyAttribute override lateinit var attachment: ContentAttachmentData
class Holder : AttachmentPreviewItem.Holder() {
override val imageView: ImageView
get() = bigImageView
private val bigImageView by bind<ImageView>(R.id.attachmentBigImageView)
}
}

View file

@ -19,6 +19,8 @@ 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.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.addFragment import im.vector.riotx.core.extensions.addFragment
@ -30,9 +32,10 @@ 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 REQUEST_CODE = 55
fun newIntent(context: Context, args: AttachmentsPreviewArgs): Intent { fun newIntent(context: Context, args: AttachmentsPreviewArgs): Intent {
return Intent(context, AttachmentsPreviewArgs::class.java).apply { return Intent(context, AttachmentsPreviewActivity::class.java).apply {
putExtra(EXTRA_FRAGMENT_ARGS, args) putExtra(EXTRA_FRAGMENT_ARGS, args)
} }
} }
@ -40,6 +43,11 @@ class AttachmentsPreviewActivity : VectorBaseActivity(), ToolbarConfigurable {
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

@ -20,13 +20,14 @@ package im.vector.riotx.features.attachments.preview
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.View import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
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 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.extensions.configureWith
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
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.*
@ -39,8 +40,9 @@ data class AttachmentsPreviewArgs(
class AttachmentsPreviewFragment @Inject constructor( class AttachmentsPreviewFragment @Inject constructor(
val viewModelFactory: AttachmentsPreviewViewModel.Factory, val viewModelFactory: AttachmentsPreviewViewModel.Factory,
private val attachmentPreviewController: AttachmentPreviewController private val attachmentMiniaturePreviewController: AttachmentMiniaturePreviewController,
) : VectorBaseFragment() { private val attachmentBigPreviewController: AttachmentBigPreviewController
) : VectorBaseFragment(), AttachmentMiniaturePreviewController.Callback {
private val fragmentArgs: AttachmentsPreviewArgs by args() private val fragmentArgs: AttachmentsPreviewArgs by args()
private val viewModel: AttachmentsPreviewViewModel by fragmentViewModel() private val viewModel: AttachmentsPreviewViewModel by fragmentViewModel()
@ -49,19 +51,36 @@ 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)
setupRecyclerView() setupRecyclerViews()
setupToolbar(attachmentPreviewerToolbar)
} }
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
attachmentPreviewerList.cleanup() attachmentPreviewerMiniatureList.cleanup()
} }
private fun setupRecyclerView() { private fun setupRecyclerViews() {
attachmentPreviewerList.configureWith(attachmentPreviewController, hasFixedSize = true) attachmentPreviewerMiniatureList.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
attachmentPreviewerMiniatureList.setHasFixedSize(true)
attachmentPreviewerMiniatureList.adapter = attachmentMiniaturePreviewController.adapter
attachmentMiniaturePreviewController.callback = this
attachmentPreviewerBigList.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
val snapHelper = PagerSnapHelper()
snapHelper.attachToRecyclerView(attachmentPreviewerBigList)
attachmentPreviewerBigList.setHasFixedSize(true)
attachmentPreviewerBigList.adapter = attachmentBigPreviewController.adapter
} }
override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
attachmentMiniaturePreviewController.setData(state)
attachmentBigPreviewController.setData(state)
}
override fun onAttachmentClicked(contentAttachmentData: ContentAttachmentData) {
} }
} }

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.preview.AttachmentsPreviewActivity
import im.vector.riotx.features.attachments.preview.AttachmentsPreviewArgs
import im.vector.riotx.features.command.Command import im.vector.riotx.features.command.Command
import im.vector.riotx.features.crypto.util.toImageRes import im.vector.riotx.features.crypto.util.toImageRes
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
@ -1337,7 +1339,8 @@ class RoomDetailFragment @Inject constructor(
// AttachmentsHelper.Callback // AttachmentsHelper.Callback
override fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>) { override fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>) {
roomDetailViewModel.handle(RoomDetailAction.SendMedia(attachments)) val intent = AttachmentsPreviewActivity.newIntent(requireContext(), AttachmentsPreviewArgs(attachments))
startActivityForResult(intent, AttachmentsPreviewActivity.REQUEST_CODE)
} }
override fun onAttachmentsProcessFailed() { override fun onAttachmentsProcessFailed() {

View file

@ -1,57 +1,32 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">
<android.support.v7.widget.Toolbar <androidx.recyclerview.widget.RecyclerView
android:id="@+id/toolbar" android:id="@+id/attachmentPreviewerBigList"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/item_attachment_big_preview"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.Toolbar
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"
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" />
<FrameLayout
android:id="@+id/attachmentPreviewerContainer"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/attachmentPreviewerFileName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar">
<ImageView
android:id="@+id/attachmentPreviewerContainerImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:scaleType="centerInside" />
<VideoView
android:id="@+id/attachmentPreviewerVideoView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
<ImageView
android:id="@+id/attachmentPreviewerThumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
<ImageView
android:id="@+id/attachmentPreviewerVideoPlay"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:src="@drawable/ic_material_play_circle" />
</FrameLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/attachmentPreviewerList" android:id="@+id/attachmentPreviewerMiniatureList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="8dp" android:padding="8dp"
@ -59,28 +34,4 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
<TextView </androidx.constraintlayout.widget.ConstraintLayout>
android:id="@+id/attachmentPreviewerFileName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:padding="8dp"
android:textAlignment="center"
app:layout_constraintBottom_toTopOf="@id/attachmentPreviewerList"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="Filename" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/attachmentPreviewerSendButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:src="@drawable/ic_material_send_white"
app:layout_constraintBottom_toTopOf="@+id/attachmentPreviewerFileName"
app:layout_constraintEnd_toEndOf="@id/attachmentPreviewerContainer" />
</android.support.constraint.ConstraintLayout>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black">
<ImageView
android:id="@+id/attachmentBigImageView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

View file

@ -1,19 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" <androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto" xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/cardView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="2dip" android:layout_margin="2dp"
card_view:cardBackgroundColor="@android:color/transparent" card_view:cardBackgroundColor="@android:color/transparent"
card_view:cardElevation="0dp"> card_view:cardElevation="0dp">
<ImageView <ImageView
android:id="@+id/attachmentPreviewImageView" android:id="@+id/attachmentMiniatureImageView"
android:layout_width="64dp" android:layout_width="64dp"
android:layout_height="64dp" android:layout_height="64dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
tools:src="@tools:sample/backgrounds/scenic" /> tools:src="@tools:sample/backgrounds/scenic" />
</android.support.v7.widget.CardView> </androidx.cardview.widget.CardView>