mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-09 11:38:40 +01:00
feat: possibility to open URLs in internal WebView
This commit is contained in:
parent
c13ef232bd
commit
275fd50f76
@ -0,0 +1,55 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.components
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Bitmap
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Composable
|
||||
actual fun CustomWebView(
|
||||
navigator: WebViewNavigator,
|
||||
modifier: Modifier,
|
||||
url: String,
|
||||
) {
|
||||
var webView: WebView? = null
|
||||
|
||||
LaunchedEffect(true) {
|
||||
navigator.events.onEach {
|
||||
when (it) {
|
||||
WebViewNavigationEvent.GoBack -> webView?.goBack()
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
AndroidView(
|
||||
modifier = modifier,
|
||||
factory = { context ->
|
||||
WebView(context).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
webViewClient = object : WebViewClient() {
|
||||
override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
|
||||
navigator.canGoBack = view.canGoBack()
|
||||
}
|
||||
}
|
||||
settings.javaScriptEnabled = true
|
||||
|
||||
loadUrl(url)
|
||||
webView = this
|
||||
}
|
||||
},
|
||||
update = {
|
||||
webView = it
|
||||
},
|
||||
)
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
expect fun CustomWebView(
|
||||
navigator: WebViewNavigator = rememberWebViewNavigator(),
|
||||
modifier: Modifier = Modifier,
|
||||
url: String,
|
||||
)
|
@ -0,0 +1,35 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal sealed interface WebViewNavigationEvent {
|
||||
object GoBack : WebViewNavigationEvent
|
||||
}
|
||||
|
||||
class WebViewNavigator(
|
||||
private val coroutineScope: CoroutineScope,
|
||||
) {
|
||||
var canGoBack: Boolean = true
|
||||
internal val events = MutableSharedFlow<WebViewNavigationEvent>()
|
||||
|
||||
fun goBack() {
|
||||
coroutineScope.launch {
|
||||
events.emit(WebViewNavigationEvent.GoBack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberWebViewNavigator(): WebViewNavigator {
|
||||
val scope = rememberCoroutineScope()
|
||||
return remember {
|
||||
WebViewNavigator(
|
||||
coroutineScope = scope
|
||||
)
|
||||
}
|
||||
}
|
@ -2,16 +2,26 @@ package com.github.diegoberaldin.raccoonforlemmy.core.commonui.components
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.web.WebViewScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.compose.Markdown
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.markdownColor
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.markdownTypography
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.KeyStoreKeys
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.di.getTemporaryKeyStore
|
||||
|
||||
@Composable
|
||||
fun PostCardBody(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val navigator = remember { getNavigationCoordinator().getRootNavigator() }
|
||||
val keyStore = remember { getTemporaryKeyStore() }
|
||||
|
||||
if (text.isNotEmpty()) {
|
||||
Markdown(
|
||||
modifier = modifier,
|
||||
@ -30,6 +40,14 @@ fun PostCardBody(
|
||||
text = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
backgroundCode = MaterialTheme.colorScheme.surfaceVariant,
|
||||
),
|
||||
onOpenUrl = { url ->
|
||||
val openExternal = keyStore[KeyStoreKeys.OpenUrlsInExternalBrowser, false]
|
||||
if (openExternal) {
|
||||
uriHandler.openUri(url)
|
||||
} else {
|
||||
navigator?.push(WebViewScreen(url))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2,16 +2,26 @@ package com.github.diegoberaldin.raccoonforlemmy.core.commonui.components
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.web.WebViewScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.compose.Markdown
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.markdownColor
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.markdownTypography
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.KeyStoreKeys
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.di.getTemporaryKeyStore
|
||||
|
||||
@Composable
|
||||
fun PostCardTitle(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val navigator = remember { getNavigationCoordinator().getRootNavigator() }
|
||||
val keyStore = remember { getTemporaryKeyStore() }
|
||||
|
||||
Markdown(
|
||||
modifier = modifier,
|
||||
content = text,
|
||||
@ -22,5 +32,13 @@ fun PostCardTitle(
|
||||
text = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
backgroundCode = MaterialTheme.colorScheme.surfaceVariant,
|
||||
),
|
||||
onOpenUrl = { url ->
|
||||
val openExternal = keyStore[KeyStoreKeys.OpenUrlsInExternalBrowser, false]
|
||||
if (openExternal) {
|
||||
uriHandler.openUri(url)
|
||||
} else {
|
||||
navigator?.push(WebViewScreen(url))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
@ -18,6 +19,10 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.github.diegoberaldin.racconforlemmy.core.utils.onClick
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.CornerSize
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.web.WebViewScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.KeyStoreKeys
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.di.getTemporaryKeyStore
|
||||
|
||||
@Composable
|
||||
fun PostLinkBanner(
|
||||
@ -25,6 +30,9 @@ fun PostLinkBanner(
|
||||
url: String,
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val navigator = remember { getNavigationCoordinator().getRootNavigator() }
|
||||
val keyStore = remember { getTemporaryKeyStore() }
|
||||
|
||||
if (url.isNotEmpty()) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
@ -32,7 +40,12 @@ fun PostLinkBanner(
|
||||
color = MaterialTheme.colorScheme.tertiary.copy(alpha = 0.1f),
|
||||
shape = RoundedCornerShape(CornerSize.l),
|
||||
).onClick {
|
||||
uriHandler.openUri(url)
|
||||
val openExternal = keyStore[KeyStoreKeys.OpenUrlsInExternalBrowser, false]
|
||||
if (openExternal) {
|
||||
uriHandler.openUri(url)
|
||||
} else {
|
||||
navigator?.push(WebViewScreen(url))
|
||||
}
|
||||
}.padding(
|
||||
horizontal = Spacing.m,
|
||||
vertical = Spacing.s,
|
||||
|
@ -16,6 +16,7 @@ internal class DefaultNavigationCoordinator : NavigationCoordinator {
|
||||
private var navigator: Navigator? = null
|
||||
private var currentTab: Tab? = null
|
||||
private val scope = CoroutineScope(SupervisorJob())
|
||||
private var canGoBackCallback: (() -> Boolean)? = null
|
||||
|
||||
override fun setRootNavigator(value: Navigator?) {
|
||||
navigator = value
|
||||
@ -38,4 +39,10 @@ internal class DefaultNavigationCoordinator : NavigationCoordinator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun setCanGoBackCallback(value: (() -> Boolean)?) {
|
||||
canGoBackCallback = value
|
||||
}
|
||||
|
||||
override fun getCanGoBackCallback(): (() -> Boolean)? = canGoBackCallback
|
||||
}
|
@ -13,6 +13,10 @@ interface NavigationCoordinator {
|
||||
|
||||
fun setRootNavigator(value: Navigator?)
|
||||
|
||||
fun setCanGoBackCallback(value: (() -> Boolean)?)
|
||||
|
||||
fun getCanGoBackCallback(): (() -> Boolean)?
|
||||
|
||||
fun getRootNavigator(): Navigator?
|
||||
|
||||
fun setBottomBarScrollConnection(value: NestedScrollConnection?)
|
||||
|
@ -0,0 +1,76 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.web
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import com.github.diegoberaldin.racconforlemmy.core.utils.onClick
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CustomWebView
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.rememberWebViewNavigator
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
|
||||
|
||||
class WebViewScreen(
|
||||
private val url: String,
|
||||
) : Screen {
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = remember { getNavigationCoordinator().getRootNavigator() }
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {},
|
||||
navigationIcon = {
|
||||
Image(
|
||||
modifier = Modifier.onClick {
|
||||
navigator?.pop()
|
||||
},
|
||||
imageVector = Icons.Default.ArrowBack,
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
Box(
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
) {
|
||||
val navigationCoordinator = remember { getNavigationCoordinator() }
|
||||
val webNavigator = rememberWebViewNavigator()
|
||||
|
||||
DisposableEffect(key) {
|
||||
navigationCoordinator.setCanGoBackCallback {
|
||||
val result = webNavigator.canGoBack
|
||||
if (result) {
|
||||
webNavigator.goBack()
|
||||
return@setCanGoBackCallback false
|
||||
}
|
||||
true
|
||||
}
|
||||
onDispose {
|
||||
navigationCoordinator.setCanGoBackCallback(null)
|
||||
}
|
||||
}
|
||||
|
||||
CustomWebView(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
navigator = webNavigator,
|
||||
url = url,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.interop.UIKitView
|
||||
import kotlinx.cinterop.readValue
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import platform.CoreGraphics.CGRectZero
|
||||
import platform.WebKit.WKNavigation
|
||||
import platform.WebKit.WKNavigationDelegateProtocol
|
||||
import platform.WebKit.WKWebView
|
||||
import platform.WebKit.WKWebViewConfiguration
|
||||
import platform.darwin.NSObject
|
||||
|
||||
@Composable
|
||||
actual fun CustomWebView(
|
||||
navigator: WebViewNavigator,
|
||||
modifier: Modifier,
|
||||
url: String,
|
||||
) {
|
||||
var webView: WKWebView? = null
|
||||
|
||||
LaunchedEffect(true) {
|
||||
navigator.events.onEach {
|
||||
when (it) {
|
||||
WebViewNavigationEvent.GoBack -> webView?.goBack()
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
UIKitView(
|
||||
factory = {
|
||||
val config = WKWebViewConfiguration().apply {
|
||||
allowsInlineMediaPlayback = true
|
||||
}
|
||||
WKWebView(
|
||||
frame = CGRectZero.readValue(),
|
||||
configuration = config
|
||||
).apply {
|
||||
userInteractionEnabled = true
|
||||
allowsBackForwardNavigationGestures = true
|
||||
val navigationDelegate = object : NSObject(), WKNavigationDelegateProtocol {
|
||||
|
||||
override fun webView(
|
||||
webView: WKWebView,
|
||||
didFinishNavigation: WKNavigation?,
|
||||
) {
|
||||
navigator.canGoBack = webView.canGoBack
|
||||
}
|
||||
}
|
||||
this.navigationDelegate = navigationDelegate
|
||||
}.also {
|
||||
webView = it
|
||||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
onRelease = {
|
||||
webView = null
|
||||
}
|
||||
)
|
||||
}
|
@ -59,6 +59,7 @@ fun Markdown(
|
||||
padding: MarkdownPadding = markdownPadding(),
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
flavour: MarkdownFlavourDescriptor = GFMFlavourDescriptor(),
|
||||
onOpenUrl: ((String) -> Unit)? = null,
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalReferenceLinkHandler provides ReferenceLinkHandlerImpl(),
|
||||
@ -69,9 +70,9 @@ fun Markdown(
|
||||
Column(modifier) {
|
||||
val parsedTree = MarkdownParser(flavour).buildMarkdownTreeFromString(content)
|
||||
parsedTree.children.forEach { node ->
|
||||
if (!node.handleElement(content)) {
|
||||
if (!node.handleElement(content, onOpenUrl)) {
|
||||
node.children.forEach { child ->
|
||||
child.handleElement(content)
|
||||
child.handleElement(content, onOpenUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -80,12 +81,15 @@ fun Markdown(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ASTNode.handleElement(content: String): Boolean {
|
||||
private fun ASTNode.handleElement(
|
||||
content: String,
|
||||
onOpenUrl: ((String) -> Unit)? = null,
|
||||
): Boolean {
|
||||
val typography = LocalMarkdownTypography.current
|
||||
var handled = true
|
||||
Spacer(Modifier.height(LocalMarkdownPadding.current.block))
|
||||
when (type) {
|
||||
TEXT -> MarkdownText(getTextInNode(content).toString())
|
||||
TEXT -> MarkdownText(getTextInNode(content).toString(), onOpenUrl = onOpenUrl)
|
||||
EOL -> {}
|
||||
CODE_FENCE -> MarkdownCodeFence(content, this)
|
||||
CODE_BLOCK -> MarkdownCodeBlock(content, this)
|
||||
@ -96,13 +100,29 @@ private fun ASTNode.handleElement(content: String): Boolean {
|
||||
ATX_5 -> MarkdownHeader(content, this, typography.h5)
|
||||
ATX_6 -> MarkdownHeader(content, this, typography.h6)
|
||||
BLOCK_QUOTE -> MarkdownBlockQuote(content, this)
|
||||
PARAGRAPH -> MarkdownParagraph(content, this, style = typography.paragraph)
|
||||
PARAGRAPH -> MarkdownParagraph(
|
||||
content,
|
||||
this,
|
||||
style = typography.paragraph,
|
||||
onOpenUrl = onOpenUrl
|
||||
)
|
||||
|
||||
ORDERED_LIST -> Column(modifier = Modifier) {
|
||||
MarkdownOrderedList(content, this@handleElement, style = typography.ordered)
|
||||
MarkdownOrderedList(
|
||||
content,
|
||||
this@handleElement,
|
||||
style = typography.ordered,
|
||||
onOpenUrl = onOpenUrl
|
||||
)
|
||||
}
|
||||
|
||||
UNORDERED_LIST -> Column(modifier = Modifier) {
|
||||
MarkdownBulletList(content, this@handleElement, style = typography.bullet)
|
||||
MarkdownBulletList(
|
||||
content,
|
||||
this@handleElement,
|
||||
style = typography.bullet,
|
||||
onOpenUrl = onOpenUrl
|
||||
)
|
||||
}
|
||||
|
||||
IMAGE -> MarkdownImage(content, this)
|
||||
|
@ -65,6 +65,7 @@ internal fun MarkdownOrderedList(
|
||||
node: ASTNode,
|
||||
style: TextStyle = LocalMarkdownTypography.current.ordered,
|
||||
level: Int = 0,
|
||||
onOpenUrl: ((String) -> Unit)? = null,
|
||||
) {
|
||||
val orderedListHandler = LocalOrderedListHandler.current
|
||||
MarkdownListItems(content, node, style, level) { child ->
|
||||
@ -81,7 +82,12 @@ internal fun MarkdownOrderedList(
|
||||
buildMarkdownAnnotatedString(content, child.children.filterNonListTypes())
|
||||
pop()
|
||||
}
|
||||
MarkdownText(text, Modifier.padding(bottom = 4.dp), style = style)
|
||||
MarkdownText(
|
||||
text,
|
||||
Modifier.padding(bottom = 4.dp),
|
||||
style = style,
|
||||
onOpenUrl = onOpenUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -92,6 +98,7 @@ internal fun MarkdownBulletList(
|
||||
node: ASTNode,
|
||||
style: TextStyle = LocalMarkdownTypography.current.bullet,
|
||||
level: Int = 0,
|
||||
onOpenUrl: ((String) -> Unit)? = null,
|
||||
) {
|
||||
val bulletHandler = LocalBulletListHandler.current
|
||||
MarkdownListItems(content, node, style, level) { child ->
|
||||
@ -108,7 +115,12 @@ internal fun MarkdownBulletList(
|
||||
buildMarkdownAnnotatedString(content, child.children.filterNonListTypes())
|
||||
pop()
|
||||
}
|
||||
MarkdownText(text, Modifier.padding(bottom = 4.dp), style = style)
|
||||
MarkdownText(
|
||||
text,
|
||||
Modifier.padding(bottom = 4.dp),
|
||||
style = style,
|
||||
onOpenUrl = onOpenUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,11 +12,12 @@ internal fun MarkdownParagraph(
|
||||
content: String,
|
||||
node: ASTNode,
|
||||
style: TextStyle = LocalMarkdownTypography.current.paragraph,
|
||||
onOpenUrl: ((String) -> Unit)? = null,
|
||||
) {
|
||||
val styledText = buildAnnotatedString {
|
||||
pushStyle(style.toSpanStyle())
|
||||
buildMarkdownAnnotatedString(content, node)
|
||||
pop()
|
||||
}
|
||||
MarkdownText(styledText, style = style)
|
||||
MarkdownText(styledText, style = style, onOpenUrl = onOpenUrl)
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.Placeholder
|
||||
import androidx.compose.ui.text.PlaceholderVerticalAlign
|
||||
@ -30,8 +29,9 @@ internal fun MarkdownText(
|
||||
content: String,
|
||||
modifier: Modifier = Modifier,
|
||||
style: TextStyle = LocalMarkdownTypography.current.text,
|
||||
onOpenUrl: ((String) -> Unit)? = null,
|
||||
) {
|
||||
MarkdownText(AnnotatedString(content), modifier, style)
|
||||
MarkdownText(AnnotatedString(content), modifier, style, onOpenUrl)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ -39,8 +39,8 @@ internal fun MarkdownText(
|
||||
content: AnnotatedString,
|
||||
modifier: Modifier = Modifier,
|
||||
style: TextStyle = LocalMarkdownTypography.current.text,
|
||||
onOpenUrl: ((String) -> Unit)? = null,
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val referenceLinkHandler = LocalReferenceLinkHandler.current
|
||||
val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) }
|
||||
|
||||
@ -52,7 +52,10 @@ internal fun MarkdownText(
|
||||
val position = layoutResult.getOffsetForPosition(pos)
|
||||
content.getStringAnnotations(TAG_URL, position, position)
|
||||
.firstOrNull()
|
||||
?.let { uriHandler.openUri(referenceLinkHandler.find(it.item)) }
|
||||
?.let {
|
||||
val url = referenceLinkHandler.find(it.item)
|
||||
onOpenUrl?.invoke(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,4 +13,5 @@ object KeyStoreKeys {
|
||||
const val BlurNsfw = "blurNsfw"
|
||||
const val NavItemTitlesVisible = "navItemTitlesVisible"
|
||||
const val DynamicColors = "dynamicColors"
|
||||
const val OpenUrlsInExternalBrowser = "openUrlsInExternalBrowser"
|
||||
}
|
||||
|
@ -240,6 +240,19 @@ class SettingsScreen : Screen {
|
||||
)
|
||||
}
|
||||
|
||||
// URL open
|
||||
SettingsSwitchRow(
|
||||
title = stringResource(MR.strings.settings_open_url_external),
|
||||
value = uiState.openUrlsInExternalBrowser,
|
||||
onValueChanged = { value ->
|
||||
model.reduce(
|
||||
SettingsScreenMviModel.Intent.ChangeOpenUrlsInExternalBrowser(
|
||||
value
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
// NSFW options
|
||||
SettingsSwitchRow(
|
||||
title = stringResource(MR.strings.settings_include_nsfw),
|
||||
|
@ -20,6 +20,7 @@ interface SettingsScreenMviModel :
|
||||
data class ChangeDynamicColors(val value: Boolean) : Intent
|
||||
data class ChangeIncludeNsfw(val value: Boolean) : Intent
|
||||
data class ChangeBlurNsfw(val value: Boolean) : Intent
|
||||
data class ChangeOpenUrlsInExternalBrowser(val value: Boolean) : Intent
|
||||
}
|
||||
|
||||
data class UiState(
|
||||
@ -35,6 +36,7 @@ interface SettingsScreenMviModel :
|
||||
val dynamicColors: Boolean = false,
|
||||
val includeNsfw: Boolean = true,
|
||||
val blurNsfw: Boolean = true,
|
||||
val openUrlsInExternalBrowser: Boolean = false,
|
||||
val appVersion: String = "",
|
||||
)
|
||||
|
||||
|
@ -82,6 +82,7 @@ class SettingsScreenViewModel(
|
||||
val listingType = keyStore[KeyStoreKeys.DefaultListingType, 0].toListingType()
|
||||
val postSortType = keyStore[KeyStoreKeys.DefaultPostSortType, 0].toSortType()
|
||||
val commentSortType = keyStore[KeyStoreKeys.DefaultCommentSortType, 3].toSortType()
|
||||
val openUrlsInExternalBrowser = keyStore[KeyStoreKeys.OpenUrlsInExternalBrowser, false]
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
defaultListingType = listingType,
|
||||
@ -89,8 +90,9 @@ class SettingsScreenViewModel(
|
||||
defaultCommentSortType = commentSortType,
|
||||
includeNsfw = keyStore[KeyStoreKeys.IncludeNsfw, true],
|
||||
blurNsfw = keyStore[KeyStoreKeys.BlurNsfw, true],
|
||||
appVersion = AppInfo.versionCode,
|
||||
supportsDynamicColors = colorSchemeProvider.supportsDynamicColors,
|
||||
openUrlsInExternalBrowser = openUrlsInExternalBrowser,
|
||||
appVersion = AppInfo.versionCode,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -136,6 +138,10 @@ class SettingsScreenViewModel(
|
||||
is SettingsScreenMviModel.Intent.ChangeDynamicColors -> {
|
||||
changeDynamicColors(intent.value)
|
||||
}
|
||||
|
||||
is SettingsScreenMviModel.Intent.ChangeOpenUrlsInExternalBrowser -> {
|
||||
changeOpenUrlsInExternalBrowser(intent.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,4 +194,9 @@ class SettingsScreenViewModel(
|
||||
themeRepository.changeDynamicColors(value)
|
||||
keyStore.save(KeyStoreKeys.DynamicColors, value)
|
||||
}
|
||||
|
||||
private fun changeOpenUrlsInExternalBrowser(value: Boolean) {
|
||||
mvi.updateState { it.copy(openUrlsInExternalBrowser = value) }
|
||||
keyStore.save(KeyStoreKeys.OpenUrlsInExternalBrowser, value)
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
androidx_activity_compose = "1.7.2"
|
||||
androidx_crypto = "1.0.0"
|
||||
android_gradle = "7.4.2"
|
||||
compose = "1.4.3"
|
||||
compose = "1.5.1"
|
||||
crashlytics = "18.4.1"
|
||||
crashlytics_gradle = "2.9.9"
|
||||
gms_gradle = "4.3.15"
|
||||
|
@ -94,6 +94,7 @@
|
||||
<string name="settings_blur_nsfw">Blur NSFW images</string>
|
||||
<string name="settings_app_version">App version</string>
|
||||
<string name="settings_dynamic_colors">Use dynamic colors</string>
|
||||
<string name="settings_open_url_external">Open URLs in external browser</string>
|
||||
|
||||
<string name="community_button_subscribe">Subscribe</string>
|
||||
<string name="community_button_subscribed">Subscribed</string>
|
||||
|
@ -92,6 +92,7 @@
|
||||
<string name="settings_blur_nsfw">Sfuma immagini NSFW</string>
|
||||
<string name="settings_app_version">Versione app</string>
|
||||
<string name="settings_dynamic_colors">Usa colori dinamici</string>
|
||||
<string name="settings_open_url_external">Apri URL su browser esterno</string>
|
||||
|
||||
<string name="community_button_subscribe">Iscriviti</string>
|
||||
<string name="community_button_subscribed">Iscritto</string>
|
||||
|
@ -77,7 +77,13 @@ fun App() {
|
||||
LaunchedEffect(lang) {}
|
||||
|
||||
BottomSheetNavigator {
|
||||
Navigator(MainScreen()) {
|
||||
Navigator(
|
||||
screen = MainScreen(),
|
||||
onBackPressed = {
|
||||
val callback = navigationCoordinator.getCanGoBackCallback()
|
||||
callback?.let { it() } ?: true
|
||||
}
|
||||
) {
|
||||
val navigator = LocalNavigator.current
|
||||
navigationCoordinator.setRootNavigator(navigator)
|
||||
CurrentScreen()
|
||||
|
Loading…
x
Reference in New Issue
Block a user