Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui): show full screen image viewer when clicking on images #578

Merged
merged 1 commit into from
Feb 3, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -127,6 +127,9 @@ dependencies {
implementation("io.coil-kt:coil-svg:$coil")
implementation("io.coil-kt:coil-gif:$coil")

// https://saket.github.io/telephoto/zoomableimage/
implementation("me.saket.telephoto:zoomable:0.7.1")

// Cancel TLSv1.3 support pre Android10
// implementation 'org.conscrypt:conscrypt-android:2.5.2'

Original file line number Diff line number Diff line change
@@ -68,6 +68,7 @@ fun LazyListScope.htmlFormattedText(
subheadUpperCase: Boolean = false,
baseUrl: String,
@DrawableRes imagePlaceholder: Int,
onImageClick: ((imgUrl: String, altText: String) -> Unit)? = null,
onLinkClick: (String) -> Unit,
) {
Jsoup.parse(inputStream, null, baseUrl)
@@ -77,6 +78,7 @@ fun LazyListScope.htmlFormattedText(
element = body,
subheadUpperCase = subheadUpperCase,
imagePlaceholder = imagePlaceholder,
onImageClick = onImageClick,
onLinkClick = onLinkClick,
baseUrl = baseUrl,
)
@@ -87,6 +89,7 @@ private fun LazyListScope.formatBody(
element: Element,
subheadUpperCase: Boolean = false,
@DrawableRes imagePlaceholder: Int,
onImageClick: ((imgUrl: String, altText: String) -> Unit)? = null,
onLinkClick: (String) -> Unit,
baseUrl: String,
) {
@@ -129,6 +132,7 @@ private fun LazyListScope.formatBody(
subheadUpperCase = subheadUpperCase,
lazyListScope = this,
imagePlaceholder = imagePlaceholder,
onImageClick = onImageClick,
onLinkClick = onLinkClick,
baseUrl = baseUrl,
)
@@ -139,6 +143,7 @@ private fun LazyListScope.formatBody(
private fun LazyListScope.formatCodeBlock(
element: Element,
@DrawableRes imagePlaceholder: Int,
onImageClick: ((imgUrl: String, altText: String) -> Unit)?,
onLinkClick: (String) -> Unit,
baseUrl: String,
) {
@@ -175,6 +180,7 @@ private fun LazyListScope.formatCodeBlock(
element.childNodes(), preFormatted = true,
lazyListScope = this,
imagePlaceholder = imagePlaceholder,
onImageClick = onImageClick,
onLinkClick = onLinkClick,
baseUrl = baseUrl,
)
@@ -188,6 +194,7 @@ private fun TextComposer.appendTextChildren(
subheadUpperCase: Boolean = false,
lazyListScope: LazyListScope,
@DrawableRes imagePlaceholder: Int,
onImageClick: ((imgUrl: String, altText: String) -> Unit)?,
onLinkClick: (String) -> Unit,
baseUrl: String,
) {
@@ -225,6 +232,7 @@ private fun TextComposer.appendTextChildren(
element.childNodes(),
lazyListScope = lazyListScope,
imagePlaceholder = imagePlaceholder,
onImageClick = onImageClick,
onLinkClick = onLinkClick,
baseUrl = baseUrl,
)
@@ -234,6 +242,7 @@ private fun TextComposer.appendTextChildren(
element.childNodes(),
lazyListScope = lazyListScope,
imagePlaceholder = imagePlaceholder,
onImageClick = onImageClick,
onLinkClick = onLinkClick,
baseUrl = baseUrl,
)
@@ -247,7 +256,12 @@ private fun TextComposer.appendTextChildren(
withComposableStyle(
style = { h1Style().toSpanStyle() }
) {
append("\n${if (subheadUpperCase) element.text().uppercase() else element.text()}")
append(
"\n${
if (subheadUpperCase) element.text()
.uppercase() else element.text()
}"
)
}
}
}
@@ -257,7 +271,12 @@ private fun TextComposer.appendTextChildren(
withComposableStyle(
style = { h2Style().toSpanStyle() }
) {
append("\n${if (subheadUpperCase) element.text().uppercase() else element.text()}")
append(
"\n${
if (subheadUpperCase) element.text()
.uppercase() else element.text()
}"
)
}
}
}
@@ -267,7 +286,12 @@ private fun TextComposer.appendTextChildren(
withComposableStyle(
style = { h3Style().toSpanStyle() }
) {
append("\n${if (subheadUpperCase) element.text().uppercase() else element.text()}")
append(
"\n${
if (subheadUpperCase) element.text()
.uppercase() else element.text()
}"
)
}
}
}
@@ -277,7 +301,12 @@ private fun TextComposer.appendTextChildren(
withComposableStyle(
style = { h4Style().toSpanStyle() }
) {
append("\n${if (subheadUpperCase) element.text().uppercase() else element.text()}")
append(
"\n${
if (subheadUpperCase) element.text()
.uppercase() else element.text()
}"
)
}
}
}
@@ -287,7 +316,12 @@ private fun TextComposer.appendTextChildren(
withComposableStyle(
style = { h5Style().toSpanStyle() }
) {
append("\n${if (subheadUpperCase) element.text().uppercase() else element.text()}")
append(
"\n${
if (subheadUpperCase) element.text()
.uppercase() else element.text()
}"
)
}
}
}
@@ -297,7 +331,12 @@ private fun TextComposer.appendTextChildren(
withComposableStyle(
style = { h6Style().toSpanStyle() }
) {
append("\n${if (subheadUpperCase) element.text().uppercase() else element.text()}")
append(
"\n${
if (subheadUpperCase) element.text()
.uppercase() else element.text()
}"
)
}
}
}
@@ -310,6 +349,7 @@ private fun TextComposer.appendTextChildren(
element.childNodes(),
lazyListScope = lazyListScope,
imagePlaceholder = imagePlaceholder,
onImageClick = onImageClick,
onLinkClick = onLinkClick,
baseUrl = baseUrl,
)
@@ -322,6 +362,7 @@ private fun TextComposer.appendTextChildren(
element.childNodes(),
lazyListScope = lazyListScope,
imagePlaceholder = imagePlaceholder,
onImageClick = onImageClick,
onLinkClick = onLinkClick,
baseUrl = baseUrl,
)
@@ -334,6 +375,7 @@ private fun TextComposer.appendTextChildren(
element.childNodes(),
lazyListScope = lazyListScope,
imagePlaceholder = imagePlaceholder,
onImageClick = onImageClick,
onLinkClick = onLinkClick,
baseUrl = baseUrl,
)
@@ -346,6 +388,7 @@ private fun TextComposer.appendTextChildren(
element.childNodes(),
lazyListScope = lazyListScope,
imagePlaceholder = imagePlaceholder,
onImageClick = onImageClick,
onLinkClick = onLinkClick,
baseUrl = baseUrl,
)
@@ -358,6 +401,7 @@ private fun TextComposer.appendTextChildren(
element.childNodes(),
lazyListScope = lazyListScope,
imagePlaceholder = imagePlaceholder,
onImageClick = onImageClick,
onLinkClick = onLinkClick,
baseUrl = baseUrl,
)
@@ -370,6 +414,7 @@ private fun TextComposer.appendTextChildren(
element.childNodes(),
lazyListScope = lazyListScope,
imagePlaceholder = imagePlaceholder,
onImageClick = onImageClick,
onLinkClick = onLinkClick,
baseUrl = baseUrl,
)
@@ -383,6 +428,7 @@ private fun TextComposer.appendTextChildren(
element.childNodes(),
lazyListScope = lazyListScope,
imagePlaceholder = imagePlaceholder,
onImageClick = onImageClick,
onLinkClick = onLinkClick,
baseUrl = baseUrl,
)
@@ -395,6 +441,7 @@ private fun TextComposer.appendTextChildren(
preFormatted = true,
lazyListScope = lazyListScope,
imagePlaceholder = imagePlaceholder,
onImageClick = onImageClick,
onLinkClick = onLinkClick,
baseUrl = baseUrl,
)
@@ -406,6 +453,7 @@ private fun TextComposer.appendTextChildren(
lazyListScope.formatCodeBlock(
element = element,
imagePlaceholder = imagePlaceholder,
onImageClick = onImageClick,
onLinkClick = onLinkClick,
baseUrl = baseUrl,
)
@@ -419,6 +467,7 @@ private fun TextComposer.appendTextChildren(
preFormatted = preFormatted,
lazyListScope = lazyListScope,
imagePlaceholder = imagePlaceholder,
onImageClick = onImageClick,
onLinkClick = onLinkClick,
baseUrl = baseUrl,
)
@@ -438,6 +487,7 @@ private fun TextComposer.appendTextChildren(
element.childNodes(),
lazyListScope = lazyListScope,
imagePlaceholder = imagePlaceholder,
onImageClick = onImageClick,
onLinkClick = onLinkClick,
baseUrl = baseUrl,
)
@@ -454,6 +504,7 @@ private fun TextComposer.appendTextChildren(
element.childNodes(),
lazyListScope = lazyListScope,
imagePlaceholder = imagePlaceholder,
onImageClick = onImageClick,
onLinkClick = onLinkClick,
baseUrl = baseUrl,
)
@@ -478,11 +529,11 @@ private fun TextComposer.appendTextChildren(
BoxWithConstraints(
modifier = Modifier
.clip(RectangleShape)
.clickable(
enabled = onClick != null
) {
onClick?.invoke()
}
// .clickable(
// enabled = onClick != null
// ) {
// onClick?.invoke()
// }
.fillMaxWidth()
// This makes scrolling a pain, find a way to solve that
// .pointerInput("imgzoom") {
@@ -497,17 +548,26 @@ private fun TextComposer.appendTextChildren(
// }
) {
val imageSize = maxImageSize()
val imgUrl = imageCandidates.getBestImageForMaxSize(
pixelDensity = pixelDensity(),
maxSize = imageSize,
)
RYAsyncImage(
modifier = Modifier
.align(Alignment.Center)
.fillMaxWidth()
.padding(horizontal = imageHorizontalPadding().dp)
.clip(imageShape())
.clickable { },
data = imageCandidates.getBestImageForMaxSize(
pixelDensity = pixelDensity(),
maxSize = imageSize,
),
.run {
if (onImageClick != null) {
this.clickable {
onImageClick(imgUrl, alt)
}
} else {
this
}
},
data = imgUrl,
contentDescription = alt,
size = imageSize,
precision = Precision.INEXACT,
@@ -547,6 +607,7 @@ private fun TextComposer.appendTextChildren(
lazyListScope = lazyListScope,
imagePlaceholder = imagePlaceholder,
onLinkClick = onLinkClick,
onImageClick = onImageClick,
baseUrl = baseUrl,
)
}
@@ -565,6 +626,7 @@ private fun TextComposer.appendTextChildren(
lazyListScope = lazyListScope,
imagePlaceholder = imagePlaceholder,
onLinkClick = onLinkClick,
onImageClick = onImageClick,
baseUrl = baseUrl,
)
}
@@ -590,6 +652,7 @@ private fun TextComposer.appendTextChildren(
lazyListScope = lazyListScope,
imagePlaceholder = imagePlaceholder,
onLinkClick = onLinkClick,
onImageClick = onImageClick,
baseUrl = baseUrl,
)
ensureDoubleNewline()
@@ -608,6 +671,7 @@ private fun TextComposer.appendTextChildren(
lazyListScope = lazyListScope,
imagePlaceholder = imagePlaceholder,
onLinkClick = onLinkClick,
onImageClick = onImageClick,
baseUrl = baseUrl,
)
terminateCurrentText()
@@ -677,6 +741,7 @@ private fun TextComposer.appendTextChildren(
subheadUpperCase = subheadUpperCase,
lazyListScope = lazyListScope,
imagePlaceholder = imagePlaceholder,
onImageClick = onImageClick,
onLinkClick = onLinkClick,
baseUrl = baseUrl,
)
@@ -709,7 +774,7 @@ private fun testIt() {
inputStream = stream,
baseUrl = "https://cowboyprogrammer.org",
imagePlaceholder = R.drawable.ic_telegram,
onLinkClick = {}
onLinkClick = {},
)
}
}
2 changes: 2 additions & 0 deletions app/src/main/java/me/ash/reader/ui/component/reader/Reader.kt
Original file line number Diff line number Diff line change
@@ -31,13 +31,15 @@ fun LazyListScope.Reader(
subheadUpperCase: Boolean = false,
link: String,
content: String,
onImageClick: ((imgUrl: String, altText: String) -> Unit)? = null,
onLinkClick: (String) -> Unit
) {
Log.i("RLog", "Reader: ")
htmlFormattedText(
inputStream = content.byteInputStream(),
subheadUpperCase = subheadUpperCase,
baseUrl = link,
onImageClick = onImageClick,
imagePlaceholder = R.drawable.ic_launcher_foreground,
onLinkClick = onLinkClick
)
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ fun Content(
publishedDate: Date,
listState: LazyListState,
isLoading: Boolean,
isShowToolBar: Boolean,
onImageClick: ((imgUrl: String, altText: String) -> Unit)? = null,
) {
val context = LocalContext.current
val subheadUpperCase = LocalReadingSubheadUpperCase.current
@@ -90,6 +90,7 @@ fun Content(
subheadUpperCase = subheadUpperCase.value,
link = link ?: "",
content = content,
onImageClick = onImageClick,
onLinkClick = {
context.openURL(it, openLink, openLinkSpecificBrowser)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package me.ash.reader.ui.page.home.reading

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.DialogWindowProvider
import me.ash.reader.R
import me.ash.reader.ui.component.base.RYAsyncImage
import me.saket.telephoto.zoomable.ZoomSpec
import me.saket.telephoto.zoomable.rememberZoomableState
import me.saket.telephoto.zoomable.zoomable

data class ImageData(val imageUrl: String = "", val altText: String = "")

@Composable
fun ReaderImageViewer(imageData: ImageData, onDismissRequest: () -> Unit = {}) {
Dialog(
onDismissRequest = onDismissRequest,
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Box(
modifier = Modifier
.fillMaxSize()
// .background(Color.Black)
.windowInsetsPadding(WindowInsets.systemBars)
) {
val dialogWindowProvider = LocalView.current.parent as? DialogWindowProvider
dialogWindowProvider?.window?.setDimAmount(1f)

val zoomableState = rememberZoomableState().apply {
contentAlignment = Alignment.Center
}

RYAsyncImage(
data = imageData.imageUrl,
contentDescription = imageData.altText,
modifier = Modifier
.align(Alignment.Center)
.zoomable(zoomableState)
.fillMaxSize(),
)

IconButton(
onClick = onDismissRequest,
colors = IconButtonDefaults.iconButtonColors(
containerColor = Color.Gray.copy(alpha = 0.5f),
contentColor = Color.White
),
modifier = Modifier.padding(12.dp)
) {
Icon(
imageVector = Icons.Outlined.Close,
contentDescription = stringResource(id = R.string.close)
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,25 @@
package me.ash.reader.ui.page.home.reading

import android.util.Log
import androidx.compose.animation.*
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import androidx.paging.compose.collectAsLazyPagingItems
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.map
import me.ash.reader.infrastructure.preference.LocalReadingAutoHideToolbar
import me.ash.reader.infrastructure.preference.LocalReadingPageTonalElevation
import me.ash.reader.ui.component.base.RYScaffold
@@ -31,7 +28,6 @@ import me.ash.reader.ui.ext.isScrollDown
import me.ash.reader.ui.motion.materialSharedAxisY
import me.ash.reader.ui.page.home.HomeViewModel

@OptIn(ExperimentalAnimationApi::class)
@Composable
fun ReadingPage(
navController: NavHostController,
@@ -44,6 +40,9 @@ fun ReadingPage(
val homeUiState = homeViewModel.homeUiState.collectAsStateValue()

var isReaderScrollingDown by remember { mutableStateOf(false) }
var showFullScreenImageViewer by remember { mutableStateOf(false) }

var currentImageData by remember { mutableStateOf(ImageData()) }

val isShowToolBar = if (LocalReadingAutoHideToolbar.current.value) {
readingUiState.articleId != null && !isReaderScrollingDown
@@ -128,7 +127,10 @@ fun ReadingPage(
publishedDate = publishedDate,
isLoading = content is ReaderState.Loading,
listState = listState,
isShowToolBar = isShowToolBar,
onImageClick = { imgUrl, altText ->
currentImageData = ImageData(imgUrl, altText)
showFullScreenImageViewer = true
}
)
}
}
@@ -159,4 +161,7 @@ fun ReadingPage(
}
}
)
if (showFullScreenImageViewer) {
ReaderImageViewer(imageData = currentImageData) { showFullScreenImageViewer = false }
}
}