MediaViewer : start adding simple way of seing image in full size. Will probably change in the future.

This commit is contained in:
ganfra 2019-03-12 08:29:49 +01:00
parent 1d4882e596
commit 157068634a
11 changed files with 160 additions and 26 deletions

View file

@ -62,6 +62,8 @@ dependencies {
def arrow_version = "0.8.2" def arrow_version = "0.8.2"
def coroutines_version = "1.0.1" def coroutines_version = "1.0.1"
def markwon_version = '3.0.0-SNAPSHOT' def markwon_version = '3.0.0-SNAPSHOT'
def big_image_viewer_version = '1.5.6'
def glide_version = '4.8.0'
implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android")
implementation project(":matrix-sdk-android-rx") implementation project(":matrix-sdk-android-rx")
@ -96,14 +98,19 @@ dependencies {
implementation "io.arrow-kt:arrow-core:$arrow_version" implementation "io.arrow-kt:arrow-core:$arrow_version"
// UI // UI
implementation 'com.github.bumptech.glide:glide:4.8.0'
kapt 'com.github.bumptech.glide:compiler:4.8.0'
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
implementation 'com.google.android.material:material:1.1.0-alpha02' implementation 'com.google.android.material:material:1.1.0-alpha02'
implementation 'me.gujun.android:span:1.7' implementation 'me.gujun.android:span:1.7'
implementation "ru.noties.markwon:core:$markwon_version" implementation "ru.noties.markwon:core:$markwon_version"
implementation "ru.noties.markwon:html:$markwon_version" implementation "ru.noties.markwon:html:$markwon_version"
// Image Loading
implementation "com.github.piasy:BigImageViewer:$big_image_viewer_version"
implementation "com.github.piasy:GlideImageLoader:$big_image_viewer_version"
implementation "com.github.piasy:ProgressPieIndicator:$big_image_viewer_version"
implementation "com.github.piasy:GlideImageViewFactory:$big_image_viewer_version"
implementation "com.github.bumptech.glide:glide:$glide_version"
kapt "com.github.bumptech.glide:compiler:$glide_version"
// DI // DI
implementation "org.koin:koin-android:$koin_version" implementation "org.koin:koin-android:$koin_version"

View file

@ -26,6 +26,7 @@
<activity android:name=".features.home.HomeActivity" /> <activity android:name=".features.home.HomeActivity" />
<activity android:name=".features.login.LoginActivity" /> <activity android:name=".features.login.LoginActivity" />
<activity android:name=".features.media.MediaViewerActivity" />
</application> </application>
</manifest> </manifest>

View file

@ -20,6 +20,8 @@ import android.app.Application
import android.content.Context import android.content.Context
import androidx.multidex.MultiDex import androidx.multidex.MultiDex
import com.facebook.stetho.Stetho import com.facebook.stetho.Stetho
import com.github.piasy.biv.BigImageViewer
import com.github.piasy.biv.loader.glide.GlideImageLoader
import com.jakewharton.threetenabp.AndroidThreeTen import com.jakewharton.threetenabp.AndroidThreeTen
import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.BuildConfig
import im.vector.riotredesign.core.di.AppModule import im.vector.riotredesign.core.di.AppModule
@ -38,6 +40,7 @@ class Riot : Application() {
Stetho.initializeWithDefaults(this) Stetho.initializeWithDefaults(this)
} }
AndroidThreeTen.init(this) AndroidThreeTen.init(this)
BigImageViewer.initialize(GlideImageLoader.with(applicationContext))
val appModule = AppModule(applicationContext).definition val appModule = AppModule(applicationContext).definition
val homeModule = HomeModule().definition val homeModule = HomeModule().definition
startKoin(listOf(appModule, homeModule), logger = EmptyLogger()) startKoin(listOf(appModule, homeModule), logger = EmptyLogger())

View file

@ -36,6 +36,8 @@ import im.vector.riotredesign.features.home.HomeModule
import im.vector.riotredesign.features.home.HomePermalinkHandler import im.vector.riotredesign.features.home.HomePermalinkHandler
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotredesign.features.home.room.detail.timeline.animation.TimelineItemAnimator import im.vector.riotredesign.features.home.room.detail.timeline.animation.TimelineItemAnimator
import im.vector.riotredesign.features.media.MediaContentRenderer
import im.vector.riotredesign.features.media.MediaViewerActivity
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_detail.* import kotlinx.android.synthetic.main.fragment_room_detail.*
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
@ -164,4 +166,9 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
roomDetailViewModel.process(RoomDetailActions.EventDisplayed(event, index)) roomDetailViewModel.process(RoomDetailActions.EventDisplayed(event, index))
} }
override fun onMediaClicked(mediaData: MediaContentRenderer.Data) {
val intent = MediaViewerActivity.newIntent(riotActivity, mediaData)
startActivity(intent)
}
} }

View file

@ -31,6 +31,7 @@ import im.vector.riotredesign.features.home.room.detail.timeline.helper.Timeline
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.riotredesign.features.home.room.detail.timeline.item.DaySeparatorItem_ import im.vector.riotredesign.features.home.room.detail.timeline.item.DaySeparatorItem_
import im.vector.riotredesign.features.home.room.detail.timeline.paging.PagedListEpoxyController import im.vector.riotredesign.features.home.room.detail.timeline.paging.PagedListEpoxyController
import im.vector.riotredesign.features.media.MediaContentRenderer
class TimelineEventController(private val dateFormatter: TimelineDateFormatter, class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
private val timelineItemFactory: TimelineItemFactory, private val timelineItemFactory: TimelineItemFactory,
@ -102,6 +103,7 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
interface Callback { interface Callback {
fun onEventVisible(event: TimelineEvent, index: Int) fun onEventVisible(event: TimelineEvent, index: Int)
fun onUrlClicked(url: String) fun onUrlClicked(url: String)
fun onMediaClicked(mediaData: MediaContentRenderer.Data)
} }
} }

View file

@ -23,26 +23,16 @@ import im.vector.matrix.android.api.permalinks.MatrixLinkify
import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel 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.model.message.*
import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.RiotEpoxyModel import im.vector.riotredesign.core.epoxy.RiotEpoxyModel
import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.core.resources.ColorProvider import im.vector.riotredesign.core.resources.ColorProvider
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.riotredesign.features.home.room.detail.timeline.item.DefaultItem import im.vector.riotredesign.features.home.room.detail.timeline.item.*
import im.vector.riotredesign.features.home.room.detail.timeline.item.DefaultItem_
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageImageItem
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageImageItem_
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageTextItem
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageTextItem_
import im.vector.riotredesign.features.html.EventHtmlRenderer import im.vector.riotredesign.features.html.EventHtmlRenderer
import im.vector.riotredesign.features.media.MediaContentRenderer import im.vector.riotredesign.features.media.MediaContentRenderer
import me.gujun.android.span.span import me.gujun.android.span.span
@ -84,7 +74,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
return when (messageContent) { return when (messageContent) {
is MessageTextContent -> buildTextMessageItem(messageContent, informationData, callback) is MessageTextContent -> buildTextMessageItem(messageContent, informationData, callback)
is MessageImageContent -> buildImageMessageItem(messageContent, informationData) is MessageImageContent -> buildImageMessageItem(messageContent, informationData, callback)
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, callback) is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, callback)
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, callback) is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, callback)
else -> buildNotHandledMessageItem(messageContent) else -> buildNotHandledMessageItem(messageContent)
@ -97,10 +87,12 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
} }
private fun buildImageMessageItem(messageContent: MessageImageContent, private fun buildImageMessageItem(messageContent: MessageImageContent,
informationData: MessageInformationData): MessageImageItem? { informationData: MessageInformationData,
callback: TimelineEventController.Callback?): MessageImageItem? {
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize() val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
val data = MediaContentRenderer.Data( val data = MediaContentRenderer.Data(
messageContent.body,
url = messageContent.url, url = messageContent.url,
height = messageContent.info?.height, height = messageContent.info?.height,
maxHeight = maxHeight, maxHeight = maxHeight,
@ -112,6 +104,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
return MessageImageItem_() return MessageImageItem_()
.informationData(informationData) .informationData(informationData)
.mediaData(data) .mediaData(data)
.clickListener { callback?.onMediaClicked(data) }
} }
private fun buildTextMessageItem(messageContent: MessageTextContent, private fun buildTextMessageItem(messageContent: MessageTextContent,

View file

@ -28,10 +28,12 @@ abstract class MessageImageItem : AbsMessageItem<MessageImageItem.Holder>() {
@EpoxyAttribute lateinit var mediaData: MediaContentRenderer.Data @EpoxyAttribute lateinit var mediaData: MediaContentRenderer.Data
@EpoxyAttribute override lateinit var informationData: MessageInformationData @EpoxyAttribute override lateinit var informationData: MessageInformationData
@EpoxyAttribute var clickListener: (() -> Unit)? = null
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
MediaContentRenderer.render(mediaData, MediaContentRenderer.Mode.THUMBNAIL, holder.imageView) MediaContentRenderer.render(mediaData, MediaContentRenderer.Mode.THUMBNAIL, holder.imageView)
holder.imageView.setOnClickListener { clickListener?.invoke() }
} }
class Holder : AbsMessageItem.Holder() { class Holder : AbsMessageItem.Holder() {

View file

@ -17,14 +17,20 @@
package im.vector.riotredesign.features.media package im.vector.riotredesign.features.media
import android.media.ExifInterface import android.media.ExifInterface
import android.net.Uri
import android.os.Parcelable
import android.widget.ImageView import android.widget.ImageView
import com.github.piasy.biv.view.BigImageView
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.riotredesign.core.glide.GlideApp import im.vector.riotredesign.core.glide.GlideApp
import kotlinx.android.parcel.Parcelize
object MediaContentRenderer { object MediaContentRenderer {
@Parcelize
data class Data( data class Data(
val filename: String,
val url: String?, val url: String?,
val height: Int?, val height: Int?,
val maxHeight: Int, val maxHeight: Int,
@ -32,7 +38,7 @@ object MediaContentRenderer {
val maxWidth: Int, val maxWidth: Int,
val orientation: Int?, val orientation: Int?,
val rotation: Int? val rotation: Int?
) ) : Parcelable
enum class Mode { enum class Mode {
FULL_SIZE, FULL_SIZE,
@ -43,13 +49,11 @@ object MediaContentRenderer {
val (width, height) = processSize(data, mode) val (width, height) = processSize(data, mode)
imageView.layoutParams.height = height imageView.layoutParams.height = height
imageView.layoutParams.width = width imageView.layoutParams.width = width
val contentUrlResolver = Matrix.getInstance().currentSession.contentUrlResolver() val contentUrlResolver = Matrix.getInstance().currentSession.contentUrlResolver()
val resolvedUrl = when (mode) { val resolvedUrl = when (mode) {
Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url) Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url)
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE) Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
} } ?: return
?: return
GlideApp GlideApp
.with(imageView) .with(imageView)
@ -58,6 +62,17 @@ object MediaContentRenderer {
.into(imageView) .into(imageView)
} }
fun render(data: Data, imageView: BigImageView) {
val (width, height) = processSize(data, Mode.THUMBNAIL)
val contentUrlResolver = Matrix.getInstance().currentSession.contentUrlResolver()
val fullSize = contentUrlResolver.resolveFullSize(data.url)
val thumbnail = contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
imageView.showImage(
Uri.parse(thumbnail),
Uri.parse(fullSize)
)
}
private fun processSize(data: Data, mode: Mode): Pair<Int, Int> { private fun processSize(data: Data, mode: Mode): Pair<Int, Int> {
val maxImageWidth = data.maxWidth val maxImageWidth = data.maxWidth
val maxImageHeight = data.maxHeight val maxImageHeight = data.maxHeight

View file

@ -0,0 +1,80 @@
/*
*
* * Copyright 2019 New Vector Ltd
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package im.vector.riotredesign.features.media
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.widget.Toolbar
import com.github.piasy.biv.indicator.progresspie.ProgressPieIndicator
import com.github.piasy.biv.view.GlideImageViewFactory
import im.vector.riotredesign.core.platform.RiotActivity
import kotlinx.android.synthetic.main.activity_media_viewer.*
class MediaViewerActivity : RiotActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(im.vector.riotredesign.R.layout.activity_media_viewer)
val mediaData = intent.getParcelableExtra<MediaContentRenderer.Data>(EXTRA_MEDIA_DATA)
if (mediaData.url.isNullOrEmpty()) {
finish()
} else {
configureToolbar(mediaViewerToolbar, mediaData)
mediaViewerImageView.setImageViewFactory(GlideImageViewFactory())
mediaViewerImageView.setProgressIndicator(ProgressPieIndicator())
MediaContentRenderer.render(mediaData, mediaViewerImageView)
}
}
private fun configureToolbar(toolbar: Toolbar, mediaData: MediaContentRenderer.Data) {
setSupportActionBar(toolbar)
supportActionBar?.apply {
title = mediaData.filename
setHomeButtonEnabled(true)
setDisplayHomeAsUpEnabled(true)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
finish()
return true
}
}
return true
}
companion object {
private const val EXTRA_MEDIA_DATA = "EXTRA_MEDIA_DATA"
fun newIntent(context: Context, mediaData: MediaContentRenderer.Data): Intent {
return Intent(context, MediaViewerActivity::class.java).apply {
putExtra(EXTRA_MEDIA_DATA, mediaData)
}
}
}
}

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/mediaViewerToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
<com.github.piasy.biv.view.BigImageView
android:id="@+id/mediaViewerImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:failureImageInitScaleType="center"
app:optimizeDisplay="true" />
</LinearLayout>

View file

@ -19,10 +19,11 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
google() maven { url "http://dl.bintray.com/piasy/maven" }
jcenter()
maven { url 'https://jitpack.io' } maven { url 'https://jitpack.io' }
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
google()
jcenter()
} }
} }