diff --git a/app/build.gradle b/app/build.gradle index 58cc5e064f..570cf611ce 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 05b64b0ee3..5958432633 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,6 +26,7 @@ + \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/Riot.kt b/app/src/main/java/im/vector/riotredesign/Riot.kt index 9a82a20fec..9332eec737 100644 --- a/app/src/main/java/im/vector/riotredesign/Riot.kt +++ b/app/src/main/java/im/vector/riotredesign/Riot.kt @@ -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()) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index a72758e9b2..4574cdb685 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -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 @@ -164,4 +166,9 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { roomDetailViewModel.process(RoomDetailActions.EventDisplayed(event, index)) } + override fun onMediaClicked(mediaData: MediaContentRenderer.Data) { + val intent = MediaViewerActivity.newIntent(riotActivity, mediaData) + startActivity(intent) + } + } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index ce89fda61f..a18900bac7 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -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.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 +103,7 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter, interface Callback { fun onEventVisible(event: TimelineEvent, index: Int) fun onUrlClicked(url: String) + fun onMediaClicked(mediaData: MediaContentRenderer.Data) } } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 4d3104c8c3..cde54929af 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -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 { callback?.onMediaClicked(data) } } private fun buildTextMessageItem(messageContent: MessageTextContent, diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageImageItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageImageItem.kt index 56d06c38dd..2156f48389 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageImageItem.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageImageItem.kt @@ -28,10 +28,12 @@ abstract class MessageImageItem : AbsMessageItem() { @EpoxyAttribute lateinit var mediaData: MediaContentRenderer.Data @EpoxyAttribute override lateinit var informationData: MessageInformationData + @EpoxyAttribute var clickListener: (() -> Unit)? = null override fun bind(holder: Holder) { super.bind(holder) MediaContentRenderer.render(mediaData, MediaContentRenderer.Mode.THUMBNAIL, holder.imageView) + holder.imageView.setOnClickListener { clickListener?.invoke() } } class Holder : AbsMessageItem.Holder() { diff --git a/app/src/main/java/im/vector/riotredesign/features/media/MediaContentRenderer.kt b/app/src/main/java/im/vector/riotredesign/features/media/MediaContentRenderer.kt index 9a97ad6b2d..86501199c3 100644 --- a/app/src/main/java/im/vector/riotredesign/features/media/MediaContentRenderer.kt +++ b/app/src/main/java/im/vector/riotredesign/features/media/MediaContentRenderer.kt @@ -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 { val maxImageWidth = data.maxWidth val maxImageHeight = data.maxHeight diff --git a/app/src/main/java/im/vector/riotredesign/features/media/MediaViewerActivity.kt b/app/src/main/java/im/vector/riotredesign/features/media/MediaViewerActivity.kt new file mode 100644 index 0000000000..2b3ae6f697 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/media/MediaViewerActivity.kt @@ -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(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) + } + } + } + + +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_media_viewer.xml b/app/src/main/res/layout/activity_media_viewer.xml new file mode 100644 index 0000000000..b3156f1478 --- /dev/null +++ b/app/src/main/res/layout/activity_media_viewer.xml @@ -0,0 +1,23 @@ + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index af25b46084..226ca00ecb 100644 --- a/build.gradle +++ b/build.gradle @@ -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() } }