mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-09 15:48:44 +01:00
feat: support embedded web view in posts (#799)
This commit is contained in:
parent
5247b0975c
commit
707f6f8a32
@ -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(
|
||||||
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
},
|
},
|
||||||
|
@ -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) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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()
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user