Migrate bookmarks to multiplatform module

pull/2064/head
lihenggui 2 years ago
parent 00b2710d27
commit 6d05ba133e

@ -39,36 +39,36 @@ As Firebase Analytics does not yet support Kotlin Multiplatform, the implementat
## Status for modules
| Module | Progress | Desktop supported | Android supported | iOS supported | Web supported |
|---------------------------|------------------|-------------------|-------------------|---------------|---------------|
| app | Not started | ❌ | ❌ | ❌ | ❌ |
| app-nia-catalog | Done | ✅ | ✅ | ❔ | ✅ |
| :core:analytics | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:common | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:data | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:data-test | Not started | ❌ | ❌ | ❌ | ❌ |
| :core:database | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:datastore | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:datastore-proto | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:datastore-test | Removed | ❌ | ❌ | ❌ | ❌ |
| :core:designsystem | Done | ✅ | ✅ | ❔ | ✅ |
| :core:domain | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:model | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:network | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:notification | Done | No implmentaion | ✔️ |No implmentaion| ❌ |
| :core:screenshot-testing | Not started | ❌ | ❌ | ❌ | ❌ |
| :core:testing | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:ui | In progress | ✔️ | ✔️ | ✔️ | ❌ |
| :feature:bookmarks | Not started | ❌ | ❌ | ❌ | ❌ |
| :feature:foryou | Not started | ❌ | ❌ | ❌ | ❌ |
| :feature:interests | Not started | ❌ | ❌ | ❌ | ❌ |
| :feature:search | Not started | ❌ | ❌ | ❌ | ❌ |
| :feature:settings | Not started | ❌ | ❌ | ❌ | ❌ |
| :feature:topic | Not started | ❌ | ❌ | ❌ | ❌ |
| lint | Not started | ❌ | ❌ | ❌ | ❌ |
| :sync:sync-test | Not started | ❌ | ❌ | ❌ | ❌ |
| :sync:work | Not started | ❌ | ❌ | ❌ | ❌ |
| ui-test-manifest | Not started | ❌ | ❌ | ❌ | ❌ |
| Module | Progress | Desktop supported | Android supported | iOS supported | Web supported |
|---------------------------|-------------|-------------------|-------------------|---------------|---------------|
| app | Not started | ❌ | ❌ | ❌ | ❌ |
| app-nia-catalog | Done | ✅ | ✅ | ❔ | ✅ |
| :core:analytics | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:common | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:data | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:data-test | Not started | ❌ | ❌ | ❌ | ❌ |
| :core:database | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:datastore | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:datastore-proto | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:datastore-test | Removed | ❌ | ❌ | ❌ | ❌ |
| :core:designsystem | Done | ✅ | ✅ | ❔ | ✅ |
| :core:domain | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:model | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:network | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:notification | Done | No implmentaion | ✔️ |No implmentaion| ❌ |
| :core:screenshot-testing | Not started | ❌ | ❌ | ❌ | ❌ |
| :core:testing | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :core:ui | Done | ✔️ | ✔️ | ✔️ | ❌ |
| :feature:bookmarks | In progress | ❌ | ❌ | ❌ | ❌ |
| :feature:foryou | Not started | ❌ | ❌ | ❌ | ❌ |
| :feature:interests | Not started | ❌ | ❌ | ❌ | ❌ |
| :feature:search | Not started | ❌ | ❌ | ❌ | ❌ |
| :feature:settings | Not started | ❌ | ❌ | ❌ | ❌ |
| :feature:topic | Not started | ❌ | ❌ | ❌ | ❌ |
| lint | Not started | ❌ | ❌ | ❌ | ❌ |
| :sync:sync-test | Not started | ❌ | ❌ | ❌ | ❌ |
| :sync:work | Not started | ❌ | ❌ | ❌ | ❌ |
| ui-test-manifest | Not started | ❌ | ❌ | ❌ | ❌ |

@ -125,5 +125,9 @@ gradlePlugin {
id = "nowinandroid.sqldelight"
implementationClass = "SqlDelightConventionPlugin"
}
register("cmpFeature") {
id = "nowinandroid.cmp.feature"
implementationClass = "CmpFeatureConventionPlugin"
}
}
}

@ -0,0 +1,57 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.android.build.gradle.LibraryExtension
import com.google.samples.apps.nowinandroid.configureGradleManagedDevices
import com.google.samples.apps.nowinandroid.libs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
// Convention plugin for the Compose Multiplatform feature module
class CmpFeatureConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply {
apply("nowinandroid.kmp.library")
apply("nowinandroid.kmp.inject")
}
extensions.configure<LibraryExtension> {
defaultConfig {
testInstrumentationRunner =
"com.google.samples.apps.nowinandroid.core.testing.NiaTestRunner"
}
testOptions.animationsDisabled = true
configureGradleManagedDevices(this)
}
dependencies {
add("commonMainImplementation", project(":core:ui"))
add("commonMainImplementation", project(":core:designsystem"))
add("commonMainImplementation", libs.findLibrary("jetbrains.compose.viewmodel").get())
add("commonMainImplementation", libs.findLibrary("jetbrains.compose.navigation").get())
add("androidMainImplementation", libs.findLibrary("androidx.hilt.navigation.compose").get())
add("androidMainImplementation", libs.findLibrary("androidx.lifecycle.runtimeCompose").get())
add("androidMainImplementation", libs.findLibrary("androidx.lifecycle.viewModelCompose").get())
add("androidMainImplementation", libs.findLibrary("androidx.tracing.ktx").get())
add("androidInstrumentedTestImplementation", libs.findLibrary("androidx.lifecycle.runtimeTesting").get())
}
}
}
}

@ -17,11 +17,13 @@
package com.google.samples.apps.nowinandroid.core.analytics
import co.touchlab.kermit.Logger
import me.tatarka.inject.annotations.Inject
/**
* An implementation of AnalyticsHelper just writes the events to logcat. Used in builds where no
* analytics events should be sent to a backend.
*/
@Inject
internal class StubAnalyticsHelper : AnalyticsHelper {
override fun logEvent(event: AnalyticsEvent) {
Logger.d { "Received analytics event: $event" }

@ -33,27 +33,27 @@ import me.tatarka.inject.annotations.Provides
abstract class DataModule {
@Provides
internal fun bindsTopicRepository(
fun bindsTopicRepository(
topicsRepository: OfflineFirstTopicsRepository,
): TopicsRepository = topicsRepository
@Provides
internal fun bindsNewsResourceRepository(
fun bindsNewsResourceRepository(
newsRepository: OfflineFirstNewsRepository,
): NewsRepository = newsRepository
@Provides
internal fun bindsUserDataRepository(
fun userDataRepository(
userDataRepository: OfflineFirstUserDataRepository,
): UserDataRepository = userDataRepository
@Provides
internal fun bindsRecentSearchRepository(
fun bindsRecentSearchRepository(
recentSearchRepository: DefaultRecentSearchRepository,
): RecentSearchRepository = recentSearchRepository
@Provides
internal fun bindsSearchContentsRepository(
fun bindsSearchContentsRepository(
searchContentsRepository: DefaultSearchContentsRepository,
): SearchContentsRepository = searchContentsRepository
}

@ -26,7 +26,7 @@ import kotlinx.datetime.Clock
import me.tatarka.inject.annotations.Inject
@Inject
internal class DefaultRecentSearchRepository(
class DefaultRecentSearchRepository(
private val recentSearchQueryDao: RecentSearchQueryDao,
) : RecentSearchRepository {
override suspend fun insertOrReplaceRecentSearch(searchQuery: String) {

@ -37,7 +37,7 @@ import me.tatarka.inject.annotations.Inject
@OptIn(ExperimentalCoroutinesApi::class)
@Inject
internal class DefaultSearchContentsRepository(
class DefaultSearchContentsRepository(
private val newsResourceDao: NewsResourceDao,
private val newsResourceFtsDao: NewsResourceFtsDao,
private val topicDao: TopicDao,

@ -46,7 +46,7 @@ private const val SYNC_BATCH_SIZE = 40
* Reads are exclusively from local storage to support offline access.
*/
@Inject
internal class OfflineFirstNewsRepository(
class OfflineFirstNewsRepository(
private val niaPreferencesDataSource: NiaPreferencesDataSource,
private val newsResourceDao: NewsResourceDaoInterface,
private val topicDao: TopicDaoInterface,

@ -35,7 +35,7 @@ import me.tatarka.inject.annotations.Inject
* Reads are exclusively from local storage to support offline access.
*/
@Inject
internal class OfflineFirstTopicsRepository(
class OfflineFirstTopicsRepository(
private val topicDao: TopicDaoInterface,
private val network: NiaNetworkDataSource,
) : TopicsRepository {

@ -25,7 +25,7 @@ import kotlinx.coroutines.flow.Flow
import me.tatarka.inject.annotations.Inject
@Inject
internal class OfflineFirstUserDataRepository(
class OfflineFirstUserDataRepository(
private val niaPreferencesDataSource: NiaPreferencesDataSource,
private val analyticsHelper: AnalyticsHelper,
) : UserDataRepository {

@ -26,10 +26,12 @@ import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.datetime.Instant
import me.tatarka.inject.annotations.Inject
/**
* DAO for [NewsResource] and [NewsResourceEntity] access
*/
@Inject
class NewsResourceDao(db: NiaDatabase, private val dispatcher: CoroutineDispatcher) : NewsResourceDaoInterface {
private val query = db.newsResourceQueries

@ -24,10 +24,12 @@ import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceFtsE
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import me.tatarka.inject.annotations.Inject
/**
* DAO for [NewsResourceFtsEntity] access.
*/
@Inject
class NewsResourceFtsDao(db: NiaDatabase, private val dispatcher: CoroutineDispatcher) : NewsResourceFtsDaoInterface {
private val dbQuery = db.newsResourceFtsQueries
override suspend fun insertAll(newsResources: List<NewsResourceFtsEntity>) {

@ -24,10 +24,12 @@ import com.google.samples.apps.nowinandroid.core.database.model.RecentSearchQuer
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.datetime.Instant
import me.tatarka.inject.annotations.Inject
/**
* DAO for [RecentSearchQueryEntity] access
*/
@Inject
class RecentSearchQueryDao(db: NiaDatabase, private val dispatcher: CoroutineDispatcher) : RecentSearchQueryDaoInterface {
private val query = db.recentSearchQueryQueries

@ -24,11 +24,12 @@ import com.google.samples.apps.nowinandroid.core.database.model.TopicEntity
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import me.tatarka.inject.annotations.Inject
/**
* DAO for [TopicEntity] access
*/
@Inject
class TopicDao(db: NiaDatabase, private val dispatcher: CoroutineDispatcher) : TopicDaoInterface {
private val query = db.topicsQueries

@ -24,10 +24,12 @@ import com.google.samples.apps.nowinandroid.core.database.model.TopicFtsEntity
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import me.tatarka.inject.annotations.Inject
/**
* DAO for [TopicFtsEntity] access.
*/
@Inject
class TopicFtsDao(db: NiaDatabase, private val dispatcher: CoroutineDispatcher) : TopicFtsDaoInterface {
private val dbQuery = db.topicFtsQueries

@ -30,10 +30,12 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import me.tatarka.inject.annotations.Inject
private const val USER_DATA_KEY = "userData"
@OptIn(ExperimentalSerializationApi::class, ExperimentalSettingsApi::class)
@Inject
class NiaPreferencesDataSource(
private val settings: Settings,
private val dispatcher: IODispatcher,

@ -0,0 +1,27 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import kotlinx.coroutines.flow.StateFlow
// collectAsStateWithLifecycle is an Android-only API
// Replaced with collectAsState in shared code
@Composable
fun <T> StateFlow<T>.collectAsStateWithLifecycle(): State<T> = collectAsState()

@ -15,8 +15,9 @@
*/
plugins {
alias(libs.plugins.nowinandroid.android.feature)
alias(libs.plugins.nowinandroid.android.library.compose)
alias(libs.plugins.nowinandroid.cmp.feature)
alias(libs.plugins.jetbrains.compose)
alias(libs.plugins.compose)
alias(libs.plugins.nowinandroid.android.library.jacoco)
}
@ -24,10 +25,22 @@ android {
namespace = "com.google.samples.apps.nowinandroid.feature.bookmarks"
}
dependencies {
implementation(projects.core.data)
kotlin {
sourceSets {
commonMain.dependencies {
implementation(projects.core.data)
implementation(compose.material3)
implementation(compose.foundation)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
testImplementation(projects.core.testing)
androidTestImplementation(projects.core.testing)
}
commonTest.dependencies {
implementation(projects.core.testing)
}
androidInstrumentedTest.dependencies {
implementation(projects.core.testing)
}
}
}

@ -49,17 +49,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaLoadingWheel
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.DraggableScrollbar
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.rememberDraggableScroller
@ -71,16 +64,29 @@ import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState.Loading
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState.Success
import com.google.samples.apps.nowinandroid.core.ui.TrackScreenViewEvent
import com.google.samples.apps.nowinandroid.core.ui.TrackScrollJank
import com.google.samples.apps.nowinandroid.core.ui.UserNewsResourcePreviewParameterProvider
import com.google.samples.apps.nowinandroid.core.ui.collectAsStateWithLifecycle
import com.google.samples.apps.nowinandroid.core.ui.newsFeed
import me.tatarka.inject.annotations.Inject
import nowinandroid.feature.bookmarks.generated.resources.Res
import nowinandroid.feature.bookmarks.generated.resources.feature_bookmarks_empty_description
import nowinandroid.feature.bookmarks.generated.resources.feature_bookmarks_empty_error
import nowinandroid.feature.bookmarks.generated.resources.feature_bookmarks_img_empty_bookmarks
import nowinandroid.feature.bookmarks.generated.resources.feature_bookmarks_loading
import nowinandroid.feature.bookmarks.generated.resources.feature_bookmarks_removed
import nowinandroid.feature.bookmarks.generated.resources.feature_bookmarks_undo
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.ui.tooling.preview.Preview
import org.jetbrains.compose.ui.tooling.preview.PreviewParameter
@Composable
@Inject
internal fun BookmarksRoute(
onTopicClick: (String) -> Unit,
onShowSnackbar: suspend (String, String?) -> Boolean,
modifier: Modifier = Modifier,
viewModel: BookmarksViewModel = hiltViewModel(),
viewModel: BookmarksViewModel,
) {
val feedState by viewModel.feedUiState.collectAsStateWithLifecycle()
BookmarksScreen(
@ -112,8 +118,8 @@ internal fun BookmarksScreen(
undoBookmarkRemoval: () -> Unit = {},
clearUndoState: () -> Unit = {},
) {
val bookmarkRemovedMessage = stringResource(id = R.string.feature_bookmarks_removed)
val undoText = stringResource(id = R.string.feature_bookmarks_undo)
val bookmarkRemovedMessage = stringResource(Res.string.feature_bookmarks_removed)
val undoText = stringResource(Res.string.feature_bookmarks_undo)
LaunchedEffect(shouldDisplayUndoBookmark) {
if (shouldDisplayUndoBookmark) {
@ -125,10 +131,10 @@ internal fun BookmarksScreen(
}
}
}
LifecycleEventEffect(Lifecycle.Event.ON_STOP) {
clearUndoState()
}
// Could not import LifecycleEventEffect
// LifecycleEventEffect(Lifecycle.Event.ON_STOP) {
// clearUndoState()
// }
when (feedState) {
Loading -> LoadingState(modifier)
@ -155,7 +161,7 @@ private fun LoadingState(modifier: Modifier = Modifier) {
.fillMaxWidth()
.wrapContentSize()
.testTag("forYou:loading"),
contentDesc = stringResource(id = R.string.feature_bookmarks_loading),
contentDesc = stringResource(Res.string.feature_bookmarks_loading),
)
}
@ -168,7 +174,6 @@ private fun BookmarksGrid(
modifier: Modifier = Modifier,
) {
val scrollableState = rememberLazyStaggeredGridState()
TrackScrollJank(scrollableState = scrollableState, stateName = "bookmarks:grid")
Box(
modifier = modifier
.fillMaxSize(),
@ -228,7 +233,7 @@ private fun EmptyState(modifier: Modifier = Modifier) {
val iconTint = LocalTintTheme.current.iconTint
Image(
modifier = Modifier.fillMaxWidth(),
painter = painterResource(id = R.drawable.feature_bookmarks_img_empty_bookmarks),
painter = painterResource(Res.drawable.feature_bookmarks_img_empty_bookmarks),
colorFilter = if (iconTint != Color.Unspecified) ColorFilter.tint(iconTint) else null,
contentDescription = null,
)
@ -236,7 +241,7 @@ private fun EmptyState(modifier: Modifier = Modifier) {
Spacer(modifier = Modifier.height(48.dp))
Text(
text = stringResource(id = R.string.feature_bookmarks_empty_error),
text = stringResource(Res.string.feature_bookmarks_empty_error),
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleMedium,
@ -246,7 +251,7 @@ private fun EmptyState(modifier: Modifier = Modifier) {
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(id = R.string.feature_bookmarks_empty_description),
text = stringResource(Res.string.feature_bookmarks_empty_description),
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium,

@ -26,17 +26,16 @@ import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourc
import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState.Loading
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject
import me.tatarka.inject.annotations.Inject
@HiltViewModel
class BookmarksViewModel @Inject constructor(
@Inject
class BookmarksViewModel(
private val userDataRepository: UserDataRepository,
userNewsResourceRepository: UserNewsResourceRepository,
) : ViewModel() {

@ -0,0 +1,25 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.feature.bookmarks.di
import com.google.samples.apps.nowinandroid.feature.bookmarks.BookmarksViewModel
import me.tatarka.inject.annotations.Component
@Component
abstract class BookmarkComponent {
abstract val viewModel: BookmarksViewModel
}

@ -21,6 +21,7 @@ import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
import com.google.samples.apps.nowinandroid.feature.bookmarks.BookmarksRoute
import com.google.samples.apps.nowinandroid.feature.bookmarks.di.BookmarkComponent
const val BOOKMARKS_ROUTE = "bookmarks_route"
@ -30,7 +31,8 @@ fun NavGraphBuilder.bookmarksScreen(
onTopicClick: (String) -> Unit,
onShowSnackbar: suspend (String, String?) -> Boolean,
) {
val viewModel = BookmarkComponent::class.create().viewModel
composable(route = BOOKMARKS_ROUTE) {
BookmarksRoute(onTopicClick, onShowSnackbar)
BookmarksRoute(onTopicClick, onShowSnackbar, viewModel)
}
}

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest />

@ -17,7 +17,7 @@ androidxCoreSplashscreen = "1.0.1"
androidxDataStore = "1.1.1"
androidxEspresso = "3.5.1"
androidxHiltNavigationCompose = "1.2.0"
androidxLifecycle = "2.7.0"
androidxLifecycle = "2.8.2"
androidxMacroBenchmark = "1.2.4"
androidxMetrics = "1.0.0-beta01"
androidxNavigation = "2.8.0-beta03"
@ -72,6 +72,8 @@ kermit = "2.0.4"
ktor = "2.3.11"
ktrofit = "1.14.0"
buildKonfig = "0.15.1"
lifecycle-viewmodel-compose = "2.8.0"
navigation-compose = "2.7.0-alpha07"
[libraries]
accompanist-permissions = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "accompanist" }
@ -194,6 +196,8 @@ ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serializatio
ktorfit-ksp = { group = "de.jensklingenberg.ktorfit", name = "ktorfit-ksp", version.ref = "ktrofit" }
ktorfit-lib = { group = "de.jensklingenberg.ktorfit", name = "ktorfit-lib", version.ref = "ktrofit" }
buildkonfig-gradlePlugin = { group = "com.codingfeline.buildkonfig", name = "buildkonfig-gradle-plugin", version.ref = "buildKonfig" }
jetbrains-compose-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle-viewmodel-compose" }
jetbrains-compose-navigation = { group = "org.jetbrains.androidx.navigation", name = "navigation-compose", version.ref = "navigation-compose" }
# Dependencies of the included build-logic
android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
@ -249,3 +253,4 @@ nowinandroid-jvm-library = { id = "nowinandroid.jvm.library", version = "unspeci
nowinandroid-kmp-library = { id = "nowinandroid.kmp.library", version = "unspecified" }
nowinandroid-kotlin-inject = { id = "nowinandroid.kmp.inject", version = "unspecified" }
nowinandroid-sqldelight = { id = "nowinandroid.sqldelight", version = "unspecified" }
nowinandroid-cmp-feature = { id = "nowinandroid.cmp.feature", version = "unspecified" }

Loading…
Cancel
Save