feat: possibility to open URLs in internal WebView

This commit is contained in:
Diego Beraldin 2023-09-12 22:49:33 +02:00
parent c13ef232bd
commit 275fd50f76
23 changed files with 389 additions and 18 deletions

View File

@ -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
},
)
}

View File

@ -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,
)

View File

@ -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
)
}
}

View File

@ -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))
}
}
)
}
}

View File

@ -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))
}
}
)
}

View File

@ -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,

View File

@ -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
}

View File

@ -13,6 +13,10 @@ interface NavigationCoordinator {
fun setRootNavigator(value: Navigator?)
fun setCanGoBackCallback(value: (() -> Boolean)?)
fun getCanGoBackCallback(): (() -> Boolean)?
fun getRootNavigator(): Navigator?
fun setBottomBarScrollConnection(value: NestedScrollConnection?)

View File

@ -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,
)
}
}
}
}

View File

@ -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
}
)
}

View File

@ -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)

View File

@ -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
)
}
}
}

View File

@ -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)
}

View File

@ -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)
}
}
}
}

View File

@ -13,4 +13,5 @@ object KeyStoreKeys {
const val BlurNsfw = "blurNsfw"
const val NavItemTitlesVisible = "navItemTitlesVisible"
const val DynamicColors = "dynamicColors"
const val OpenUrlsInExternalBrowser = "openUrlsInExternalBrowser"
}

View File

@ -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),

View File

@ -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 = "",
)

View File

@ -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)
}
}

View File

@ -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"

View File

@ -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>

View File

@ -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>

View File

@ -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()