new: extract WebView feature module
This commit is contained in:
@@ -0,0 +1,50 @@
|
|||||||
|
plugins {
|
||||||
|
id("gwenedeg.kmp.library")
|
||||||
|
id("gwenedeg.compose")
|
||||||
|
alias(libs.plugins.kotlinSerialization)
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
androidLibrary {
|
||||||
|
namespace = "bzh.ajaury.chombev.webview"
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
androidMain.dependencies {
|
||||||
|
implementation(libs.compose.uiToolingPreview)
|
||||||
|
implementation(libs.androidx.webkit)
|
||||||
|
}
|
||||||
|
commonMain.dependencies {
|
||||||
|
// Core modules
|
||||||
|
implementation(projects.core.logging)
|
||||||
|
|
||||||
|
// Reads the bundled offline page snapshots and web strings
|
||||||
|
implementation(projects.data.resources)
|
||||||
|
|
||||||
|
// Serialization: WebPage is carried in the serializable navigation back stack
|
||||||
|
implementation(libs.kotlinx.serializationJson)
|
||||||
|
|
||||||
|
// UI - Compose
|
||||||
|
implementation(libs.compose.runtime)
|
||||||
|
implementation(libs.compose.foundation)
|
||||||
|
implementation(libs.compose.material3)
|
||||||
|
implementation(libs.compose.ui)
|
||||||
|
implementation(libs.compose.uiToolingPreview)
|
||||||
|
implementation(libs.compose.components.resources)
|
||||||
|
|
||||||
|
// Lifecycle
|
||||||
|
implementation(libs.lifecycle.viewmodelCompose)
|
||||||
|
implementation(libs.lifecycle.runtimeCompose)
|
||||||
|
|
||||||
|
// DI
|
||||||
|
implementation(project.dependencies.platform(libs.koin.bom))
|
||||||
|
implementation(libs.koin.core)
|
||||||
|
implementation(libs.koin.core.viewmodel)
|
||||||
|
implementation(libs.koin.compose.viewmodel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
androidRuntimeClasspath(libs.compose.uiTooling)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package bzh.ajaury.chombev.web.di
|
||||||
|
|
||||||
|
import bzh.ajaury.chombev.web.ui.viewmodel.WebViewModel
|
||||||
|
import org.koin.core.module.Module
|
||||||
|
import org.koin.core.module.dsl.viewModelOf
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val webviewModule: Module = module {
|
||||||
|
viewModelOf(::WebViewModel)
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package bzh.ajaury.chombev.web.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web page with a distant and local fallback.
|
||||||
|
*
|
||||||
|
* [offlineAsset] is the resource path of a bundled HTML snapshot shown when the online [url] cannot
|
||||||
|
* be reached, so the page still has content offline.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class WebPage(
|
||||||
|
val url: String,
|
||||||
|
val offlineAsset: String,
|
||||||
|
)
|
||||||
+1
-2
@@ -9,7 +9,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import bzh.ajaury.chombev.theme.ChomBevTheme
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Full-surface loading overlay shown on top of the embedded web view while a page is loading. The
|
* Full-surface loading overlay shown on top of the embedded web view while a page is loading. The
|
||||||
@@ -29,7 +28,7 @@ internal fun WebViewLoader(modifier: Modifier = Modifier) {
|
|||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun WebViewLoaderPreview() {
|
private fun WebViewLoaderPreview() {
|
||||||
ChomBevTheme {
|
MaterialTheme {
|
||||||
WebViewLoader(modifier = Modifier.fillMaxSize())
|
WebViewLoader(modifier = Modifier.fillMaxSize())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -12,7 +12,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import bzh.ajaury.chombev.navigation.WebPage
|
import bzh.ajaury.chombev.web.model.WebPage
|
||||||
import bzh.ajaury.chombev.web.ui.viewmodel.WebUiState
|
import bzh.ajaury.chombev.web.ui.viewmodel.WebUiState
|
||||||
import bzh.ajaury.chombev.web.ui.viewmodel.WebViewModel
|
import bzh.ajaury.chombev.web.ui.viewmodel.WebViewModel
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
@@ -27,7 +27,7 @@ fun WebViewScreen(
|
|||||||
page: WebPage,
|
page: WebPage,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
viewModel: WebViewModel = koinViewModel(key = page.name) { parametersOf(page) },
|
viewModel: WebViewModel = koinViewModel(key = page.url) { parametersOf(page) },
|
||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
+1
-2
@@ -17,7 +17,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import bzh.ajaury.chombev.resources.generated.resources.Res
|
import bzh.ajaury.chombev.resources.generated.resources.Res
|
||||||
import bzh.ajaury.chombev.resources.generated.resources.web_open_in_browser
|
import bzh.ajaury.chombev.resources.generated.resources.web_open_in_browser
|
||||||
import bzh.ajaury.chombev.resources.generated.resources.web_unavailable_message
|
import bzh.ajaury.chombev.resources.generated.resources.web_unavailable_message
|
||||||
import bzh.ajaury.chombev.theme.ChomBevTheme
|
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,7 +51,7 @@ internal fun WebViewUnavailable(
|
|||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun WebViewUnavailablePreview() {
|
private fun WebViewUnavailablePreview() {
|
||||||
ChomBevTheme {
|
MaterialTheme {
|
||||||
WebViewUnavailable(url = "https://ksk.bzh")
|
WebViewUnavailable(url = "https://ksk.bzh")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -3,8 +3,8 @@ package bzh.ajaury.chombev.web.ui.viewmodel
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import bzh.ajaury.chombev.core.logging.domain.Logger
|
import bzh.ajaury.chombev.core.logging.domain.Logger
|
||||||
import bzh.ajaury.chombev.navigation.WebPage
|
|
||||||
import bzh.ajaury.chombev.resources.domain.ResourceReader
|
import bzh.ajaury.chombev.resources.domain.ResourceReader
|
||||||
|
import bzh.ajaury.chombev.web.model.WebPage
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
+8
-3
@@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.interop.UIKitView
|
import androidx.compose.ui.interop.UIKitView
|
||||||
import kotlinx.cinterop.ExperimentalForeignApi
|
import kotlinx.cinterop.ExperimentalForeignApi
|
||||||
import kotlinx.cinterop.ObjCSignatureOverride
|
import kotlinx.cinterop.ObjCSignatureOverride
|
||||||
|
import kotlinx.cinterop.readValue
|
||||||
import platform.CoreGraphics.CGRectZero
|
import platform.CoreGraphics.CGRectZero
|
||||||
import platform.Foundation.NSError
|
import platform.Foundation.NSError
|
||||||
import platform.Foundation.NSURL
|
import platform.Foundation.NSURL
|
||||||
@@ -41,7 +42,8 @@ actual fun PlatformWebView(
|
|||||||
frame = CGRectZero.readValue(),
|
frame = CGRectZero.readValue(),
|
||||||
configuration = WKWebViewConfiguration(),
|
configuration = WKWebViewConfiguration(),
|
||||||
).apply {
|
).apply {
|
||||||
navigationDelegate = navigationDelegate
|
// Qualify with `this` so it targets the web view's property, not the local val above.
|
||||||
|
this.navigationDelegate = navigationDelegate
|
||||||
setOverrideUserInterfaceStyle(darkTheme.toInterfaceStyle())
|
setOverrideUserInterfaceStyle(darkTheme.toInterfaceStyle())
|
||||||
NSURL.URLWithString(url)?.let { nsUrl ->
|
NSURL.URLWithString(url)?.let { nsUrl ->
|
||||||
loadRequest(NSURLRequest(uRL = nsUrl))
|
loadRequest(NSURLRequest(uRL = nsUrl))
|
||||||
@@ -71,6 +73,10 @@ private class WebViewNavigationDelegate :
|
|||||||
|
|
||||||
private var showingOffline = false
|
private var showingOffline = false
|
||||||
|
|
||||||
|
// The four delegate callbacks below collapse to two Kotlin signatures — (WKWebView, WKNavigation?)
|
||||||
|
// and (WKWebView, WKNavigation?, NSError) — so every colliding override is marked
|
||||||
|
// @ObjCSignatureOverride.
|
||||||
|
@ObjCSignatureOverride
|
||||||
override fun webView(
|
override fun webView(
|
||||||
webView: WKWebView,
|
webView: WKWebView,
|
||||||
didStartProvisionalNavigation: WKNavigation?,
|
didStartProvisionalNavigation: WKNavigation?,
|
||||||
@@ -78,7 +84,6 @@ private class WebViewNavigationDelegate :
|
|||||||
onLoadingStateChange(true)
|
onLoadingStateChange(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shares the (WKWebView, WKNavigation?) Kotlin signature with didStartProvisionalNavigation above.
|
|
||||||
@ObjCSignatureOverride
|
@ObjCSignatureOverride
|
||||||
override fun webView(
|
override fun webView(
|
||||||
webView: WKWebView,
|
webView: WKWebView,
|
||||||
@@ -87,6 +92,7 @@ private class WebViewNavigationDelegate :
|
|||||||
onLoadingStateChange(false)
|
onLoadingStateChange(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ObjCSignatureOverride
|
||||||
override fun webView(
|
override fun webView(
|
||||||
webView: WKWebView,
|
webView: WKWebView,
|
||||||
didFailNavigation: WKNavigation?,
|
didFailNavigation: WKNavigation?,
|
||||||
@@ -95,7 +101,6 @@ private class WebViewNavigationDelegate :
|
|||||||
handleFailure(webView)
|
handleFailure(webView)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shares the (WKWebView, WKNavigation?, NSError) Kotlin signature with didFailNavigation above.
|
|
||||||
@ObjCSignatureOverride
|
@ObjCSignatureOverride
|
||||||
override fun webView(
|
override fun webView(
|
||||||
webView: WKWebView,
|
webView: WKWebView,
|
||||||
@@ -50,3 +50,6 @@ include(":data:preferences")
|
|||||||
include(":data:records")
|
include(":data:records")
|
||||||
include(":data:resources")
|
include(":data:resources")
|
||||||
include(":data:subtitle")
|
include(":data:subtitle")
|
||||||
|
|
||||||
|
// Feature modules
|
||||||
|
include(":feature:webview")
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ kotlin {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
androidMain.dependencies {
|
androidMain.dependencies {
|
||||||
implementation(libs.compose.uiToolingPreview)
|
implementation(libs.compose.uiToolingPreview)
|
||||||
implementation(libs.androidx.webkit)
|
|
||||||
}
|
}
|
||||||
commonMain.dependencies {
|
commonMain.dependencies {
|
||||||
// Core modules
|
// Core modules
|
||||||
@@ -46,6 +45,9 @@ kotlin {
|
|||||||
implementation(projects.data.resources)
|
implementation(projects.data.resources)
|
||||||
implementation(projects.data.subtitle)
|
implementation(projects.data.subtitle)
|
||||||
|
|
||||||
|
// Feature modules
|
||||||
|
implementation(projects.feature.webview)
|
||||||
|
|
||||||
implementation(libs.compose.runtime)
|
implementation(libs.compose.runtime)
|
||||||
implementation(libs.compose.foundation)
|
implementation(libs.compose.foundation)
|
||||||
implementation(libs.compose.material3)
|
implementation(libs.compose.material3)
|
||||||
|
|||||||
@@ -16,12 +16,13 @@ import bzh.ajaury.chombev.di.sharedModule
|
|||||||
import bzh.ajaury.chombev.navigation.MainBottomBar
|
import bzh.ajaury.chombev.navigation.MainBottomBar
|
||||||
import bzh.ajaury.chombev.navigation.MainTopBar
|
import bzh.ajaury.chombev.navigation.MainTopBar
|
||||||
import bzh.ajaury.chombev.navigation.Route
|
import bzh.ajaury.chombev.navigation.Route
|
||||||
import bzh.ajaury.chombev.navigation.WebPage
|
import bzh.ajaury.chombev.navigation.WebPages
|
||||||
import bzh.ajaury.chombev.player.di.audioPlayerModule
|
import bzh.ajaury.chombev.player.di.audioPlayerModule
|
||||||
import bzh.ajaury.chombev.player.ui.PlayerScreen
|
import bzh.ajaury.chombev.player.ui.PlayerScreen
|
||||||
import bzh.ajaury.chombev.records.ui.RecordsScreen
|
import bzh.ajaury.chombev.records.ui.RecordsScreen
|
||||||
import bzh.ajaury.chombev.subtitle.di.subtitleModule
|
import bzh.ajaury.chombev.subtitle.di.subtitleModule
|
||||||
import bzh.ajaury.chombev.theme.ChomBevTheme
|
import bzh.ajaury.chombev.theme.ChomBevTheme
|
||||||
|
import bzh.ajaury.chombev.web.model.WebPage
|
||||||
import bzh.ajaury.chombev.web.ui.WebViewScreen
|
import bzh.ajaury.chombev.web.ui.WebViewScreen
|
||||||
import org.koin.compose.KoinApplication
|
import org.koin.compose.KoinApplication
|
||||||
import org.koin.dsl.koinConfiguration
|
import org.koin.dsl.koinConfiguration
|
||||||
@@ -81,8 +82,8 @@ fun App() {
|
|||||||
MainBottomBar(
|
MainBottomBar(
|
||||||
selectedPage = selectedPage,
|
selectedPage = selectedPage,
|
||||||
onRecordsClick = { selectMainTab(null) },
|
onRecordsClick = { selectMainTab(null) },
|
||||||
onDeepenClick = { selectMainTab(WebPage.DEEPEN) },
|
onDeepenClick = { selectMainTab(WebPages.DEEPEN) },
|
||||||
onAboutClick = { selectMainTab(WebPage.ABOUT) },
|
onAboutClick = { selectMainTab(WebPages.ABOUT) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import bzh.ajaury.chombev.preferences.di.preferencesModule
|
|||||||
import bzh.ajaury.chombev.records.di.recordsModule
|
import bzh.ajaury.chombev.records.di.recordsModule
|
||||||
import bzh.ajaury.chombev.records.ui.viewmodel.RecordsViewModel
|
import bzh.ajaury.chombev.records.ui.viewmodel.RecordsViewModel
|
||||||
import bzh.ajaury.chombev.resources.di.resourcesModule
|
import bzh.ajaury.chombev.resources.di.resourcesModule
|
||||||
import bzh.ajaury.chombev.web.ui.viewmodel.WebViewModel
|
import bzh.ajaury.chombev.web.di.webviewModule
|
||||||
import org.koin.core.module.dsl.viewModelOf
|
import org.koin.core.module.dsl.viewModelOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ val sharedModule = module {
|
|||||||
includes(preferencesModule)
|
includes(preferencesModule)
|
||||||
includes(recordsModule)
|
includes(recordsModule)
|
||||||
includes(resourcesModule)
|
includes(resourcesModule)
|
||||||
|
includes(webviewModule)
|
||||||
viewModelOf(::RecordsViewModel)
|
viewModelOf(::RecordsViewModel)
|
||||||
viewModelOf(::PlayerViewModel)
|
viewModelOf(::PlayerViewModel)
|
||||||
viewModelOf(::WebViewModel)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import bzh.ajaury.chombev.resources.generated.resources.nav_about
|
|||||||
import bzh.ajaury.chombev.resources.generated.resources.nav_deepen
|
import bzh.ajaury.chombev.resources.generated.resources.nav_deepen
|
||||||
import bzh.ajaury.chombev.resources.generated.resources.nav_records
|
import bzh.ajaury.chombev.resources.generated.resources.nav_records
|
||||||
import bzh.ajaury.chombev.theme.ChomBevTheme
|
import bzh.ajaury.chombev.theme.ChomBevTheme
|
||||||
|
import bzh.ajaury.chombev.web.model.WebPage
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,13 +41,13 @@ fun MainBottomBar(
|
|||||||
label = { Text(text = stringResource(Res.string.nav_records)) },
|
label = { Text(text = stringResource(Res.string.nav_records)) },
|
||||||
)
|
)
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
selected = selectedPage == WebPage.DEEPEN,
|
selected = selectedPage == WebPages.DEEPEN,
|
||||||
onClick = onDeepenClick,
|
onClick = onDeepenClick,
|
||||||
icon = { Icon(imageVector = Icons.Filled.School, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.Filled.School, contentDescription = null) },
|
||||||
label = { Text(text = stringResource(Res.string.nav_deepen)) },
|
label = { Text(text = stringResource(Res.string.nav_deepen)) },
|
||||||
)
|
)
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
selected = selectedPage == WebPage.ABOUT,
|
selected = selectedPage == WebPages.ABOUT,
|
||||||
onClick = onAboutClick,
|
onClick = onAboutClick,
|
||||||
icon = { Icon(imageVector = Icons.Outlined.Info, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.Outlined.Info, contentDescription = null) },
|
||||||
label = { Text(text = stringResource(Res.string.nav_about)) },
|
label = { Text(text = stringResource(Res.string.nav_about)) },
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package bzh.ajaury.chombev.navigation
|
package bzh.ajaury.chombev.navigation
|
||||||
|
|
||||||
import androidx.navigation3.runtime.NavKey
|
import androidx.navigation3.runtime.NavKey
|
||||||
|
import bzh.ajaury.chombev.web.model.WebPage
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|||||||
+6
-13
@@ -1,24 +1,17 @@
|
|||||||
package bzh.ajaury.chombev.navigation
|
package bzh.ajaury.chombev.navigation
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import bzh.ajaury.chombev.web.model.WebPage
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Web destinations reachable from the main bottom navigation bar and shown inside the app.
|
* Web destinations reachable from the main bottom navigation bar and shown inside the app.
|
||||||
*
|
|
||||||
* [offlineAsset] is the resource path of a bundled HTML snapshot shown when the online [url] cannot
|
|
||||||
* be reached, so the page still has content offline.
|
|
||||||
*/
|
*/
|
||||||
@Serializable
|
object WebPages {
|
||||||
enum class WebPage(
|
val DEEPEN = WebPage(
|
||||||
val url: String,
|
|
||||||
val offlineAsset: String,
|
|
||||||
) {
|
|
||||||
DEEPEN(
|
|
||||||
url = "https://ksk.bzh/fr/chom-bev-approfondir/",
|
url = "https://ksk.bzh/fr/chom-bev-approfondir/",
|
||||||
offlineAsset = "files/offline_deepen.html",
|
offlineAsset = "files/offline_deepen.html",
|
||||||
),
|
)
|
||||||
ABOUT(
|
val ABOUT = WebPage(
|
||||||
url = "https://ksk.bzh/fr/chom-bev-a-propos/",
|
url = "https://ksk.bzh/fr/chom-bev-a-propos/",
|
||||||
offlineAsset = "files/offline_about.html",
|
offlineAsset = "files/offline_about.html",
|
||||||
),
|
)
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package bzh.ajaury.chombev.di
|
|||||||
|
|
||||||
import bzh.ajaury.chombev.appModule
|
import bzh.ajaury.chombev.appModule
|
||||||
import bzh.ajaury.chombev.player.di.audioPlayerModule
|
import bzh.ajaury.chombev.player.di.audioPlayerModule
|
||||||
|
import bzh.ajaury.chombev.web.model.WebPage
|
||||||
import org.koin.core.annotation.KoinExperimentalAPI
|
import org.koin.core.annotation.KoinExperimentalAPI
|
||||||
import org.koin.test.verify.verify
|
import org.koin.test.verify.verify
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
@@ -18,6 +19,10 @@ class KoinModulesTest {
|
|||||||
@OptIn(KoinExperimentalAPI::class)
|
@OptIn(KoinExperimentalAPI::class)
|
||||||
@Test
|
@Test
|
||||||
fun koinConfigurationIsValid() {
|
fun koinConfigurationIsValid() {
|
||||||
appModule.verify()
|
// WebPage is supplied at call time via parametersOf (WebViewModel), so declare it as a known
|
||||||
|
// extra type rather than a resolvable definition.
|
||||||
|
appModule.verify(
|
||||||
|
extraTypes = listOf(WebPage::class),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user