Uploads: add screen - WIP
This commit is contained in:
parent
0992e76800
commit
e9ca876444
@ -220,3 +220,20 @@ fun Event.isImageMessage(): Boolean {
|
|||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Event.isVideoMessage(): Boolean {
|
||||||
|
return getClearType() == EventType.MESSAGE
|
||||||
|
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
||||||
|
MessageType.MSGTYPE_VIDEO -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Event.isPreviewableMessage(): Boolean {
|
||||||
|
return getClearType() == EventType.MESSAGE
|
||||||
|
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
||||||
|
MessageType.MSGTYPE_IMAGE,
|
||||||
|
MessageType.MSGTYPE_VIDEO -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,7 +19,7 @@ package im.vector.matrix.android.api.session.room.uploads
|
|||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
|
||||||
data class GetUploadsResult(
|
data class GetUploadsResult(
|
||||||
// List of fetched Events
|
// List of fetched Events, most recent first
|
||||||
val events: List<Event>,
|
val events: List<Event>,
|
||||||
// token to get more events, or null if there is no more event to fetch
|
// token to get more events, or null if there is no more event to fetch
|
||||||
val nextToken: String?
|
val nextToken: String?
|
||||||
|
@ -38,7 +38,9 @@ internal class DefaultUploadsService @AssistedInject constructor(
|
|||||||
|
|
||||||
override fun getUploads(numberOfEvents: Int, since: String?, callback: MatrixCallback<GetUploadsResult>): Cancelable {
|
override fun getUploads(numberOfEvents: Int, since: String?, callback: MatrixCallback<GetUploadsResult>): Cancelable {
|
||||||
return getUploadsTask
|
return getUploadsTask
|
||||||
.configureWith(GetUploadsTask.Params(roomId, numberOfEvents, since))
|
.configureWith(GetUploadsTask.Params(roomId, numberOfEvents, since)) {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ internal class DefaultGetUploadsTask @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return GetUploadsResult(
|
return GetUploadsResult(
|
||||||
events = chunk.events, // reverse?
|
events = chunk.events,
|
||||||
nextToken = chunk.end?.takeIf { it != chunk.start }
|
nextToken = chunk.end?.takeIf { it != chunk.start }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -77,8 +77,8 @@ import im.vector.riotx.features.roomprofile.RoomProfileFragment
|
|||||||
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
|
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
|
||||||
import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment
|
import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment
|
||||||
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsFragment
|
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsFragment
|
||||||
import im.vector.riotx.features.roomprofile.uploads.child.RoomUploadsFilesFragment
|
import im.vector.riotx.features.roomprofile.uploads.files.RoomUploadsFilesFragment
|
||||||
import im.vector.riotx.features.roomprofile.uploads.child.RoomUploadsMediaFragment
|
import im.vector.riotx.features.roomprofile.uploads.media.RoomUploadsMediaFragment
|
||||||
import im.vector.riotx.features.settings.VectorSettingsAdvancedNotificationPreferenceFragment
|
import im.vector.riotx.features.settings.VectorSettingsAdvancedNotificationPreferenceFragment
|
||||||
import im.vector.riotx.features.settings.VectorSettingsHelpAboutFragment
|
import im.vector.riotx.features.settings.VectorSettingsHelpAboutFragment
|
||||||
import im.vector.riotx.features.settings.VectorSettingsLabsFragment
|
import im.vector.riotx.features.settings.VectorSettingsLabsFragment
|
||||||
|
@ -14,20 +14,13 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.roomprofile.uploads.child
|
package im.vector.riotx.core.epoxy
|
||||||
|
|
||||||
import com.airbnb.mvrx.parentFragmentViewModel
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
|
||||||
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewModel
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
/**
|
@EpoxyModelClass(layout = R.layout.item_loading_square)
|
||||||
* A placeholder fragment containing a simple view.
|
abstract class SquareLoadingItem : VectorEpoxyModel<SquareLoadingItem.Holder>() {
|
||||||
*/
|
|
||||||
class RoomUploadsFilesFragment @Inject constructor() : VectorBaseFragment() {
|
|
||||||
|
|
||||||
private val uploadsViewModel by parentFragmentViewModel(RoomUploadsViewModel::class)
|
class Holder : VectorEpoxyHolder()
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
|
||||||
}
|
}
|
@ -36,4 +36,8 @@ class DimensionConverter @Inject constructor(val resources: Resources) {
|
|||||||
resources.displayMetrics
|
resources.displayMetrics
|
||||||
).toInt()
|
).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun pdToDp(px: Int): Int {
|
||||||
|
return (px.toFloat() / resources.displayMetrics.density).toInt()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ import android.content.DialogInterface
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
@ -30,12 +29,10 @@ import android.view.HapticFeedbackConstants
|
|||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.Window
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.app.ActivityOptionsCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.core.text.buildSpannedString
|
import androidx.core.text.buildSpannedString
|
||||||
@ -150,7 +147,6 @@ import im.vector.riotx.features.html.EventHtmlRenderer
|
|||||||
import im.vector.riotx.features.html.PillImageSpan
|
import im.vector.riotx.features.html.PillImageSpan
|
||||||
import im.vector.riotx.features.invite.VectorInviteView
|
import im.vector.riotx.features.invite.VectorInviteView
|
||||||
import im.vector.riotx.features.media.ImageContentRenderer
|
import im.vector.riotx.features.media.ImageContentRenderer
|
||||||
import im.vector.riotx.features.media.ImageMediaViewerActivity
|
|
||||||
import im.vector.riotx.features.media.VideoContentRenderer
|
import im.vector.riotx.features.media.VideoContentRenderer
|
||||||
import im.vector.riotx.features.media.VideoMediaViewerActivity
|
import im.vector.riotx.features.media.VideoMediaViewerActivity
|
||||||
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
||||||
@ -998,31 +994,14 @@ class RoomDetailFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onImageMessageClicked(messageImageContent: MessageImageInfoContent, mediaData: ImageContentRenderer.Data, view: View) {
|
override fun onImageMessageClicked(messageImageContent: MessageImageInfoContent, mediaData: ImageContentRenderer.Data, view: View) {
|
||||||
// TODO Use navigator
|
navigator.openImageViewer(requireActivity(), mediaData, view) { pairs ->
|
||||||
|
|
||||||
val intent = ImageMediaViewerActivity.newIntent(vectorBaseActivity, mediaData, ViewCompat.getTransitionName(view))
|
|
||||||
val pairs = ArrayList<Pair<View, String>>()
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
requireActivity().window.decorView.findViewById<View>(android.R.id.statusBarBackground)?.let {
|
|
||||||
pairs.add(Pair(it, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME))
|
|
||||||
}
|
|
||||||
requireActivity().window.decorView.findViewById<View>(android.R.id.navigationBarBackground)?.let {
|
|
||||||
pairs.add(Pair(it, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pairs.add(Pair(view, ViewCompat.getTransitionName(view) ?: ""))
|
|
||||||
pairs.add(Pair(roomToolbar, ViewCompat.getTransitionName(roomToolbar) ?: ""))
|
pairs.add(Pair(roomToolbar, ViewCompat.getTransitionName(roomToolbar) ?: ""))
|
||||||
pairs.add(Pair(composerLayout, ViewCompat.getTransitionName(composerLayout) ?: ""))
|
pairs.add(Pair(composerLayout, ViewCompat.getTransitionName(composerLayout) ?: ""))
|
||||||
|
}
|
||||||
val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
|
||||||
requireActivity(), *pairs.toTypedArray()).toBundle()
|
|
||||||
startActivity(intent, bundle)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View) {
|
override fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View) {
|
||||||
// TODO Use navigator
|
navigator.openVideoViewer(requireActivity(), mediaData)
|
||||||
val intent = VideoMediaViewerActivity.newIntent(vectorBaseActivity, mediaData)
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFileMessageClicked(eventId: String, messageFileContent: MessageFileContent) {
|
override fun onFileMessageClicked(eventId: String, messageFileContent: MessageFileContent) {
|
||||||
|
@ -65,6 +65,17 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
|||||||
STICKER
|
STICKER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For gallery
|
||||||
|
*/
|
||||||
|
fun render(data: Data, imageView: ImageView, size: Int) {
|
||||||
|
// a11y
|
||||||
|
imageView.contentDescription = data.filename
|
||||||
|
|
||||||
|
createGlideRequest(data, Mode.THUMBNAIL, imageView, Size(size, size))
|
||||||
|
.into(imageView)
|
||||||
|
}
|
||||||
|
|
||||||
fun render(data: Data, mode: Mode, imageView: ImageView) {
|
fun render(data: Data, mode: Mode, imageView: ImageView) {
|
||||||
val size = processSize(data, mode)
|
val size = processSize(data, mode)
|
||||||
imageView.layoutParams.width = size.width
|
imageView.layoutParams.width = size.width
|
||||||
|
@ -19,9 +19,12 @@ package im.vector.riotx.features.navigation
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.Window
|
||||||
import androidx.core.app.ActivityOptionsCompat
|
import androidx.core.app.ActivityOptionsCompat
|
||||||
import androidx.core.app.TaskStackBuilder
|
import androidx.core.app.TaskStackBuilder
|
||||||
|
import androidx.core.util.Pair
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction
|
import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction
|
||||||
@ -45,6 +48,10 @@ import im.vector.riotx.features.home.room.detail.RoomDetailArgs
|
|||||||
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
|
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
|
||||||
import im.vector.riotx.features.invite.InviteUsersToRoomActivity
|
import im.vector.riotx.features.invite.InviteUsersToRoomActivity
|
||||||
import im.vector.riotx.features.media.BigImageViewerActivity
|
import im.vector.riotx.features.media.BigImageViewerActivity
|
||||||
|
import im.vector.riotx.features.media.ImageContentRenderer
|
||||||
|
import im.vector.riotx.features.media.ImageMediaViewerActivity
|
||||||
|
import im.vector.riotx.features.media.VideoContentRenderer
|
||||||
|
import im.vector.riotx.features.media.VideoMediaViewerActivity
|
||||||
import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
|
import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
|
||||||
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity
|
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity
|
||||||
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewActivity
|
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewActivity
|
||||||
@ -215,6 +222,29 @@ class DefaultNavigator @Inject constructor(
|
|||||||
fragment.startActivityForResult(intent, requestCode)
|
fragment.startActivityForResult(intent, requestCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun openImageViewer(activity: Activity, mediaData: ImageContentRenderer.Data, view: View, options: ((MutableList<Pair<View, String>>) -> Unit)?) {
|
||||||
|
val intent = ImageMediaViewerActivity.newIntent(activity, mediaData, ViewCompat.getTransitionName(view))
|
||||||
|
val pairs = ArrayList<Pair<View, String>>()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
activity.window.decorView.findViewById<View>(android.R.id.statusBarBackground)?.let {
|
||||||
|
pairs.add(Pair(it, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME))
|
||||||
|
}
|
||||||
|
activity.window.decorView.findViewById<View>(android.R.id.navigationBarBackground)?.let {
|
||||||
|
pairs.add(Pair(it, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pairs.add(Pair(view, ViewCompat.getTransitionName(view) ?: ""))
|
||||||
|
options?.invoke(pairs)
|
||||||
|
|
||||||
|
val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, *pairs.toTypedArray()).toBundle()
|
||||||
|
activity.startActivity(intent, bundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun openVideoViewer(activity: Activity, mediaData: VideoContentRenderer.Data) {
|
||||||
|
val intent = VideoMediaViewerActivity.newIntent(activity, mediaData)
|
||||||
|
activity.startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) {
|
private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) {
|
||||||
if (buildTask) {
|
if (buildTask) {
|
||||||
val stackBuilder = TaskStackBuilder.create(context)
|
val stackBuilder = TaskStackBuilder.create(context)
|
||||||
|
@ -19,10 +19,13 @@ package im.vector.riotx.features.navigation
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.core.util.Pair
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||||
import im.vector.matrix.android.api.session.terms.TermsService
|
import im.vector.matrix.android.api.session.terms.TermsService
|
||||||
import im.vector.matrix.android.api.util.MatrixItem
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
|
import im.vector.riotx.features.media.ImageContentRenderer
|
||||||
|
import im.vector.riotx.features.media.VideoContentRenderer
|
||||||
import im.vector.riotx.features.settings.VectorSettingsActivity
|
import im.vector.riotx.features.settings.VectorSettingsActivity
|
||||||
import im.vector.riotx.features.share.SharedData
|
import im.vector.riotx.features.share.SharedData
|
||||||
import im.vector.riotx.features.terms.ReviewTermsActivity
|
import im.vector.riotx.features.terms.ReviewTermsActivity
|
||||||
@ -76,4 +79,8 @@ interface Navigator {
|
|||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
token: String?,
|
token: String?,
|
||||||
requestCode: Int = ReviewTermsActivity.TERMS_REQUEST_CODE)
|
requestCode: Int = ReviewTermsActivity.TERMS_REQUEST_CODE)
|
||||||
|
|
||||||
|
fun openImageViewer(activity: Activity, mediaData: ImageContentRenderer.Data, view: View, options: ((MutableList<Pair<View, String>>) -> Unit)?)
|
||||||
|
|
||||||
|
fun openVideoViewer(activity: Activity, mediaData: VideoContentRenderer.Data)
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,12 @@
|
|||||||
|
|
||||||
package im.vector.riotx.features.roomprofile.uploads
|
package im.vector.riotx.features.roomprofile.uploads
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
sealed class RoomUploadsAction : VectorViewModelAction
|
sealed class RoomUploadsAction : VectorViewModelAction {
|
||||||
|
data class Download(val event: Event) : RoomUploadsAction()
|
||||||
|
data class Share(val event: Event) : RoomUploadsAction()
|
||||||
|
|
||||||
|
object Retry : RoomUploadsAction()
|
||||||
|
}
|
||||||
|
@ -21,6 +21,7 @@ import android.view.View
|
|||||||
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.google.android.material.tabs.TabLayoutMediator
|
||||||
import im.vector.matrix.android.api.util.toMatrixItem
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
@ -45,11 +46,17 @@ class RoomUploadsFragment @Inject constructor(
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val sectionsPagerAdapter = RoomUploadsPagerAdapter(childFragmentManager, stringProvider)
|
val sectionsPagerAdapter = RoomUploadsPagerAdapter(this)
|
||||||
view_pager.adapter = sectionsPagerAdapter
|
roomUploadsViewPager.adapter = sectionsPagerAdapter
|
||||||
tabs.setupWithViewPager(view_pager)
|
|
||||||
|
|
||||||
setupToolbar(matrixProfileToolbar)
|
TabLayoutMediator(roomUploadsTabs, roomUploadsViewPager) { tab, position ->
|
||||||
|
when (position) {
|
||||||
|
0 -> tab.text = stringProvider.getString(R.string.uploads_media_title)
|
||||||
|
1 -> tab.text = stringProvider.getString(R.string.uploads_files_title)
|
||||||
|
}
|
||||||
|
}.attach()
|
||||||
|
|
||||||
|
setupToolbar(roomUploadsToolbar)
|
||||||
|
|
||||||
// Initialize your view, subscribe to viewModel...
|
// Initialize your view, subscribe to viewModel...
|
||||||
}
|
}
|
||||||
@ -68,8 +75,8 @@ class RoomUploadsFragment @Inject constructor(
|
|||||||
|
|
||||||
private fun renderRoomSummary(state: RoomUploadsViewState) {
|
private fun renderRoomSummary(state: RoomUploadsViewState) {
|
||||||
state.roomSummary()?.let {
|
state.roomSummary()?.let {
|
||||||
matrixProfileToolbarTitleView.text = it.displayName
|
roomUploadsToolbarTitleView.text = it.displayName
|
||||||
avatarRenderer.render(it.toMatrixItem(), matrixProfileToolbarAvatarImageView)
|
avatarRenderer.render(it.toMatrixItem(), roomUploadsToolbarAvatarImageView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,43 +17,21 @@
|
|||||||
package im.vector.riotx.features.roomprofile.uploads
|
package im.vector.riotx.features.roomprofile.uploads
|
||||||
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import androidx.fragment.app.FragmentPagerAdapter
|
import im.vector.riotx.features.roomprofile.uploads.files.RoomUploadsFilesFragment
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.features.roomprofile.uploads.media.RoomUploadsMediaFragment
|
||||||
import im.vector.riotx.core.resources.StringProvider
|
|
||||||
import im.vector.riotx.features.roomprofile.uploads.child.RoomUploadsFilesFragment
|
|
||||||
import im.vector.riotx.features.roomprofile.uploads.child.RoomUploadsMediaFragment
|
|
||||||
|
|
||||||
private val TAB_TITLES = arrayOf(
|
|
||||||
R.string.uploads_title_media,
|
|
||||||
R.string.uploads_title_files
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A [FragmentPagerAdapter] that returns a fragment corresponding to
|
|
||||||
* one of the sections/tabs/pages.
|
|
||||||
*/
|
|
||||||
class RoomUploadsPagerAdapter(
|
class RoomUploadsPagerAdapter(
|
||||||
fm: FragmentManager,
|
private val fragment: Fragment
|
||||||
private val stringProvider: StringProvider
|
) : FragmentStateAdapter(fragment) {
|
||||||
) : FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
|
||||||
|
|
||||||
override fun getItem(position: Int): Fragment {
|
override fun getItemCount() = 2
|
||||||
// getItem is called to instantiate the fragment for the given page.
|
|
||||||
// Return a PlaceholderFragment (defined as a static inner class below).
|
override fun createFragment(position: Int): Fragment {
|
||||||
return if (position == 0) {
|
return if (position == 0) {
|
||||||
RoomUploadsMediaFragment()
|
fragment.childFragmentManager.fragmentFactory.instantiate(fragment.requireContext().classLoader, RoomUploadsMediaFragment::class.java.name)
|
||||||
} else {
|
} else {
|
||||||
RoomUploadsFilesFragment()
|
fragment.childFragmentManager.fragmentFactory.instantiate(fragment.requireContext().classLoader, RoomUploadsFilesFragment::class.java.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPageTitle(position: Int): CharSequence? {
|
|
||||||
return stringProvider.getString(TAB_TITLES[position])
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCount(): Int {
|
|
||||||
// Show 2 total pages.
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,10 @@ 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.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.api.session.events.model.isPreviewableMessage
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
import im.vector.matrix.android.api.session.room.uploads.GetUploadsResult
|
import im.vector.matrix.android.api.session.room.uploads.GetUploadsResult
|
||||||
import im.vector.matrix.android.internal.util.awaitCallback
|
import im.vector.matrix.android.internal.util.awaitCallback
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
@ -90,10 +94,15 @@ class RoomUploadsViewModel @AssistedInject constructor(
|
|||||||
|
|
||||||
token = result.nextToken
|
token = result.nextToken
|
||||||
|
|
||||||
|
val groupedEvents = result.events
|
||||||
|
.filter { it.getClearType() == EventType.MESSAGE && it.getClearContent()?.toModel<MessageContent>() != null }
|
||||||
|
.groupBy { it.isPreviewableMessage() }
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
asyncEventsRequest = Uninitialized,
|
asyncEventsRequest = Uninitialized,
|
||||||
events = this.events + result.events,
|
mediaEvents = this.mediaEvents + groupedEvents[true].orEmpty(),
|
||||||
|
fileEvents = this.fileEvents + groupedEvents[false].orEmpty(),
|
||||||
hasMore = result.nextToken != null
|
hasMore = result.nextToken != null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* 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.roomprofile.uploads.files
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import com.airbnb.mvrx.parentFragmentViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.riotx.R
|
||||||
|
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.features.roomprofile.uploads.RoomUploadsAction
|
||||||
|
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewModel
|
||||||
|
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomUploadsFilesFragment @Inject constructor(
|
||||||
|
private val controller: UploadsFileController
|
||||||
|
) : VectorBaseFragment(), UploadsFileController.Listener {
|
||||||
|
|
||||||
|
private val uploadsViewModel by parentFragmentViewModel(RoomUploadsViewModel::class)
|
||||||
|
|
||||||
|
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
recyclerView.configureWith(controller, showDivider = true)
|
||||||
|
controller.listener = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
recyclerView.cleanup()
|
||||||
|
controller.listener = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOpenClicked(event: Event) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRetry() {
|
||||||
|
uploadsViewModel.handle(RoomUploadsAction.Retry)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDownloadClicked(event: Event) {
|
||||||
|
uploadsViewModel.handle(RoomUploadsAction.Download(event))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onShareClicked(event: Event) {
|
||||||
|
uploadsViewModel.handle(RoomUploadsAction.Share(event))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(uploadsViewModel) { state ->
|
||||||
|
controller.setData(state)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* 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.roomprofile.uploads.files
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.epoxy.errorWithRetryItem
|
||||||
|
import im.vector.riotx.core.epoxy.loadingItem
|
||||||
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
|
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewState
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class UploadsFileController @Inject constructor(
|
||||||
|
private val errorFormatter: ErrorFormatter,
|
||||||
|
colorProvider: ColorProvider
|
||||||
|
) : TypedEpoxyController<RoomUploadsViewState>() {
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun onRetry()
|
||||||
|
fun onOpenClicked(event: Event)
|
||||||
|
fun onDownloadClicked(event: Event)
|
||||||
|
fun onShareClicked(event: Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
|
||||||
|
|
||||||
|
var listener: Listener? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
setData(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun buildModels(data: RoomUploadsViewState?) {
|
||||||
|
data ?: return
|
||||||
|
|
||||||
|
if (data.fileEvents.isEmpty()) {
|
||||||
|
when (data.asyncEventsRequest) {
|
||||||
|
is Loading -> {
|
||||||
|
loadingItem {
|
||||||
|
id("loading")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Fail -> {
|
||||||
|
errorWithRetryItem {
|
||||||
|
id("error")
|
||||||
|
text(errorFormatter.toHumanReadable(data.asyncEventsRequest.error))
|
||||||
|
listener { listener?.onRetry() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buildFileItems(data.fileEvents)
|
||||||
|
|
||||||
|
if (data.hasMore) {
|
||||||
|
loadingItem {
|
||||||
|
id("loadMore")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildFileItems(fileEvents: List<Event>) {
|
||||||
|
fileEvents.forEach {
|
||||||
|
uploadsFileItem {
|
||||||
|
id(it.eventId ?: "")
|
||||||
|
title(it.getClearType())
|
||||||
|
subtitle(it.getSenderKey())
|
||||||
|
listener(object : UploadsFileItem.Listener {
|
||||||
|
override fun onItemClicked() {
|
||||||
|
listener?.onOpenClicked(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDownloadClicked() {
|
||||||
|
listener?.onDownloadClicked(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onShareClicked() {
|
||||||
|
listener?.onShareClicked(it)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* 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.roomprofile.uploads.files
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.riotx.core.extensions.setTextOrHide
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_uploads_file)
|
||||||
|
abstract class UploadsFileItem : VectorEpoxyModel<UploadsFileItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute var title: String? = null
|
||||||
|
@EpoxyAttribute var subtitle: String? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute var listener: Listener? = null
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
holder.view.setOnClickListener { listener?.onItemClicked() }
|
||||||
|
holder.titleView.text = title
|
||||||
|
holder.subtitleView.setTextOrHide(subtitle)
|
||||||
|
holder.downloadView.setOnClickListener { listener?.onDownloadClicked() }
|
||||||
|
holder.shareView.setOnClickListener { listener?.onShareClicked() }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val titleView by bind<TextView>(R.id.uploadsFileTitle)
|
||||||
|
val subtitleView by bind<TextView>(R.id.uploadsFileSubtitle)
|
||||||
|
val downloadView by bind<View>(R.id.uploadsFileActionDownload)
|
||||||
|
val shareView by bind<View>(R.id.uploadsFileActionShare)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun onItemClicked()
|
||||||
|
fun onDownloadClicked()
|
||||||
|
fun onShareClicked()
|
||||||
|
}
|
||||||
|
}
|
@ -14,20 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.roomprofile.uploads.child
|
package im.vector.riotx.features.roomprofile.uploads.media
|
||||||
|
|
||||||
import com.airbnb.mvrx.parentFragmentViewModel
|
// Min image size. Size will be adjusted at runtime
|
||||||
import im.vector.riotx.R
|
const val IMAGE_SIZE_DP = 120
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
|
||||||
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewModel
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A placeholder fragment containing a simple view.
|
|
||||||
*/
|
|
||||||
class RoomUploadsMediaFragment @Inject constructor() : VectorBaseFragment() {
|
|
||||||
|
|
||||||
private val uploadsViewModel by parentFragmentViewModel(RoomUploadsViewModel::class)
|
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
|
||||||
}
|
|
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* 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.roomprofile.uploads.media
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.DisplayMetrics
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import com.airbnb.mvrx.parentFragmentViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.riotx.core.utils.DimensionConverter
|
||||||
|
import im.vector.riotx.features.media.ImageContentRenderer
|
||||||
|
import im.vector.riotx.features.media.VideoContentRenderer
|
||||||
|
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsAction
|
||||||
|
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewModel
|
||||||
|
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomUploadsMediaFragment @Inject constructor(
|
||||||
|
private val controller: UploadsMediaController,
|
||||||
|
private val dimensionConverter: DimensionConverter
|
||||||
|
) : VectorBaseFragment(), UploadsMediaController.Listener {
|
||||||
|
|
||||||
|
private val uploadsViewModel by parentFragmentViewModel(RoomUploadsViewModel::class)
|
||||||
|
|
||||||
|
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
recyclerView.layoutManager = GridLayoutManager(context, getNumberOfColumns())
|
||||||
|
recyclerView.adapter = controller.adapter
|
||||||
|
recyclerView.setHasFixedSize(true)
|
||||||
|
|
||||||
|
controller.listener = this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getNumberOfColumns(): Int {
|
||||||
|
val displayMetrics = DisplayMetrics()
|
||||||
|
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||||
|
return dimensionConverter.pdToDp(displayMetrics.widthPixels) / IMAGE_SIZE_DP
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
recyclerView.cleanup()
|
||||||
|
controller.listener = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOpenImageClicked(view: View, mediaData: ImageContentRenderer.Data) {
|
||||||
|
navigator.openImageViewer(requireActivity(), mediaData, view, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOpenVideoClicked(view: View, mediaData: VideoContentRenderer.Data) {
|
||||||
|
navigator.openVideoViewer(requireActivity(), mediaData)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRetry() {
|
||||||
|
uploadsViewModel.handle(RoomUploadsAction.Retry)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(uploadsViewModel) { state ->
|
||||||
|
controller.setData(state)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.roomprofile.uploads.media
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.riotx.features.media.ImageContentRenderer
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_uploads_image)
|
||||||
|
abstract class UploadsImageItem : VectorEpoxyModel<UploadsImageItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute lateinit var imageContentRenderer: ImageContentRenderer
|
||||||
|
@EpoxyAttribute lateinit var data: ImageContentRenderer.Data
|
||||||
|
|
||||||
|
@EpoxyAttribute var listener: Listener? = null
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
holder.view.setOnClickListener { listener?.onItemClicked(holder.imageView, data) }
|
||||||
|
imageContentRenderer.render(data, holder.imageView, IMAGE_SIZE_DP)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val imageView by bind<ImageView>(R.id.uploadsImagePreview)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun onItemClicked(view: View, data: ImageContentRenderer.Data)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
* 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.roomprofile.uploads.media
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.session.events.model.isImageMessage
|
||||||
|
import im.vector.matrix.android.api.session.events.model.isVideoMessage
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.getFileUrl
|
||||||
|
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.epoxy.errorWithRetryItem
|
||||||
|
import im.vector.riotx.core.epoxy.squareLoadingItem
|
||||||
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
|
import im.vector.riotx.core.utils.DimensionConverter
|
||||||
|
import im.vector.riotx.features.media.ImageContentRenderer
|
||||||
|
import im.vector.riotx.features.media.VideoContentRenderer
|
||||||
|
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewState
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class UploadsMediaController @Inject constructor(
|
||||||
|
private val errorFormatter: ErrorFormatter,
|
||||||
|
private val imageContentRenderer: ImageContentRenderer,
|
||||||
|
private val dimensionConverter: DimensionConverter,
|
||||||
|
colorProvider: ColorProvider
|
||||||
|
) : TypedEpoxyController<RoomUploadsViewState>() {
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun onRetry()
|
||||||
|
fun onOpenImageClicked(view: View, mediaData: ImageContentRenderer.Data)
|
||||||
|
fun onOpenVideoClicked(view: View, mediaData: VideoContentRenderer.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
|
||||||
|
|
||||||
|
var listener: Listener? = null
|
||||||
|
|
||||||
|
private val itemSize = dimensionConverter.dpToPx(64)
|
||||||
|
|
||||||
|
init {
|
||||||
|
setData(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun buildModels(data: RoomUploadsViewState?) {
|
||||||
|
data ?: return
|
||||||
|
|
||||||
|
if (data.mediaEvents.isEmpty()) {
|
||||||
|
when (data.asyncEventsRequest) {
|
||||||
|
is Loading -> {
|
||||||
|
squareLoadingItem {
|
||||||
|
id("loading")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Fail -> {
|
||||||
|
errorWithRetryItem {
|
||||||
|
id("error")
|
||||||
|
text(errorFormatter.toHumanReadable(data.asyncEventsRequest.error))
|
||||||
|
listener { listener?.onRetry() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buildMediaItems(data.mediaEvents)
|
||||||
|
|
||||||
|
if (data.hasMore) {
|
||||||
|
squareLoadingItem {
|
||||||
|
id("loadMore")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildMediaItems(mediaEvents: List<Event>) {
|
||||||
|
mediaEvents.forEach { event ->
|
||||||
|
when {
|
||||||
|
event.isImageMessage() -> {
|
||||||
|
val data = event.toImageContentRendererData() ?: return@forEach
|
||||||
|
uploadsImageItem {
|
||||||
|
id(event.eventId ?: "")
|
||||||
|
imageContentRenderer(imageContentRenderer)
|
||||||
|
data(data)
|
||||||
|
listener(object : UploadsImageItem.Listener {
|
||||||
|
override fun onItemClicked(view: View, data: ImageContentRenderer.Data) {
|
||||||
|
listener?.onOpenImageClicked(view, data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
event.isVideoMessage() -> {
|
||||||
|
val data = event.toVideoContentRendererData() ?: return@forEach
|
||||||
|
uploadsVideoItem {
|
||||||
|
id(event.eventId ?: "")
|
||||||
|
imageContentRenderer(imageContentRenderer)
|
||||||
|
data(data)
|
||||||
|
listener(object : UploadsVideoItem.Listener {
|
||||||
|
override fun onItemClicked(view: View, data: VideoContentRenderer.Data) {
|
||||||
|
listener?.onOpenVideoClicked(view, data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Event.toImageContentRendererData(): ImageContentRenderer.Data? {
|
||||||
|
val messageContent = getClearContent()?.toModel<MessageImageContent>() ?: return null
|
||||||
|
|
||||||
|
return ImageContentRenderer.Data(
|
||||||
|
eventId = eventId ?: "",
|
||||||
|
filename = messageContent.body,
|
||||||
|
url = messageContent.getFileUrl(),
|
||||||
|
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
|
||||||
|
height = messageContent.info?.height,
|
||||||
|
maxHeight = itemSize,
|
||||||
|
width = messageContent.info?.width,
|
||||||
|
maxWidth = itemSize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Event.toVideoContentRendererData(): VideoContentRenderer.Data? {
|
||||||
|
val messageContent = getClearContent()?.toModel<MessageVideoContent>() ?: return null
|
||||||
|
|
||||||
|
val thumbnailData = ImageContentRenderer.Data(
|
||||||
|
eventId = eventId ?: "",
|
||||||
|
filename = messageContent.body,
|
||||||
|
url = messageContent.videoInfo?.thumbnailFile?.url ?: messageContent.videoInfo?.thumbnailUrl,
|
||||||
|
elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
|
||||||
|
height = messageContent.videoInfo?.height,
|
||||||
|
maxHeight = itemSize,
|
||||||
|
width = messageContent.videoInfo?.width,
|
||||||
|
maxWidth = itemSize
|
||||||
|
)
|
||||||
|
|
||||||
|
return VideoContentRenderer.Data(
|
||||||
|
eventId = eventId ?: "",
|
||||||
|
filename = messageContent.body,
|
||||||
|
url = messageContent.getFileUrl(),
|
||||||
|
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
|
||||||
|
thumbnailMediaData = thumbnailData
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* 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.roomprofile.uploads.media
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.riotx.features.media.ImageContentRenderer
|
||||||
|
import im.vector.riotx.features.media.VideoContentRenderer
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_uploads_video)
|
||||||
|
abstract class UploadsVideoItem : VectorEpoxyModel<UploadsVideoItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute lateinit var imageContentRenderer: ImageContentRenderer
|
||||||
|
@EpoxyAttribute lateinit var data: VideoContentRenderer.Data
|
||||||
|
|
||||||
|
@EpoxyAttribute var listener: Listener? = null
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
holder.view.setOnClickListener { listener?.onItemClicked(holder.imageView, data) }
|
||||||
|
imageContentRenderer.render(data.thumbnailMediaData, holder.imageView, IMAGE_SIZE_DP)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val imageView by bind<ImageView>(R.id.uploadsVideoPreview)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun onItemClicked(view: View, data: VideoContentRenderer.Data)
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,7 @@
|
|||||||
android:elevation="4dp">
|
android:elevation="4dp">
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@+id/matrixProfileToolbar"
|
android:id="@+id/roomUploadsToolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
@ -20,12 +20,12 @@
|
|||||||
app:layout_collapseMode="pin">
|
app:layout_collapseMode="pin">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/matrixProfileToolbarContainer"
|
android:id="@+id/roomUploadsToolbarContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/matrixProfileToolbarAvatarImageView"
|
android:id="@+id/roomUploadsToolbarAvatarImageView"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
@ -36,17 +36,17 @@
|
|||||||
tools:src="@tools:sample/avatars" />
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/matrixProfileDecorationToolbarAvatarImageView"
|
android:id="@+id/roomUploadsDecorationToolbarAvatarImageView"
|
||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="24dp"
|
android:layout_height="24dp"
|
||||||
app:layout_constraintCircle="@+id/matrixProfileToolbarAvatarImageView"
|
app:layout_constraintCircle="@+id/roomUploadsToolbarAvatarImageView"
|
||||||
app:layout_constraintCircleAngle="135"
|
app:layout_constraintCircleAngle="135"
|
||||||
app:layout_constraintCircleRadius="20dp"
|
app:layout_constraintCircleRadius="20dp"
|
||||||
tools:ignore="MissingConstraints"
|
tools:ignore="MissingConstraints"
|
||||||
tools:src="@drawable/ic_shield_trusted" />
|
tools:src="@drawable/ic_shield_trusted" />
|
||||||
|
|
||||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||||
android:id="@+id/matrixProfileToolbarTitleView"
|
android:id="@+id/roomUploadsToolbarTitleView"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
@ -57,7 +57,7 @@
|
|||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@+id/matrixProfileToolbarAvatarImageView"
|
app:layout_constraintStart_toEndOf="@+id/roomUploadsToolbarAvatarImageView"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:text="@sample/matrix.json/data/roomName" />
|
tools:text="@sample/matrix.json/data/roomName" />
|
||||||
|
|
||||||
@ -66,18 +66,18 @@
|
|||||||
</androidx.appcompat.widget.Toolbar>
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
<com.google.android.material.tabs.TabLayout
|
||||||
android:id="@+id/tabs"
|
android:id="@+id/roomUploadsTabs"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/matrixProfileToolbarAvatarImageView"
|
app:layout_constraintTop_toBottomOf="@+id/roomUploadsToolbarAvatarImageView"
|
||||||
app:tabGravity="fill"
|
app:tabGravity="fill"
|
||||||
app:tabMaxWidth="0dp"
|
app:tabMaxWidth="0dp"
|
||||||
app:tabMode="fixed" />
|
app:tabMode="fixed" />
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.viewpager.widget.ViewPager
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
android:id="@+id/view_pager"
|
android:id="@+id/roomUploadsViewPager"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
|
23
vector/src/main/res/layout/item_loading_square.xml
Normal file
23
vector/src/main/res/layout/item_loading_square.xml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="H,1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
75
vector/src/main/res/layout/item_uploads_file.xml
Normal file
75
vector/src/main/res/layout/item_uploads_file.xml
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout 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"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:foreground="?selectableItemBackground"
|
||||||
|
android:minHeight="64dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/uploadsFileIcon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||||
|
android:src="@drawable/ic_file"
|
||||||
|
android:tint="?riotx_text_primary"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||||
|
android:id="@+id/uploadsFileTitle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/uploadsFileSubtitle"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/uploadsFileActionDownload"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/uploadsFileIcon"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_chainStyle="packed"
|
||||||
|
tools:text="Filename.file" />
|
||||||
|
|
||||||
|
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||||
|
android:id="@+id/uploadsFileSubtitle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:textColor="?riotx_text_secondary"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/uploadsFileTitle"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/uploadsFileTitle"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/uploadsFileTitle"
|
||||||
|
tools:text="Username at 12:00 on 01/01/01" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/uploadsFileActionDownload"
|
||||||
|
android:layout_width="@dimen/layout_touch_size"
|
||||||
|
android:layout_height="@dimen/layout_touch_size"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:src="@drawable/ic_download"
|
||||||
|
android:tint="?colorAccent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/uploadsFileActionShare"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/uploadsFileActionShare"
|
||||||
|
android:layout_width="@dimen/layout_touch_size"
|
||||||
|
android:layout_height="@dimen/layout_touch_size"
|
||||||
|
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:src="@drawable/ic_material_share"
|
||||||
|
android:tint="?colorAccent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
22
vector/src/main/res/layout/item_uploads_image.xml
Normal file
22
vector/src/main/res/layout/item_uploads_image.xml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout 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"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:foreground="?selectableItemBackground">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/uploadsImagePreview"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_margin="2dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="H,1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@tools:sample/backgrounds/scenic" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
31
vector/src/main/res/layout/item_uploads_video.xml
Normal file
31
vector/src/main/res/layout/item_uploads_video.xml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout 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"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:foreground="?selectableItemBackground">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/uploadsVideoPreview"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_margin="2dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="H,1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@tools:sample/backgrounds/scenic" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_material_play_circle"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
x
Reference in New Issue
Block a user