feat: support embedded web view in posts (#799)

This commit is contained in:
Diego Beraldin 2024-05-07 00:41:55 +02:00 committed by GitHub
parent 5247b0975c
commit 707f6f8a32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 147 additions and 16 deletions

View File

@ -45,11 +45,13 @@ import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallb
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs
import com.github.diegoberaldin.raccoonforlemmy.core.utils.looksLikeAVideo
import com.github.diegoberaldin.raccoonforlemmy.core.utils.looksLikeAnImage
import com.github.diegoberaldin.raccoonforlemmy.core.utils.showInEmbeddedWebView
import com.github.diegoberaldin.raccoonforlemmy.core.utils.url.getCustomTabsHelper
import com.github.diegoberaldin.raccoonforlemmy.core.utils.url.toUrlOpeningMode
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.embeddedUrl
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.imageUrl
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.videoUrl
@ -420,7 +422,11 @@ private fun ExtendedPost(
val navigationCoordinator = remember { getNavigationCoordinator() }
val optionsMenuOpen = remember { mutableStateOf(false) }
val postLinkUrl = post.url.orEmpty().takeIf {
it != post.imageUrl && it != post.videoUrl && !it.looksLikeAnImage && !it.looksLikeAVideo
it != post.imageUrl
&& it != post.videoUrl
&& !it.looksLikeAnImage
&& !it.looksLikeAVideo
&& !it.showInEmbeddedWebView
}.orEmpty()
Column(
@ -483,7 +489,34 @@ private fun ExtendedPost(
)
}
if (post.videoUrl.isNotEmpty()) {
if (post.embeddedUrl.isNotEmpty()) {
PostCardEmbeddedWebView(
modifier = Modifier
.padding(
vertical = Spacing.xxs,
horizontal = if (fullWidthImage) 0.dp else Spacing.s,
),
blurred = blurNsfw && post.nsfw,
autoLoadImages = autoLoadImages,
url = post.embeddedUrl,
onOpen = {
if (postLinkUrl.isNotEmpty()) {
navigationCoordinator.handleUrl(
url = postLinkUrl,
openingMode = settings.urlOpeningMode.toUrlOpeningMode(),
uriHandler = uriHandler,
customTabsHelper = customTabsHelper,
onOpenWeb = onOpenWeb,
onOpenCommunity = onOpenCommunity,
onOpenPost = onOpenPost,
onOpenUser = onOpenCreator,
)
} else {
onClick?.invoke()
}
},
)
} else if (post.videoUrl.isNotEmpty()) {
PostCardVideo(
modifier = Modifier
.padding(
@ -595,7 +628,7 @@ private fun ExtendedPost(
}
}
}
if (postLinkUrl.isNotEmpty() && postLinkUrl != post.imageUrl && postLinkUrl != post.videoUrl) {
if (postLinkUrl.isNotEmpty()) {
PostLinkBanner(
modifier = Modifier
.padding(

View File

@ -0,0 +1,85 @@
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.text.style.TextOverflow
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CustomWebView
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.LocalXmlStrings
@Composable
fun PostCardEmbeddedWebView(
modifier: Modifier = Modifier,
url: String,
blurred: Boolean = false,
autoLoadImages: Boolean = true,
onOpen: (() -> Unit)? = null,
) {
if (url.isEmpty()) {
return
}
Box(
modifier = modifier.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
if (blurred) {
Column(
modifier = Modifier.padding(vertical = Spacing.s),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(Spacing.s),
) {
Text(
text = LocalXmlStrings.current.messageVideoNsfw,
style = MaterialTheme.typography.bodyMedium,
)
Button(
onClick = {
onOpen?.invoke()
},
) {
Text(
text = LocalXmlStrings.current.buttonLoad,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
} else {
var shouldBeRendered by remember(autoLoadImages) { mutableStateOf(autoLoadImages) }
if (shouldBeRendered) {
CustomWebView(
modifier = Modifier.aspectRatio(9f / 16f),
url = url,
)
} else {
Button(
modifier = Modifier.padding(vertical = Spacing.s),
onClick = {
shouldBeRendered = true
},
) {
Text(
text = LocalXmlStrings.current.buttonLoad,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
}
}

View File

@ -1,11 +1,13 @@
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
@ -21,9 +23,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.VideoPlayer
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.LocalXmlStrings
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
@Composable
fun PostCardVideo(
@ -39,15 +41,14 @@ fun PostCardVideo(
}
Box(
modifier = modifier
.fillMaxWidth()
.aspectRatio(1.33f)
.onClick(),
modifier = modifier.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
if (blurred) {
Column(
modifier = Modifier.padding(vertical = Spacing.s),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(Spacing.s),
) {
Text(
text = LocalXmlStrings.current.messageVideoNsfw,
@ -70,7 +71,7 @@ fun PostCardVideo(
var loading by remember { mutableStateOf(true) }
if (shouldBeRendered) {
VideoPlayer(
modifier = Modifier.fillMaxSize(),
modifier = Modifier.aspectRatio(4f / 3f),
url = url,
onPlaybackStarted = {
loading = false
@ -89,6 +90,7 @@ fun PostCardVideo(
}
} else {
Button(
modifier = Modifier.padding(vertical = Spacing.s),
onClick = {
shouldBeRendered = true
},

View File

@ -157,14 +157,22 @@ fun Boolean.toInboxDefaultType(): Int = if (this) 0 else 1
val String.looksLikeAnImage: Boolean
get() {
val imageExtensions = listOf(".jpeg", ".jpg", ".png", ".webp", ".gif")
return imageExtensions.any { this.endsWith(it) }
val extensions = listOf(".jpeg", ".jpg", ".png", ".webp", ".gif")
return extensions.any { this.endsWith(it) }
}
val String.looksLikeAVideo: Boolean
get() {
val imageExtensions = listOf(".mp4", ".mov", ".webm", ".avi")
return imageExtensions.any { this.endsWith(it) }
val extensions = listOf(".mp4", ".mov", ".webm", ".avi")
return extensions.any { this.endsWith(it) }
}
val String.showInEmbeddedWebView: Boolean
get() {
val patterns = listOf(
".redgifs.com/",
)
return patterns.any { this.contains(it) }
}

View File

@ -2,6 +2,7 @@ package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.looksLikeAVideo
import com.github.diegoberaldin.raccoonforlemmy.core.utils.looksLikeAnImage
import com.github.diegoberaldin.raccoonforlemmy.core.utils.showInEmbeddedWebView
data class PostModel(
val id: Long = 0,
@ -15,7 +16,6 @@ data class PostModel(
val unreadComments: Int? = null,
val thumbnailUrl: String? = null,
val url: String? = null,
val embedVideoUrl: String? = null,
val community: CommunityModel? = null,
val creator: UserModel? = null,
val saved: Boolean = false,
@ -34,7 +34,11 @@ data class PostModel(
)
val PostModel.imageUrl: String
get() = (thumbnailUrl?.takeIf { it.isNotEmpty() } ?: url?.takeIf { it.looksLikeAnImage }).orEmpty()
get() = (thumbnailUrl?.takeIf { it.isNotEmpty() }
?: url?.takeIf { it.looksLikeAnImage }).orEmpty()
val PostModel.videoUrl: String
get() = url?.takeIf { it.looksLikeAVideo }?.takeIf { it.isNotEmpty() }.orEmpty()
val PostModel.embeddedUrl: String
get() = url?.takeIf { it.showInEmbeddedWebView }?.takeIf { it.isNotEmpty() }.orEmpty()

View File

@ -199,7 +199,6 @@ internal fun Post.toModel() = PostModel(
publishDate = published,
updateDate = updated,
nsfw = nsfw,
embedVideoUrl = embedVideoUrl,
featuredCommunity = featuredCommunity,
featuredLocal = featuredLocal,
removed = removed,