pull/2043/merge
Simon Marquis 2 days ago committed by GitHub
commit 8bbb45bf92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -13,10 +13,6 @@ config:
graph TB
subgraph :feature
direction TB
subgraph :feature:settings
direction TB
:feature:settings:impl[impl]:::android-library
end
subgraph :feature:foryou
direction TB
:feature:foryou:api[api]:::android-library
@ -32,6 +28,11 @@ graph TB
:feature:search:api[api]:::android-library
:feature:search:impl[impl]:::android-library
end
subgraph :feature:settings
direction TB
:feature:settings:api[api]:::android-library
:feature:settings:impl[impl]:::android-library
end
subgraph :feature:interests
direction TB
:feature:interests:api[api]:::android-library
@ -81,6 +82,7 @@ graph TB
:app -.-> :feature:interests:impl
:app -.-> :feature:search:api
:app -.-> :feature:search:impl
:app -.-> :feature:settings:api
:app -.-> :feature:settings:impl
:app -.-> :feature:topic:api
:app -.-> :feature:topic:impl
@ -128,13 +130,16 @@ graph TB
:feature:search:api --> :core:navigation
:feature:search:impl -.-> :core:designsystem
:feature:search:impl -.-> :core:domain
:feature:search:impl -.-> :core:navigation
:feature:search:impl -.-> :core:ui
:feature:search:impl -.-> :feature:interests:api
:feature:search:impl -.-> :feature:search:api
:feature:search:impl -.-> :feature:topic:api
:feature:settings:api --> :core:navigation
:feature:settings:impl -.-> :core:data
:feature:settings:impl -.-> :core:designsystem
:feature:settings:impl -.-> :core:ui
:feature:settings:impl -.-> :feature:settings:api
:feature:topic:api -.-> :core:designsystem
:feature:topic:api --> :core:navigation
:feature:topic:api -.-> :core:ui

@ -78,6 +78,7 @@ dependencies {
implementation(projects.feature.topic.impl)
implementation(projects.feature.search.api)
implementation(projects.feature.search.impl)
implementation(projects.feature.settings.api)
implementation(projects.feature.settings.impl)
implementation(projects.core.common)

@ -45,10 +45,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
@ -63,6 +60,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.scene.DialogSceneStrategy
import androidx.navigation3.ui.NavDisplay
import com.google.samples.apps.nowinandroid.R
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackground
@ -81,7 +79,8 @@ import com.google.samples.apps.nowinandroid.feature.foryou.impl.navigation.forYo
import com.google.samples.apps.nowinandroid.feature.interests.impl.navigation.interestsEntry
import com.google.samples.apps.nowinandroid.feature.search.api.navigation.SearchNavKey
import com.google.samples.apps.nowinandroid.feature.search.impl.navigation.searchEntry
import com.google.samples.apps.nowinandroid.feature.settings.impl.SettingsDialog
import com.google.samples.apps.nowinandroid.feature.settings.api.navigation.SettingsNavKey
import com.google.samples.apps.nowinandroid.feature.settings.impl.navigation.settingsEntry
import com.google.samples.apps.nowinandroid.feature.topic.impl.navigation.topicEntry
import com.google.samples.apps.nowinandroid.navigation.TOP_LEVEL_NAV_ITEMS
import com.google.samples.apps.nowinandroid.feature.settings.impl.R as settingsR
@ -93,7 +92,6 @@ fun NiaApp(
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo(),
) {
val shouldShowGradientBackground = appState.navigationState.currentTopLevelKey == ForYouNavKey
var showSettingsDialog by rememberSaveable { mutableStateOf(false) }
NiaBackground(modifier = modifier) {
NiaGradientBackground(
@ -118,13 +116,8 @@ fun NiaApp(
}
}
CompositionLocalProvider(LocalSnackbarHostState provides snackbarHostState) {
NiaApp(
NiaAppContent(
appState = appState,
// TODO: Settings should be a dialog screen
showSettingsDialog = showSettingsDialog,
onSettingsDismissed = { showSettingsDialog = false },
onTopAppBarActionClick = { showSettingsDialog = true },
windowAdaptiveInfo = windowAdaptiveInfo,
)
}
@ -138,23 +131,14 @@ fun NiaApp(
ExperimentalComposeUiApi::class,
ExperimentalMaterial3AdaptiveApi::class,
)
internal fun NiaApp(
internal fun NiaAppContent(
appState: NiaAppState,
showSettingsDialog: Boolean,
onSettingsDismissed: () -> Unit,
onTopAppBarActionClick: () -> Unit,
modifier: Modifier = Modifier,
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo(),
) {
val unreadNavKeys by appState.topLevelNavKeysWithUnreadResources
.collectAsStateWithLifecycle()
if (showSettingsDialog) {
SettingsDialog(
onDismiss = { onSettingsDismissed() },
)
}
val snackbarHostState = LocalSnackbarHostState.current
val navigator = remember { Navigator(appState.navigationState) }
@ -220,7 +204,7 @@ internal fun NiaApp(
// Only show the top app bar on top level destinations.
var shouldShowTopAppBar = false
if (appState.navigationState.currentKey in appState.navigationState.topLevelKeys) {
if (appState.navigationState.currentKeyIgnoringDialogs in appState.navigationState.topLevelKeys) {
shouldShowTopAppBar = true
val destination = TOP_LEVEL_NAV_ITEMS[appState.navigationState.currentTopLevelKey]
@ -239,7 +223,7 @@ internal fun NiaApp(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent,
),
onActionClick = { onTopAppBarActionClick() },
onActionClick = { navigator.navigate(SettingsNavKey) },
onNavigationClick = { navigator.navigate(SearchNavKey) },
)
}
@ -255,6 +239,7 @@ internal fun NiaApp(
),
) {
val listDetailStrategy = rememberListDetailSceneStrategy<NavKey>()
val dialogStrategy = remember { DialogSceneStrategy<NavKey>() }
val entryProvider = entryProvider {
forYouEntry(navigator)
@ -262,11 +247,12 @@ internal fun NiaApp(
interestsEntry(navigator)
topicEntry(navigator)
searchEntry(navigator)
settingsEntry(navigator)
}
NavDisplay(
entries = appState.navigationState.toEntries(entryProvider),
sceneStrategy = listDetailStrategy,
sceneStrategy = dialogStrategy then listDetailStrategy,
onBack = { navigator.goBack() },
)
}

@ -251,11 +251,8 @@ class SnackbarInsetsScreenshotTests {
userNewsResourceRepository = userNewsResourceRepository,
timeZoneMonitor = timeZoneMonitor,
)
NiaApp(
NiaAppContent(
appState = appState,
showSettingsDialog = false,
onSettingsDismissed = {},
onTopAppBarActionClick = {},
windowAdaptiveInfo = WindowAdaptiveInfo(
windowSizeClass = WindowSizeClass.compute(
maxWidth.value,

@ -201,11 +201,8 @@ class SnackbarScreenshotTests {
userNewsResourceRepository = userNewsResourceRepository,
timeZoneMonitor = timeZoneMonitor,
)
NiaApp(
NiaAppContent(
appState = appState,
showSettingsDialog = false,
onSettingsDismissed = {},
onTopAppBarActionClick = {},
windowAdaptiveInfo = WindowAdaptiveInfo(
windowSizeClass = WindowSizeClass.compute(
maxWidth.value,

@ -13,10 +13,6 @@ config:
graph TB
subgraph :feature
direction TB
subgraph :feature:settings
direction TB
:feature:settings:impl[impl]:::android-library
end
subgraph :feature:foryou
direction TB
:feature:foryou:api[api]:::android-library
@ -32,6 +28,11 @@ graph TB
:feature:search:api[api]:::android-library
:feature:search:impl[impl]:::android-library
end
subgraph :feature:settings
direction TB
:feature:settings:api[api]:::android-library
:feature:settings:impl[impl]:::android-library
end
subgraph :feature:interests
direction TB
:feature:interests:api[api]:::android-library
@ -81,6 +82,7 @@ graph TB
:app -.-> :feature:interests:impl
:app -.-> :feature:search:api
:app -.-> :feature:search:impl
:app -.-> :feature:settings:api
:app -.-> :feature:settings:impl
:app -.-> :feature:topic:api
:app -.-> :feature:topic:impl
@ -128,13 +130,16 @@ graph TB
:feature:search:api --> :core:navigation
:feature:search:impl -.-> :core:designsystem
:feature:search:impl -.-> :core:domain
:feature:search:impl -.-> :core:navigation
:feature:search:impl -.-> :core:ui
:feature:search:impl -.-> :feature:interests:api
:feature:search:impl -.-> :feature:search:api
:feature:search:impl -.-> :feature:topic:api
:feature:settings:api --> :core:navigation
:feature:settings:impl -.-> :core:data
:feature:settings:impl -.-> :core:designsystem
:feature:settings:impl -.-> :core:ui
:feature:settings:impl -.-> :feature:settings:api
:feature:topic:api -.-> :core:designsystem
:feature:topic:api --> :core:navigation
:feature:topic:api -.-> :core:ui

@ -0,0 +1,24 @@
/*
* Copyright 2026 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.navigation
import androidx.navigation3.runtime.NavKey
/**
* [NavKey] that must behave as a dialog when handled by a [DialogSceneStrategy].
*/
interface DialogNavKey : NavKey

@ -75,6 +75,11 @@ class NavigationState(
@get:VisibleForTesting
val currentKey: NavKey by derivedStateOf { currentSubStack.last() }
/**
* Current [NavKey] but ignores [DialogNavKey]s that could be added on top.
*/
val currentKeyIgnoringDialogs: NavKey by derivedStateOf { currentSubStack.last { it !is DialogNavKey } }
}
/**

@ -68,6 +68,7 @@ graph TB
:feature:search:api --> :core:navigation
:feature:search:impl -.-> :core:designsystem
:feature:search:impl -.-> :core:domain
:feature:search:impl -.-> :core:navigation
:feature:search:impl -.-> :core:ui
:feature:search:impl -.-> :feature:interests:api
:feature:search:impl -.-> :feature:search:api

@ -26,6 +26,7 @@ android {
dependencies {
implementation(projects.core.domain)
implementation(projects.core.navigation)
implementation(projects.feature.interests.api)
implementation(projects.feature.search.api)
implementation(projects.feature.topic.api)

@ -0,0 +1,57 @@
# `:feature:settings:api`
## Module dependency graph
<!--region graph-->
```mermaid
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
graph TB
subgraph :feature
direction TB
subgraph :feature:settings
direction TB
:feature:settings:api[api]:::android-library
end
end
subgraph :core
direction TB
:core:navigation[navigation]:::android-library
end
:feature:settings:api --> :core:navigation
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
<details><summary>📋 Graph legend</summary>
```mermaid
graph TB
application[application]:::android-application
feature[feature]:::android-feature
library[library]:::android-library
jvm[jvm]:::jvm-library
application -.-> feature
library --> jvm
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
</details>
<!--endregion-->

@ -0,0 +1,23 @@
/*
* Copyright 2026 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.
*/
plugins {
alias(libs.plugins.nowinandroid.android.feature.api)
}
android {
namespace = "com.google.samples.apps.nowinandroid.feature.settings.api"
}

@ -0,0 +1,23 @@
/*
* Copyright 2026 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.settings.api.navigation
import com.google.samples.apps.nowinandroid.core.navigation.DialogNavKey
import kotlinx.serialization.Serializable
@Serializable
object SettingsNavKey : DialogNavKey

@ -15,6 +15,7 @@ graph TB
direction TB
subgraph :feature:settings
direction TB
:feature:settings:api[api]:::android-library
:feature:settings:impl[impl]:::android-library
end
end
@ -28,6 +29,7 @@ graph TB
:core:datastore-proto[datastore-proto]:::jvm-library
:core:designsystem[designsystem]:::android-library
:core:model[model]:::jvm-library
:core:navigation[navigation]:::android-library
:core:network[network]:::android-library
:core:notifications[notifications]:::android-library
:core:ui[ui]:::android-library
@ -50,9 +52,11 @@ graph TB
:core:ui --> :core:analytics
:core:ui --> :core:designsystem
:core:ui --> :core:model
:feature:settings:api --> :core:navigation
:feature:settings:impl -.-> :core:data
:feature:settings:impl -.-> :core:designsystem
:feature:settings:impl -.-> :core:ui
:feature:settings:impl -.-> :feature:settings:api
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;

@ -26,8 +26,10 @@ android {
dependencies {
implementation(libs.androidx.appcompat)
implementation(libs.androidx.navigation3.ui)
implementation(libs.google.oss.licenses)
implementation(projects.core.data)
implementation(projects.feature.settings.api)
testImplementation(projects.core.testing)

@ -71,7 +71,7 @@ import com.google.samples.apps.nowinandroid.feature.settings.impl.SettingsUiStat
import com.google.samples.apps.nowinandroid.feature.settings.impl.SettingsUiState.Success
@Composable
fun SettingsDialog(
internal fun SettingsDialog(
onDismiss: () -> Unit,
viewModel: SettingsViewModel = hiltViewModel(),
) {
@ -86,7 +86,7 @@ fun SettingsDialog(
}
@Composable
fun SettingsDialog(
internal fun SettingsDialog(
settingsUiState: SettingsUiState,
supportDynamicColor: Boolean = supportsDynamicTheming(),
onDismiss: () -> Unit,

@ -33,7 +33,7 @@ import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@HiltViewModel
class SettingsViewModel @Inject constructor(
internal class SettingsViewModel @Inject constructor(
private val userDataRepository: UserDataRepository,
) : ViewModel() {
val settingsUiState: StateFlow<SettingsUiState> =

@ -0,0 +1,34 @@
/*
* Copyright 2025 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.settings.impl.navigation
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.scene.DialogSceneStrategy
import com.google.samples.apps.nowinandroid.core.navigation.Navigator
import com.google.samples.apps.nowinandroid.feature.settings.api.navigation.SettingsNavKey
import com.google.samples.apps.nowinandroid.feature.settings.impl.SettingsDialog
fun EntryProviderScope<NavKey>.settingsEntry(navigator: Navigator) {
entry<SettingsNavKey>(
metadata = DialogSceneStrategy.dialog(),
) {
SettingsDialog(
onDismiss = navigator::goBack,
)
}
}

@ -76,6 +76,7 @@ include(":feature:topic:api")
include(":feature:topic:impl")
include(":feature:search:api")
include(":feature:search:impl")
include(":feature:settings:api")
include(":feature:settings:impl")
include(":lint")
include(":sync:work")

Loading…
Cancel
Save