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.compose.rememberCallbackArgs
import com.github.diegoberaldin.raccoonforlemmy.core.utils.looksLikeAVideo import com.github.diegoberaldin.raccoonforlemmy.core.utils.looksLikeAVideo
import com.github.diegoberaldin.raccoonforlemmy.core.utils.looksLikeAnImage 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.getCustomTabsHelper
import com.github.diegoberaldin.raccoonforlemmy.core.utils.url.toUrlOpeningMode 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.CommunityModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel 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.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.imageUrl
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.videoUrl import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.videoUrl
@ -420,7 +422,11 @@ private fun ExtendedPost(
val navigationCoordinator = remember { getNavigationCoordinator() } val navigationCoordinator = remember { getNavigationCoordinator() }
val optionsMenuOpen = remember { mutableStateOf(false) } val optionsMenuOpen = remember { mutableStateOf(false) }
val postLinkUrl = post.url.orEmpty().takeIf { 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() }.orEmpty()
Column( 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( PostCardVideo(
modifier = Modifier modifier = Modifier
.padding( .padding(
@ -595,7 +628,7 @@ private fun ExtendedPost(
} }
} }
} }
if (postLinkUrl.isNotEmpty() && postLinkUrl != post.imageUrl && postLinkUrl != post.videoUrl) { if (postLinkUrl.isNotEmpty()) {
PostLinkBanner( PostLinkBanner(
modifier = Modifier modifier = Modifier
.padding( .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 package com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
@ -21,9 +23,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp 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.commonui.components.VideoPlayer
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.LocalXmlStrings import com.github.diegoberaldin.raccoonforlemmy.core.l10n.LocalXmlStrings
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
@Composable @Composable
fun PostCardVideo( fun PostCardVideo(
@ -39,15 +41,14 @@ fun PostCardVideo(
} }
Box( Box(
modifier = modifier modifier = modifier.fillMaxWidth(),
.fillMaxWidth()
.aspectRatio(1.33f)
.onClick(),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
if (blurred) { if (blurred) {
Column( Column(
modifier = Modifier.padding(vertical = Spacing.s),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(Spacing.s),
) { ) {
Text( Text(
text = LocalXmlStrings.current.messageVideoNsfw, text = LocalXmlStrings.current.messageVideoNsfw,
@ -70,7 +71,7 @@ fun PostCardVideo(
var loading by remember { mutableStateOf(true) } var loading by remember { mutableStateOf(true) }
if (shouldBeRendered) { if (shouldBeRendered) {
VideoPlayer( VideoPlayer(
modifier = Modifier.fillMaxSize(), modifier = Modifier.aspectRatio(4f / 3f),
url = url, url = url,
onPlaybackStarted = { onPlaybackStarted = {
loading = false loading = false
@ -89,6 +90,7 @@ fun PostCardVideo(
} }
} else { } else {
Button( Button(
modifier = Modifier.padding(vertical = Spacing.s),
onClick = { onClick = {
shouldBeRendered = true shouldBeRendered = true
}, },

View File

@ -157,14 +157,22 @@ fun Boolean.toInboxDefaultType(): Int = if (this) 0 else 1
val String.looksLikeAnImage: Boolean val String.looksLikeAnImage: Boolean
get() { get() {
val imageExtensions = listOf(".jpeg", ".jpg", ".png", ".webp", ".gif") val extensions = listOf(".jpeg", ".jpg", ".png", ".webp", ".gif")
return imageExtensions.any { this.endsWith(it) } return extensions.any { this.endsWith(it) }
} }
val String.looksLikeAVideo: Boolean val String.looksLikeAVideo: Boolean
get() { get() {
val imageExtensions = listOf(".mp4", ".mov", ".webm", ".avi") val extensions = listOf(".mp4", ".mov", ".webm", ".avi")
return imageExtensions.any { this.endsWith(it) } 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.looksLikeAVideo
import com.github.diegoberaldin.raccoonforlemmy.core.utils.looksLikeAnImage import com.github.diegoberaldin.raccoonforlemmy.core.utils.looksLikeAnImage
import com.github.diegoberaldin.raccoonforlemmy.core.utils.showInEmbeddedWebView
data class PostModel( data class PostModel(
val id: Long = 0, val id: Long = 0,
@ -15,7 +16,6 @@ data class PostModel(
val unreadComments: Int? = null, val unreadComments: Int? = null,
val thumbnailUrl: String? = null, val thumbnailUrl: String? = null,
val url: String? = null, val url: String? = null,
val embedVideoUrl: String? = null,
val community: CommunityModel? = null, val community: CommunityModel? = null,
val creator: UserModel? = null, val creator: UserModel? = null,
val saved: Boolean = false, val saved: Boolean = false,
@ -34,7 +34,11 @@ data class PostModel(
) )
val PostModel.imageUrl: String 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 val PostModel.videoUrl: String
get() = url?.takeIf { it.looksLikeAVideo }?.takeIf { it.isNotEmpty() }.orEmpty() 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, publishDate = published,
updateDate = updated, updateDate = updated,
nsfw = nsfw, nsfw = nsfw,
embedVideoUrl = embedVideoUrl,
featuredCommunity = featuredCommunity, featuredCommunity = featuredCommunity,
featuredLocal = featuredLocal, featuredLocal = featuredLocal,
removed = removed, removed = removed,