mirror of
https://github.com/element-hq/element-android
synced 2024-11-27 03:48:12 +03:00
Merge branch 'feature/image_viewer' into develop
This commit is contained in:
commit
388eae6a1c
12 changed files with 216 additions and 26 deletions
|
@ -27,7 +27,7 @@ def generateVersionCodeFromVersionName() {
|
|||
project.android.buildTypes.all { buildType ->
|
||||
buildType.javaCompileOptions.annotationProcessorOptions.arguments =
|
||||
[
|
||||
validateEpoxyModelUsage : String.valueOf(buildType.name == 'debug')
|
||||
validateEpoxyModelUsage: String.valueOf(buildType.name == 'debug')
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -62,6 +62,8 @@ dependencies {
|
|||
def arrow_version = "0.8.2"
|
||||
def coroutines_version = "1.0.1"
|
||||
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-rx")
|
||||
|
@ -96,14 +98,19 @@ dependencies {
|
|||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||
|
||||
// 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.google.android.material:material:1.1.0-alpha02'
|
||||
implementation 'me.gujun.android:span:1.7'
|
||||
implementation "ru.noties.markwon:core:$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
|
||||
implementation "org.koin:koin-android:$koin_version"
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
<activity android:name=".features.home.HomeActivity" />
|
||||
<activity android:name=".features.login.LoginActivity" />
|
||||
<activity android:name=".features.media.MediaViewerActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -20,6 +20,8 @@ import android.app.Application
|
|||
import android.content.Context
|
||||
import androidx.multidex.MultiDex
|
||||
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 im.vector.matrix.android.BuildConfig
|
||||
import im.vector.riotredesign.core.di.AppModule
|
||||
|
@ -38,6 +40,7 @@ class Riot : Application() {
|
|||
Stetho.initializeWithDefaults(this)
|
||||
}
|
||||
AndroidThreeTen.init(this)
|
||||
BigImageViewer.initialize(GlideImageLoader.with(applicationContext))
|
||||
val appModule = AppModule(applicationContext).definition
|
||||
val homeModule = HomeModule().definition
|
||||
startKoin(listOf(appModule, homeModule), logger = EmptyLogger())
|
||||
|
|
|
@ -36,6 +36,8 @@ import im.vector.riotredesign.features.home.HomeModule
|
|||
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.animation.TimelineItemAnimator
|
||||
import im.vector.riotredesign.features.media.MediaContentRenderer
|
||||
import im.vector.riotredesign.features.media.MediaViewerActivity
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.fragment_room_detail.*
|
||||
import org.koin.android.ext.android.inject
|
||||
|
@ -43,6 +45,7 @@ import org.koin.android.scope.ext.android.bindScope
|
|||
import org.koin.android.scope.ext.android.getOrCreateScope
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
|
||||
@Parcelize
|
||||
data class RoomDetailArgs(
|
||||
val roomId: String,
|
||||
|
@ -164,4 +167,9 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
|||
roomDetailViewModel.process(RoomDetailActions.EventDisplayed(event, index))
|
||||
}
|
||||
|
||||
override fun onMediaClicked(mediaData: MediaContentRenderer.Data, view: View) {
|
||||
val intent = MediaViewerActivity.newIntent(riotActivity, mediaData)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.epoxy.EpoxyAsyncUtil
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
|
@ -31,6 +32,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.item.DaySeparatorItem_
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.paging.PagedListEpoxyController
|
||||
import im.vector.riotredesign.features.media.MediaContentRenderer
|
||||
|
||||
class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
|
||||
private val timelineItemFactory: TimelineItemFactory,
|
||||
|
@ -102,6 +104,7 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
|
|||
interface Callback {
|
||||
fun onEventVisible(event: TimelineEvent, index: Int)
|
||||
fun onUrlClicked(url: String)
|
||||
fun onMediaClicked(mediaData: MediaContentRenderer.Data, view: View)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.session.events.model.EventType
|
||||
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.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.model.message.*
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.epoxy.RiotEpoxyModel
|
||||
import im.vector.riotredesign.core.extensions.localDateTime
|
||||
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.helper.TimelineDateFormatter
|
||||
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.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.home.room.detail.timeline.item.*
|
||||
import im.vector.riotredesign.features.html.EventHtmlRenderer
|
||||
import im.vector.riotredesign.features.media.MediaContentRenderer
|
||||
import me.gujun.android.span.span
|
||||
|
@ -84,7 +74,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
|
|||
|
||||
return when (messageContent) {
|
||||
is MessageTextContent -> buildTextMessageItem(messageContent, informationData, callback)
|
||||
is MessageImageContent -> buildImageMessageItem(messageContent, informationData)
|
||||
is MessageImageContent -> buildImageMessageItem(messageContent, informationData, callback)
|
||||
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, callback)
|
||||
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, callback)
|
||||
else -> buildNotHandledMessageItem(messageContent)
|
||||
|
@ -97,10 +87,12 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
|
|||
}
|
||||
|
||||
private fun buildImageMessageItem(messageContent: MessageImageContent,
|
||||
informationData: MessageInformationData): MessageImageItem? {
|
||||
informationData: MessageInformationData,
|
||||
callback: TimelineEventController.Callback?): MessageImageItem? {
|
||||
|
||||
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
||||
val data = MediaContentRenderer.Data(
|
||||
messageContent.body,
|
||||
url = messageContent.url,
|
||||
height = messageContent.info?.height,
|
||||
maxHeight = maxHeight,
|
||||
|
@ -112,6 +104,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
|
|||
return MessageImageItem_()
|
||||
.informationData(informationData)
|
||||
.mediaData(data)
|
||||
.clickListener { view -> callback?.onMediaClicked(data, view) }
|
||||
}
|
||||
|
||||
private fun buildTextMessageItem(messageContent: MessageTextContent,
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.riotredesign.features.home.room.detail.timeline.item
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
|
@ -28,10 +29,12 @@ abstract class MessageImageItem : AbsMessageItem<MessageImageItem.Holder>() {
|
|||
|
||||
@EpoxyAttribute lateinit var mediaData: MediaContentRenderer.Data
|
||||
@EpoxyAttribute override lateinit var informationData: MessageInformationData
|
||||
@EpoxyAttribute var clickListener: View.OnClickListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
MediaContentRenderer.render(mediaData, MediaContentRenderer.Mode.THUMBNAIL, holder.imageView)
|
||||
holder.imageView.setOnClickListener(clickListener)
|
||||
}
|
||||
|
||||
class Holder : AbsMessageItem.Holder() {
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
*
|
||||
* * 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 com.github.piasy.biv.loader.ImageLoader
|
||||
import java.io.File
|
||||
|
||||
interface DefaultImageLoaderCallback : ImageLoader.Callback {
|
||||
|
||||
override fun onFinish() {
|
||||
//no-op
|
||||
}
|
||||
|
||||
override fun onSuccess(image: File?) {
|
||||
//no-op
|
||||
}
|
||||
|
||||
override fun onFail(error: Exception?) {
|
||||
//no-op
|
||||
}
|
||||
|
||||
override fun onCacheHit(imageType: Int, image: File?) {
|
||||
//no-op
|
||||
}
|
||||
|
||||
override fun onCacheMiss(imageType: Int, image: File?) {
|
||||
//no-op
|
||||
}
|
||||
|
||||
override fun onProgress(progress: Int) {
|
||||
//no-op
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
//no-op
|
||||
}
|
||||
}
|
|
@ -17,14 +17,20 @@
|
|||
package im.vector.riotredesign.features.media
|
||||
|
||||
import android.media.ExifInterface
|
||||
import android.net.Uri
|
||||
import android.os.Parcelable
|
||||
import android.widget.ImageView
|
||||
import com.github.piasy.biv.view.BigImageView
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||
import im.vector.riotredesign.core.glide.GlideApp
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
||||
object MediaContentRenderer {
|
||||
|
||||
@Parcelize
|
||||
data class Data(
|
||||
val filename: String,
|
||||
val url: String?,
|
||||
val height: Int?,
|
||||
val maxHeight: Int,
|
||||
|
@ -32,7 +38,7 @@ object MediaContentRenderer {
|
|||
val maxWidth: Int,
|
||||
val orientation: Int?,
|
||||
val rotation: Int?
|
||||
)
|
||||
) : Parcelable
|
||||
|
||||
enum class Mode {
|
||||
FULL_SIZE,
|
||||
|
@ -43,13 +49,11 @@ object MediaContentRenderer {
|
|||
val (width, height) = processSize(data, mode)
|
||||
imageView.layoutParams.height = height
|
||||
imageView.layoutParams.width = width
|
||||
|
||||
val contentUrlResolver = Matrix.getInstance().currentSession.contentUrlResolver()
|
||||
val resolvedUrl = when (mode) {
|
||||
Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url)
|
||||
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
||||
}
|
||||
?: return
|
||||
Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url)
|
||||
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
||||
} ?: return
|
||||
|
||||
GlideApp
|
||||
.with(imageView)
|
||||
|
@ -58,6 +62,17 @@ object MediaContentRenderer {
|
|||
.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> {
|
||||
val maxImageWidth = data.maxWidth
|
||||
val maxImageHeight = data.maxHeight
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
23
app/src/main/res/layout/activity_media_viewer.xml
Normal file
23
app/src/main/res/layout/activity_media_viewer.xml
Normal 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>
|
|
@ -19,10 +19,11 @@ buildscript {
|
|||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven { url "http://dl.bintray.com/piasy/maven" }
|
||||
maven { url 'https://jitpack.io' }
|
||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue