diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 1a54cb27c2..327746b5a2 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -54,6 +54,8 @@ dependencies { implementation(libs.androidx.media3.exoplayer) implementation(libs.androidx.media3.ui) implementation(libs.accompanist.systemui) + implementation(libs.vanniktech.blurhash) + implementation(libs.telephoto.zoomableimage) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMediaView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMediaView.kt index 7fc6ac5608..b5591c14f2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMediaView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMediaView.kt @@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext @@ -36,9 +35,8 @@ import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer import androidx.media3.ui.AspectRatioFrameLayout import androidx.media3.ui.PlayerView -import coil.compose.AsyncImage -import io.element.android.libraries.designsystem.components.ZoomableBox import io.element.android.libraries.designsystem.utils.OnLifecycleEvent +import me.saket.telephoto.zoomable.coil.ZoomableAsyncImage @SuppressLint("UnsafeOptInUsageError") @Composable @@ -64,19 +62,12 @@ private fun MediaImageView( uri: Uri, modifier: Modifier = Modifier, ) { - ZoomableBox( + ZoomableAsyncImage( modifier = modifier.fillMaxSize(), - contentAlignment = Alignment.Center, - ) { - AsyncImage( - modifier = Modifier - .zoomable() - .fillMaxSize(), - model = uri, - contentDescription = "Image", - contentScale = ContentScale.Fit, - ) - } + model = uri, + contentDescription = "Image", + contentScale = ContentScale.Fit, + ) } @UnstableApi diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d3c8167441..a917c8ddbe 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -42,6 +42,7 @@ appyx = "1.2.0" dependencycheck = "8.2.1" stem = "2.3.0" sqldelight = "1.5.5" +telephoto = "0.3.0" # DI dagger = "2.46.1" @@ -142,6 +143,7 @@ unifiedpush = "com.github.UnifiedPush:android-connector:2.1.1" gujun_span = "me.gujun.android:span:1.7" otaliastudios_transcoder = "com.otaliastudios:transcoder:0.10.5" vanniktech_blurhash = "com.vanniktech:blurhash:0.1.0" +telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil", version.ref = "telephoto" } # Di inject = "javax.inject:javax.inject:1" diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ZoomableBox.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ZoomableBox.kt deleted file mode 100644 index 89ffd9a197..0000000000 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ZoomableBox.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2023 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 io.element.android.libraries.designsystem.components - -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.gestures.detectTransformGestures -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxScope -import androidx.compose.foundation.layout.LayoutScopeMarker -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.unit.IntSize - -@Composable -fun ZoomableBox( - modifier: Modifier = Modifier, - contentAlignment: Alignment = Alignment.TopStart, - minZoom: Float = 1f, - maxZoom: Float = 5f, - content: @Composable ZoomableBoxScope.() -> Unit -) { - var zoom by remember { mutableStateOf(minZoom) } - var offsetX by remember { mutableStateOf(0f) } - var offsetY by remember { mutableStateOf(0f) } - var size by remember { mutableStateOf(IntSize.Zero) } - - Box( - modifier = modifier - .clip(RectangleShape) - .onSizeChanged { - size = it - } - .pointerInput(Unit) { - detectTransformGestures { _, panChange, zoomChange, _ -> - zoom = (zoom * zoomChange).coerceIn(minZoom, maxZoom) - val maxX = (size.width * (zoom - 1)) / 2f - val minX = -maxX - val maxY = (size.height * (zoom - 1)) / 2f - val minY = -maxY - offsetX = maxOf(minX, minOf(maxX, offsetX + panChange.x)) - offsetY = maxOf(minY, minOf(maxY, offsetY + panChange.y)) - } - } - .pointerInput(Unit) { - detectTapGestures( - onDoubleTap = { - offsetX = 0f - offsetY = 0f - zoom = if (zoom > minZoom) { - minZoom - } else { - maxZoom / 2f - } - - } - ) - }, - contentAlignment = contentAlignment, - ) { - DefaultZoomableBoxScope(this, zoom, offsetX, offsetY).content() - } -} - -@LayoutScopeMarker -@Immutable -interface ZoomableBoxScope : BoxScope { - @Stable - fun Modifier.zoomable(): Modifier -} - -private class DefaultZoomableBoxScope( - private val parentScope: BoxScope, - private val scale: Float, - private val offsetX: Float, - private val offsetY: Float -) : ZoomableBoxScope, BoxScope by parentScope { - - override fun Modifier.zoomable() = this.then( - graphicsLayer( - scaleX = scale, - scaleY = scale, - translationX = offsetX, - translationY = offsetY, - ) - ) -} diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index 36a3a33e70..d7c60d7fb0 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -23,7 +23,7 @@ object Versions { const val compileSdk = 33 const val targetSdk = 33 - const val minSdk = 23 + const val minSdk = 24 val javaCompileVersion = JavaVersion.VERSION_17 val javaLanguageVersion: JavaLanguageVersion = JavaLanguageVersion.of(11) }