@ -1,58 +0,0 @@
|
|||||||
# Now in Android Project
|
|
||||||
|
|
||||||
Now in Android is a native Android mobile application written in Kotlin. It provides regular news
|
|
||||||
about Android development. Users can choose to follow topics, be notified when new content is
|
|
||||||
available, and bookmark items.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
This project is a modern Android application that follows the official architecture guidance from Google. It is a reactive, single-activity app that uses the following:
|
|
||||||
|
|
||||||
- **UI:** Built entirely with Jetpack Compose, including Material 3 components and adaptive layouts for different screen sizes.
|
|
||||||
- **State Management:** Unidirectional Data Flow (UDF) is implemented using Kotlin Coroutines and `Flow`s. `ViewModel`s act as state holders, exposing UI state as streams of data.
|
|
||||||
- **Dependency Injection:** Hilt is used for dependency injection throughout the app, simplifying the management of dependencies and improving testability.
|
|
||||||
- **Navigation:** Navigation is handled by Jetpack Navigation 2 for Compose, allowing for a declarative and type-safe way to navigate between screens.
|
|
||||||
- **Data:** The data layer is implemented using the repository pattern.
|
|
||||||
- **Local Data:** Room and DataStore are used for local data persistence.
|
|
||||||
- **Remote Data:** Retrofit and OkHttp are used for fetching data from the network.
|
|
||||||
- **Background Processing:** WorkManager is used for deferrable background tasks.
|
|
||||||
|
|
||||||
## Modules
|
|
||||||
|
|
||||||
The main Android app lives in the `app/` folder. Feature modules live in `feature/` and core and shared modules in `core/`.
|
|
||||||
|
|
||||||
## Commands to Build & Test
|
|
||||||
|
|
||||||
The app and Android libraries have two product flavors: `demo` and `prod`, and two build types: `debug` and `release`.
|
|
||||||
|
|
||||||
- Build: `./gradlew assemble{Variant}`. Typically `assembleDemoDebug`.
|
|
||||||
- Fix linting/formatting: `./gradlew --init-script gradle/init.gradle.kts spotlessApply`
|
|
||||||
- Run local tests: `./gradlew {variant}Test`
|
|
||||||
- Run single test: `./gradlew {variant}Test --tests "com.example.myapp.MyTestClass"`
|
|
||||||
- Run local screenshot tests: `./gradlew verifyRoborazziDemoDebug`
|
|
||||||
|
|
||||||
### Instrumented tests
|
|
||||||
|
|
||||||
- Gradle-managed devices to run on device tests: `./gradlew pixel6api31aospDebugAndroidTest`. Also `pixel4api30aospatdDebugAndroidTest` and `pixelcapi30aospatdDebugAndroidTest`.
|
|
||||||
|
|
||||||
### Creating tests
|
|
||||||
|
|
||||||
#### Instrumented tests
|
|
||||||
|
|
||||||
- Tests for UI features should only use `ComposeTestRule` with a `ComponentActivity`.
|
|
||||||
- Bigger tests live in the `:app` module and they can start activities like `MainActivity`.
|
|
||||||
|
|
||||||
#### Local tests
|
|
||||||
|
|
||||||
- [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) for most assertions
|
|
||||||
- [cashapp/turbine](https://github.com/cashapp/turbine) for complex coroutine tests
|
|
||||||
- [google/truth](https://github.com/google/truth) for assertions
|
|
||||||
|
|
||||||
## Continuous integration
|
|
||||||
|
|
||||||
- The workflows are defined in `.github/workflows/*.yaml` and they contain various checks.
|
|
||||||
- Screenshot tests are generated by CI, so they shouldn't be checked into the repo from a workstation.
|
|
||||||
|
|
||||||
## Version control and code location
|
|
||||||
|
|
||||||
- The project uses git and is hosted in https://github.com/android/nowinandroid.
|
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
# Now in Android Project
|
||||||
|
|
||||||
|
Now in Android is a native Android mobile application written in Kotlin. It provides regular news
|
||||||
|
about Android development. Users can choose to follow topics, be notified when new content is
|
||||||
|
available, and bookmark items.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
This project is a modern Android application that follows the official architecture guidance from Google. It is a reactive, single-activity app that uses the following:
|
||||||
|
|
||||||
|
- **UI:** Built entirely with Jetpack Compose, including Material 3 components and adaptive layouts for different screen sizes.
|
||||||
|
- **State Management:** Unidirectional Data Flow (UDF) is implemented using Kotlin Coroutines and `Flow`s. `ViewModel`s act as state holders, exposing UI state as streams of data.
|
||||||
|
- **Dependency Injection:** Hilt is used for dependency injection throughout the app, simplifying the management of dependencies and improving testability.
|
||||||
|
- **Navigation:** Navigation is handled by Jetpack Navigation 2 for Compose, allowing for a declarative and type-safe way to navigate between screens.
|
||||||
|
- **Data:** The data layer is implemented using the repository pattern.
|
||||||
|
- **Local Data:** Room and DataStore are used for local data persistence.
|
||||||
|
- **Remote Data:** Retrofit and OkHttp are used for fetching data from the network.
|
||||||
|
- **Background Processing:** WorkManager is used for deferrable background tasks.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
The main Android app lives in the `app/` folder. Feature modules live in `feature/` and core and shared modules in `core/`.
|
||||||
|
|
||||||
|
## Commands to Build & Test
|
||||||
|
|
||||||
|
The app and Android libraries have two product flavors: `demo` and `prod`, and two build types: `debug` and `release`.
|
||||||
|
|
||||||
|
- Build: `./gradlew assemble{Variant}`. Typically `assembleDemoDebug`.
|
||||||
|
- Fix linting/formatting: `./gradlew --init-script gradle/init.gradle.kts spotlessApply`
|
||||||
|
- Run local tests: `./gradlew {variant}Test`
|
||||||
|
- Run single test: `./gradlew {variant}Test --tests "com.example.myapp.MyTestClass"`
|
||||||
|
- Run local screenshot tests: `./gradlew verifyRoborazziDemoDebug`
|
||||||
|
|
||||||
|
### Instrumented tests
|
||||||
|
|
||||||
|
- Gradle-managed devices to run on device tests: `./gradlew pixel6api31aospDebugAndroidTest`. Also `pixel4api30aospatdDebugAndroidTest` and `pixelcapi30aospatdDebugAndroidTest`.
|
||||||
|
|
||||||
|
### Creating tests
|
||||||
|
|
||||||
|
#### Instrumented tests
|
||||||
|
|
||||||
|
- Tests for UI features should only use `ComposeTestRule` with a `ComponentActivity`.
|
||||||
|
- Bigger tests live in the `:app` module and they can start activities like `MainActivity`.
|
||||||
|
|
||||||
|
#### Local tests
|
||||||
|
|
||||||
|
- [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) for most assertions
|
||||||
|
- [cashapp/turbine](https://github.com/cashapp/turbine) for complex coroutine tests
|
||||||
|
- [google/truth](https://github.com/google/truth) for assertions
|
||||||
|
|
||||||
|
## Continuous integration
|
||||||
|
|
||||||
|
- The workflows are defined in `.github/workflows/*.yaml` and they contain various checks.
|
||||||
|
- Screenshot tests are generated by CI, so they shouldn't be checked into the repo from a workstation.
|
||||||
|
|
||||||
|
## Version control and code location
|
||||||
|
|
||||||
|
- The project uses git and is hosted in https://github.com/android/nowinandroid.
|
||||||
@ -1,3 +1,58 @@
|
|||||||
# :app-nia-catalog module
|
# `:app-nia-catalog`
|
||||||
## Dependency graph
|
|
||||||

|
## Module dependency graph
|
||||||
|
|
||||||
|
<!--region graph-->
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: SIMPLE
|
||||||
|
---
|
||||||
|
graph TB
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:analytics[analytics]:::android-library
|
||||||
|
:core:designsystem[designsystem]:::android-library
|
||||||
|
:core:model[model]:::jvm-library
|
||||||
|
:core:ui[ui]:::android-library
|
||||||
|
end
|
||||||
|
:app-nia-catalog[app-nia-catalog]:::android-application
|
||||||
|
|
||||||
|
:app-nia-catalog -.-> :core:designsystem
|
||||||
|
:app-nia-catalog -.-> :core:ui
|
||||||
|
:core:ui --> :core:analytics
|
||||||
|
:core:ui --> :core:designsystem
|
||||||
|
:core:ui --> :core:model
|
||||||
|
|
||||||
|
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-->
|
||||||
|
|||||||
@ -1,3 +1,178 @@
|
|||||||
# :app module
|
# `:app`
|
||||||
## Dependency graph
|
|
||||||

|
## 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:impl[impl]:::android-library
|
||||||
|
end
|
||||||
|
subgraph :feature:foryou
|
||||||
|
direction TB
|
||||||
|
:feature:foryou:api[api]:::android-library
|
||||||
|
:feature:foryou:impl[impl]:::android-library
|
||||||
|
end
|
||||||
|
subgraph :feature:bookmarks
|
||||||
|
direction TB
|
||||||
|
:feature:bookmarks:api[api]:::android-library
|
||||||
|
:feature:bookmarks:impl[impl]:::android-library
|
||||||
|
end
|
||||||
|
subgraph :feature:search
|
||||||
|
direction TB
|
||||||
|
:feature:search:api[api]:::android-library
|
||||||
|
:feature:search:impl[impl]:::android-library
|
||||||
|
end
|
||||||
|
subgraph :feature:interests
|
||||||
|
direction TB
|
||||||
|
:feature:interests:api[api]:::android-library
|
||||||
|
:feature:interests:impl[impl]:::android-library
|
||||||
|
end
|
||||||
|
subgraph :feature:topic
|
||||||
|
direction TB
|
||||||
|
:feature:topic:api[api]:::android-library
|
||||||
|
:feature:topic:impl[impl]:::android-library
|
||||||
|
end
|
||||||
|
end
|
||||||
|
subgraph :sync
|
||||||
|
direction TB
|
||||||
|
:sync:work[work]:::android-library
|
||||||
|
end
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:analytics[analytics]:::android-library
|
||||||
|
:core:common[common]:::jvm-library
|
||||||
|
:core:data[data]:::android-library
|
||||||
|
:core:database[database]:::android-library
|
||||||
|
:core:datastore[datastore]:::android-library
|
||||||
|
:core:datastore-proto[datastore-proto]:::android-library
|
||||||
|
:core:designsystem[designsystem]:::android-library
|
||||||
|
:core:domain[domain]:::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
|
||||||
|
end
|
||||||
|
:benchmarks[benchmarks]:::android-test
|
||||||
|
:app[app]:::android-application
|
||||||
|
|
||||||
|
:app -.->|baselineProfile| :benchmarks
|
||||||
|
:app -.-> :core:analytics
|
||||||
|
:app -.-> :core:common
|
||||||
|
:app -.-> :core:data
|
||||||
|
:app -.-> :core:designsystem
|
||||||
|
:app -.-> :core:model
|
||||||
|
:app -.-> :core:ui
|
||||||
|
:app -.-> :feature:bookmarks:api
|
||||||
|
:app -.-> :feature:bookmarks:impl
|
||||||
|
:app -.-> :feature:foryou:api
|
||||||
|
:app -.-> :feature:foryou:impl
|
||||||
|
:app -.-> :feature:interests:api
|
||||||
|
:app -.-> :feature:interests:impl
|
||||||
|
:app -.-> :feature:search:api
|
||||||
|
:app -.-> :feature:search:impl
|
||||||
|
:app -.-> :feature:settings:impl
|
||||||
|
:app -.-> :feature:topic:api
|
||||||
|
:app -.-> :feature:topic:impl
|
||||||
|
:app -.-> :sync:work
|
||||||
|
:benchmarks -.->|testedApks| :app
|
||||||
|
:core:data -.-> :core:analytics
|
||||||
|
:core:data --> :core:common
|
||||||
|
:core:data --> :core:database
|
||||||
|
:core:data --> :core:datastore
|
||||||
|
:core:data --> :core:network
|
||||||
|
:core:data -.-> :core:notifications
|
||||||
|
:core:database --> :core:model
|
||||||
|
:core:datastore -.-> :core:common
|
||||||
|
:core:datastore --> :core:datastore-proto
|
||||||
|
:core:datastore --> :core:model
|
||||||
|
:core:domain --> :core:data
|
||||||
|
:core:domain --> :core:model
|
||||||
|
:core:network --> :core:common
|
||||||
|
:core:network --> :core:model
|
||||||
|
:core:notifications -.-> :core:common
|
||||||
|
:core:notifications --> :core:model
|
||||||
|
:core:ui --> :core:analytics
|
||||||
|
:core:ui --> :core:designsystem
|
||||||
|
:core:ui --> :core:model
|
||||||
|
:feature:bookmarks:api --> :core:navigation
|
||||||
|
:feature:bookmarks:impl -.-> :core:data
|
||||||
|
:feature:bookmarks:impl -.-> :core:designsystem
|
||||||
|
:feature:bookmarks:impl -.-> :core:ui
|
||||||
|
:feature:bookmarks:impl -.-> :feature:bookmarks:api
|
||||||
|
:feature:bookmarks:impl -.-> :feature:topic:api
|
||||||
|
:feature:foryou:api --> :core:navigation
|
||||||
|
:feature:foryou:impl -.-> :core:designsystem
|
||||||
|
:feature:foryou:impl -.-> :core:domain
|
||||||
|
:feature:foryou:impl -.-> :core:notifications
|
||||||
|
:feature:foryou:impl -.-> :core:ui
|
||||||
|
:feature:foryou:impl -.-> :feature:foryou:api
|
||||||
|
:feature:foryou:impl -.-> :feature:topic:api
|
||||||
|
:feature:interests:api --> :core:navigation
|
||||||
|
:feature:interests:impl -.-> :core:designsystem
|
||||||
|
:feature:interests:impl -.-> :core:domain
|
||||||
|
:feature:interests:impl -.-> :core:ui
|
||||||
|
:feature:interests:impl -.-> :feature:interests:api
|
||||||
|
:feature:interests:impl -.-> :feature:topic:api
|
||||||
|
:feature:search:api -.-> :core:domain
|
||||||
|
:feature:search:api --> :core:navigation
|
||||||
|
:feature:search:impl -.-> :core:designsystem
|
||||||
|
:feature:search:impl -.-> :core:domain
|
||||||
|
: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:impl -.-> :core:data
|
||||||
|
:feature:settings:impl -.-> :core:designsystem
|
||||||
|
:feature:settings:impl -.-> :core:ui
|
||||||
|
:feature:topic:api -.-> :core:designsystem
|
||||||
|
:feature:topic:api --> :core:navigation
|
||||||
|
:feature:topic:api -.-> :core:ui
|
||||||
|
:feature:topic:impl -.-> :core:data
|
||||||
|
:feature:topic:impl -.-> :core:designsystem
|
||||||
|
:feature:topic:impl -.-> :core:ui
|
||||||
|
:feature:topic:impl -.-> :feature:topic:api
|
||||||
|
:sync:work -.-> :core:analytics
|
||||||
|
:sync:work -.-> :core:data
|
||||||
|
:sync:work -.-> :core:notifications
|
||||||
|
|
||||||
|
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-->
|
||||||
|
|||||||
@ -1,72 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
*
|
|
||||||
* 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.navigation
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.navigation.compose.NavHost
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.bookmarksScreen
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.ForYouBaseRoute
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.forYouSection
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.interests.navigation.navigateToInterests
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.search.navigation.searchScreen
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.topic.navigation.navigateToTopic
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicScreen
|
|
||||||
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination.INTERESTS
|
|
||||||
import com.google.samples.apps.nowinandroid.ui.NiaAppState
|
|
||||||
import com.google.samples.apps.nowinandroid.ui.interests2pane.interestsListDetailScreen
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Top-level navigation graph. Navigation is organized as explained at
|
|
||||||
* https://d.android.com/jetpack/compose/nav-adaptive
|
|
||||||
*
|
|
||||||
* The navigation graph defined in this file defines the different top level routes. Navigation
|
|
||||||
* within each route is handled using state and Back Handlers.
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun NiaNavHost(
|
|
||||||
appState: NiaAppState,
|
|
||||||
onShowSnackbar: suspend (String, String?) -> Boolean,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
val navController = appState.navController
|
|
||||||
NavHost(
|
|
||||||
navController = navController,
|
|
||||||
startDestination = ForYouBaseRoute,
|
|
||||||
modifier = modifier,
|
|
||||||
) {
|
|
||||||
forYouSection(
|
|
||||||
onTopicClick = navController::navigateToTopic,
|
|
||||||
) {
|
|
||||||
topicScreen(
|
|
||||||
showBackButton = true,
|
|
||||||
onBackClick = navController::popBackStack,
|
|
||||||
onTopicClick = navController::navigateToTopic,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
bookmarksScreen(
|
|
||||||
onTopicClick = navController::navigateToInterests,
|
|
||||||
onShowSnackbar = onShowSnackbar,
|
|
||||||
)
|
|
||||||
searchScreen(
|
|
||||||
onBackClick = navController::popBackStack,
|
|
||||||
onInterestsClick = { appState.navigateToTopLevelDestination(INTERESTS) },
|
|
||||||
onTopicClick = navController::navigateToInterests,
|
|
||||||
)
|
|
||||||
interestsListDetailScreen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
*
|
|
||||||
* 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.navigation
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import com.google.samples.apps.nowinandroid.R
|
|
||||||
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.BookmarksRoute
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.ForYouBaseRoute
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.ForYouRoute
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsRoute
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.bookmarks.R as bookmarksR
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.foryou.R as forYouR
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.search.R as searchR
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type for the top level destinations in the application. Contains metadata about the destination
|
|
||||||
* that is used in the top app bar and common navigation UI.
|
|
||||||
*
|
|
||||||
* @param selectedIcon The icon to be displayed in the navigation UI when this destination is
|
|
||||||
* selected.
|
|
||||||
* @param unselectedIcon The icon to be displayed in the navigation UI when this destination is
|
|
||||||
* not selected.
|
|
||||||
* @param iconTextId Text that to be displayed in the navigation UI.
|
|
||||||
* @param titleTextId Text that is displayed on the top app bar.
|
|
||||||
* @param route The route to use when navigating to this destination.
|
|
||||||
* @param baseRoute The highest ancestor of this destination. Defaults to [route], meaning that
|
|
||||||
* there is a single destination in that section of the app (no nested destinations).
|
|
||||||
*/
|
|
||||||
enum class TopLevelDestination(
|
|
||||||
val selectedIcon: ImageVector,
|
|
||||||
val unselectedIcon: ImageVector,
|
|
||||||
@StringRes val iconTextId: Int,
|
|
||||||
@StringRes val titleTextId: Int,
|
|
||||||
val route: KClass<*>,
|
|
||||||
val baseRoute: KClass<*> = route,
|
|
||||||
) {
|
|
||||||
FOR_YOU(
|
|
||||||
selectedIcon = NiaIcons.Upcoming,
|
|
||||||
unselectedIcon = NiaIcons.UpcomingBorder,
|
|
||||||
iconTextId = forYouR.string.feature_foryou_title,
|
|
||||||
titleTextId = R.string.app_name,
|
|
||||||
route = ForYouRoute::class,
|
|
||||||
baseRoute = ForYouBaseRoute::class,
|
|
||||||
),
|
|
||||||
BOOKMARKS(
|
|
||||||
selectedIcon = NiaIcons.Bookmarks,
|
|
||||||
unselectedIcon = NiaIcons.BookmarksBorder,
|
|
||||||
iconTextId = bookmarksR.string.feature_bookmarks_title,
|
|
||||||
titleTextId = bookmarksR.string.feature_bookmarks_title,
|
|
||||||
route = BookmarksRoute::class,
|
|
||||||
),
|
|
||||||
INTERESTS(
|
|
||||||
selectedIcon = NiaIcons.Grid3x3,
|
|
||||||
unselectedIcon = NiaIcons.Grid3x3,
|
|
||||||
iconTextId = searchR.string.feature_search_interests,
|
|
||||||
titleTextId = searchR.string.feature_search_interests,
|
|
||||||
route = InterestsRoute::class,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* 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.navigation
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import com.google.samples.apps.nowinandroid.R
|
||||||
|
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
|
||||||
|
import com.google.samples.apps.nowinandroid.feature.bookmarks.api.navigation.BookmarksNavKey
|
||||||
|
import com.google.samples.apps.nowinandroid.feature.foryou.api.navigation.ForYouNavKey
|
||||||
|
import com.google.samples.apps.nowinandroid.feature.interests.api.navigation.InterestsNavKey
|
||||||
|
import com.google.samples.apps.nowinandroid.feature.bookmarks.api.R as bookmarksR
|
||||||
|
import com.google.samples.apps.nowinandroid.feature.foryou.api.R as forYouR
|
||||||
|
import com.google.samples.apps.nowinandroid.feature.search.api.R as searchR
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type for the top level navigation items in the application. Contains UI information about the
|
||||||
|
* current route that is used in the top app bar and common navigation UI.
|
||||||
|
*
|
||||||
|
* @param selectedIcon The icon to be displayed in the navigation UI when this destination is
|
||||||
|
* selected.
|
||||||
|
* @param unselectedIcon The icon to be displayed in the navigation UI when this destination is
|
||||||
|
* not selected.
|
||||||
|
* @param iconTextId Text that to be displayed in the navigation UI.
|
||||||
|
* @param titleTextId Text that is displayed on the top app bar.
|
||||||
|
*/
|
||||||
|
data class TopLevelNavItem(
|
||||||
|
val selectedIcon: ImageVector,
|
||||||
|
val unselectedIcon: ImageVector,
|
||||||
|
@StringRes val iconTextId: Int,
|
||||||
|
@StringRes val titleTextId: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
val FOR_YOU = TopLevelNavItem(
|
||||||
|
selectedIcon = NiaIcons.Upcoming,
|
||||||
|
unselectedIcon = NiaIcons.UpcomingBorder,
|
||||||
|
iconTextId = forYouR.string.feature_foryou_api_title,
|
||||||
|
titleTextId = R.string.app_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
val BOOKMARKS = TopLevelNavItem(
|
||||||
|
selectedIcon = NiaIcons.Bookmarks,
|
||||||
|
unselectedIcon = NiaIcons.BookmarksBorder,
|
||||||
|
iconTextId = bookmarksR.string.feature_bookmarks_api_title,
|
||||||
|
titleTextId = bookmarksR.string.feature_bookmarks_api_title,
|
||||||
|
)
|
||||||
|
|
||||||
|
val INTERESTS = TopLevelNavItem(
|
||||||
|
selectedIcon = NiaIcons.Grid3x3,
|
||||||
|
unselectedIcon = NiaIcons.Grid3x3,
|
||||||
|
iconTextId = searchR.string.feature_search_api_interests,
|
||||||
|
titleTextId = searchR.string.feature_search_api_interests,
|
||||||
|
)
|
||||||
|
|
||||||
|
val TOP_LEVEL_NAV_ITEMS = mapOf(
|
||||||
|
ForYouNavKey to FOR_YOU,
|
||||||
|
BookmarksNavKey to BOOKMARKS,
|
||||||
|
InterestsNavKey(null) to INTERESTS,
|
||||||
|
)
|
||||||
@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.ui.interests2pane
|
|
||||||
|
|
||||||
import androidx.lifecycle.SavedStateHandle
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.navigation.toRoute
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsRoute
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
const val TOPIC_ID_KEY = "selectedTopicId"
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
class Interests2PaneViewModel @Inject constructor(
|
|
||||||
private val savedStateHandle: SavedStateHandle,
|
|
||||||
) : ViewModel() {
|
|
||||||
|
|
||||||
val route = savedStateHandle.toRoute<InterestsRoute>()
|
|
||||||
val selectedTopicId: StateFlow<String?> = savedStateHandle.getStateFlow(
|
|
||||||
key = TOPIC_ID_KEY,
|
|
||||||
initialValue = route.initialTopicId,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun onTopicClick(topicId: String?) {
|
|
||||||
savedStateHandle[TOPIC_ID_KEY] = topicId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,244 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.ui.interests2pane
|
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
|
||||||
import androidx.compose.animation.AnimatedContent
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
|
|
||||||
import androidx.compose.material3.VerticalDragHandle
|
|
||||||
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
|
||||||
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
|
|
||||||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
|
||||||
import androidx.compose.material3.adaptive.layout.AnimatedPane
|
|
||||||
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
|
|
||||||
import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
|
|
||||||
import androidx.compose.material3.adaptive.layout.PaneExpansionAnchor
|
|
||||||
import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem
|
|
||||||
import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirective
|
|
||||||
import androidx.compose.material3.adaptive.layout.defaultDragHandleSemantics
|
|
||||||
import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState
|
|
||||||
import androidx.compose.material3.adaptive.navigation.BackNavigationBehavior
|
|
||||||
import androidx.compose.material3.adaptive.navigation.NavigableListDetailPaneScaffold
|
|
||||||
import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator
|
|
||||||
import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldPredictiveBackHandler
|
|
||||||
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clipToBounds
|
|
||||||
import androidx.compose.ui.layout.layout
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|
||||||
import androidx.navigation.NavGraphBuilder
|
|
||||||
import androidx.navigation.compose.composable
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.interests.InterestsRoute
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsRoute
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.topic.TopicDetailPlaceholder
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.topic.TopicScreen
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.topic.TopicViewModel
|
|
||||||
import com.google.samples.apps.nowinandroid.feature.topic.navigation.TopicRoute
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
@Serializable internal object TopicPlaceholderRoute
|
|
||||||
|
|
||||||
fun NavGraphBuilder.interestsListDetailScreen() {
|
|
||||||
composable<InterestsRoute> {
|
|
||||||
InterestsListDetailScreen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
internal fun InterestsListDetailScreen(
|
|
||||||
viewModel: Interests2PaneViewModel = hiltViewModel(),
|
|
||||||
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo(),
|
|
||||||
) {
|
|
||||||
val selectedTopicId by viewModel.selectedTopicId.collectAsStateWithLifecycle()
|
|
||||||
InterestsListDetailScreen(
|
|
||||||
selectedTopicId = selectedTopicId,
|
|
||||||
onTopicClick = viewModel::onTopicClick,
|
|
||||||
windowAdaptiveInfo = windowAdaptiveInfo,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
|
||||||
@Composable
|
|
||||||
internal fun InterestsListDetailScreen(
|
|
||||||
selectedTopicId: String?,
|
|
||||||
onTopicClick: (String) -> Unit,
|
|
||||||
windowAdaptiveInfo: WindowAdaptiveInfo,
|
|
||||||
) {
|
|
||||||
val listDetailNavigator = rememberListDetailPaneScaffoldNavigator(
|
|
||||||
scaffoldDirective = calculatePaneScaffoldDirective(windowAdaptiveInfo),
|
|
||||||
initialDestinationHistory = listOfNotNull(
|
|
||||||
ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List),
|
|
||||||
ThreePaneScaffoldDestinationItem<Nothing>(ListDetailPaneScaffoldRole.Detail).takeIf {
|
|
||||||
selectedTopicId != null
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
val paneExpansionState = rememberPaneExpansionState(
|
|
||||||
anchors = listOf(
|
|
||||||
PaneExpansionAnchor.Proportion(0f),
|
|
||||||
PaneExpansionAnchor.Proportion(0.5f),
|
|
||||||
PaneExpansionAnchor.Proportion(1f),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
ThreePaneScaffoldPredictiveBackHandler(
|
|
||||||
listDetailNavigator,
|
|
||||||
BackNavigationBehavior.PopUntilScaffoldValueChange,
|
|
||||||
)
|
|
||||||
BackHandler(
|
|
||||||
paneExpansionState.currentAnchor == PaneExpansionAnchor.Proportion(0f) &&
|
|
||||||
listDetailNavigator.isListPaneVisible() &&
|
|
||||||
listDetailNavigator.isDetailPaneVisible(),
|
|
||||||
) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
paneExpansionState.animateTo(PaneExpansionAnchor.Proportion(1f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var topicRoute by remember {
|
|
||||||
val route = selectedTopicId?.let { TopicRoute(id = it) } ?: TopicPlaceholderRoute
|
|
||||||
mutableStateOf(route)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onTopicClickShowDetailPane(topicId: String) {
|
|
||||||
onTopicClick(topicId)
|
|
||||||
topicRoute = TopicRoute(id = topicId)
|
|
||||||
coroutineScope.launch {
|
|
||||||
listDetailNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
|
|
||||||
}
|
|
||||||
if (paneExpansionState.currentAnchor == PaneExpansionAnchor.Proportion(1f)) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
paneExpansionState.animateTo(PaneExpansionAnchor.Proportion(0f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val mutableInteractionSource = remember { MutableInteractionSource() }
|
|
||||||
val minPaneWidth = 300.dp
|
|
||||||
|
|
||||||
NavigableListDetailPaneScaffold(
|
|
||||||
navigator = listDetailNavigator,
|
|
||||||
listPane = {
|
|
||||||
AnimatedPane {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.clipToBounds()
|
|
||||||
.layout { measurable, constraints ->
|
|
||||||
val width = max(minPaneWidth.roundToPx(), constraints.maxWidth)
|
|
||||||
val placeable = measurable.measure(
|
|
||||||
constraints.copy(
|
|
||||||
minWidth = minPaneWidth.roundToPx(),
|
|
||||||
maxWidth = width,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
layout(constraints.maxWidth, placeable.height) {
|
|
||||||
placeable.placeRelative(
|
|
||||||
x = 0,
|
|
||||||
y = 0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
InterestsRoute(
|
|
||||||
onTopicClick = ::onTopicClickShowDetailPane,
|
|
||||||
shouldHighlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
detailPane = {
|
|
||||||
AnimatedPane {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.clipToBounds()
|
|
||||||
.layout { measurable, constraints ->
|
|
||||||
val width = max(minPaneWidth.roundToPx(), constraints.maxWidth)
|
|
||||||
val placeable = measurable.measure(
|
|
||||||
constraints.copy(
|
|
||||||
minWidth = minPaneWidth.roundToPx(),
|
|
||||||
maxWidth = width,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
layout(constraints.maxWidth, placeable.height) {
|
|
||||||
placeable.placeRelative(
|
|
||||||
x = constraints.maxWidth -
|
|
||||||
max(constraints.maxWidth, placeable.width),
|
|
||||||
y = 0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
AnimatedContent(topicRoute) { route ->
|
|
||||||
when (route) {
|
|
||||||
is TopicRoute -> {
|
|
||||||
TopicScreen(
|
|
||||||
showBackButton = !listDetailNavigator.isListPaneVisible(),
|
|
||||||
onBackClick = {
|
|
||||||
coroutineScope.launch {
|
|
||||||
listDetailNavigator.navigateBack()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onTopicClick = ::onTopicClickShowDetailPane,
|
|
||||||
viewModel = hiltViewModel<TopicViewModel, TopicViewModel.Factory>(
|
|
||||||
key = route.id,
|
|
||||||
) { factory ->
|
|
||||||
factory.create(route.id)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is TopicPlaceholderRoute -> {
|
|
||||||
TopicDetailPlaceholder()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
paneExpansionState = paneExpansionState,
|
|
||||||
paneExpansionDragHandle = {
|
|
||||||
VerticalDragHandle(
|
|
||||||
modifier = Modifier.paneExpansionDraggable(
|
|
||||||
state = paneExpansionState,
|
|
||||||
minTouchTargetSize = LocalMinimumInteractiveComponentSize.current,
|
|
||||||
interactionSource = mutableInteractionSource,
|
|
||||||
semanticsProperties = paneExpansionState.defaultDragHandleSemantics(),
|
|
||||||
),
|
|
||||||
interactionSource = mutableInteractionSource,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
|
||||||
private fun <T> ThreePaneScaffoldNavigator<T>.isListPaneVisible(): Boolean =
|
|
||||||
scaffoldValue[ListDetailPaneScaffoldRole.List] == PaneAdaptedValue.Expanded
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
|
||||||
private fun <T> ThreePaneScaffoldNavigator<T>.isDetailPaneVisible(): Boolean =
|
|
||||||
scaffoldValue[ListDetailPaneScaffoldRole.Detail] == PaneAdaptedValue.Expanded
|
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
sdk = 35
|
||||||
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 276 KiB After Width: | Height: | Size: 276 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 198 KiB After Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 185 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 216 KiB After Width: | Height: | Size: 216 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
@ -0,0 +1,178 @@
|
|||||||
|
# `:benchmarks`
|
||||||
|
|
||||||
|
## 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:impl[impl]:::android-library
|
||||||
|
end
|
||||||
|
subgraph :feature:foryou
|
||||||
|
direction TB
|
||||||
|
:feature:foryou:api[api]:::android-library
|
||||||
|
:feature:foryou:impl[impl]:::android-library
|
||||||
|
end
|
||||||
|
subgraph :feature:bookmarks
|
||||||
|
direction TB
|
||||||
|
:feature:bookmarks:api[api]:::android-library
|
||||||
|
:feature:bookmarks:impl[impl]:::android-library
|
||||||
|
end
|
||||||
|
subgraph :feature:search
|
||||||
|
direction TB
|
||||||
|
:feature:search:api[api]:::android-library
|
||||||
|
:feature:search:impl[impl]:::android-library
|
||||||
|
end
|
||||||
|
subgraph :feature:interests
|
||||||
|
direction TB
|
||||||
|
:feature:interests:api[api]:::android-library
|
||||||
|
:feature:interests:impl[impl]:::android-library
|
||||||
|
end
|
||||||
|
subgraph :feature:topic
|
||||||
|
direction TB
|
||||||
|
:feature:topic:api[api]:::android-library
|
||||||
|
:feature:topic:impl[impl]:::android-library
|
||||||
|
end
|
||||||
|
end
|
||||||
|
subgraph :sync
|
||||||
|
direction TB
|
||||||
|
:sync:work[work]:::android-library
|
||||||
|
end
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:analytics[analytics]:::android-library
|
||||||
|
:core:common[common]:::jvm-library
|
||||||
|
:core:data[data]:::android-library
|
||||||
|
:core:database[database]:::android-library
|
||||||
|
:core:datastore[datastore]:::android-library
|
||||||
|
:core:datastore-proto[datastore-proto]:::android-library
|
||||||
|
:core:designsystem[designsystem]:::android-library
|
||||||
|
:core:domain[domain]:::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
|
||||||
|
end
|
||||||
|
:benchmarks[benchmarks]:::android-test
|
||||||
|
:app[app]:::android-application
|
||||||
|
|
||||||
|
:app -.->|baselineProfile| :benchmarks
|
||||||
|
:app -.-> :core:analytics
|
||||||
|
:app -.-> :core:common
|
||||||
|
:app -.-> :core:data
|
||||||
|
:app -.-> :core:designsystem
|
||||||
|
:app -.-> :core:model
|
||||||
|
:app -.-> :core:ui
|
||||||
|
:app -.-> :feature:bookmarks:api
|
||||||
|
:app -.-> :feature:bookmarks:impl
|
||||||
|
:app -.-> :feature:foryou:api
|
||||||
|
:app -.-> :feature:foryou:impl
|
||||||
|
:app -.-> :feature:interests:api
|
||||||
|
:app -.-> :feature:interests:impl
|
||||||
|
:app -.-> :feature:search:api
|
||||||
|
:app -.-> :feature:search:impl
|
||||||
|
:app -.-> :feature:settings:impl
|
||||||
|
:app -.-> :feature:topic:api
|
||||||
|
:app -.-> :feature:topic:impl
|
||||||
|
:app -.-> :sync:work
|
||||||
|
:benchmarks -.->|testedApks| :app
|
||||||
|
:core:data -.-> :core:analytics
|
||||||
|
:core:data --> :core:common
|
||||||
|
:core:data --> :core:database
|
||||||
|
:core:data --> :core:datastore
|
||||||
|
:core:data --> :core:network
|
||||||
|
:core:data -.-> :core:notifications
|
||||||
|
:core:database --> :core:model
|
||||||
|
:core:datastore -.-> :core:common
|
||||||
|
:core:datastore --> :core:datastore-proto
|
||||||
|
:core:datastore --> :core:model
|
||||||
|
:core:domain --> :core:data
|
||||||
|
:core:domain --> :core:model
|
||||||
|
:core:network --> :core:common
|
||||||
|
:core:network --> :core:model
|
||||||
|
:core:notifications -.-> :core:common
|
||||||
|
:core:notifications --> :core:model
|
||||||
|
:core:ui --> :core:analytics
|
||||||
|
:core:ui --> :core:designsystem
|
||||||
|
:core:ui --> :core:model
|
||||||
|
:feature:bookmarks:api --> :core:navigation
|
||||||
|
:feature:bookmarks:impl -.-> :core:data
|
||||||
|
:feature:bookmarks:impl -.-> :core:designsystem
|
||||||
|
:feature:bookmarks:impl -.-> :core:ui
|
||||||
|
:feature:bookmarks:impl -.-> :feature:bookmarks:api
|
||||||
|
:feature:bookmarks:impl -.-> :feature:topic:api
|
||||||
|
:feature:foryou:api --> :core:navigation
|
||||||
|
:feature:foryou:impl -.-> :core:designsystem
|
||||||
|
:feature:foryou:impl -.-> :core:domain
|
||||||
|
:feature:foryou:impl -.-> :core:notifications
|
||||||
|
:feature:foryou:impl -.-> :core:ui
|
||||||
|
:feature:foryou:impl -.-> :feature:foryou:api
|
||||||
|
:feature:foryou:impl -.-> :feature:topic:api
|
||||||
|
:feature:interests:api --> :core:navigation
|
||||||
|
:feature:interests:impl -.-> :core:designsystem
|
||||||
|
:feature:interests:impl -.-> :core:domain
|
||||||
|
:feature:interests:impl -.-> :core:ui
|
||||||
|
:feature:interests:impl -.-> :feature:interests:api
|
||||||
|
:feature:interests:impl -.-> :feature:topic:api
|
||||||
|
:feature:search:api -.-> :core:domain
|
||||||
|
:feature:search:api --> :core:navigation
|
||||||
|
:feature:search:impl -.-> :core:designsystem
|
||||||
|
:feature:search:impl -.-> :core:domain
|
||||||
|
: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:impl -.-> :core:data
|
||||||
|
:feature:settings:impl -.-> :core:designsystem
|
||||||
|
:feature:settings:impl -.-> :core:ui
|
||||||
|
:feature:topic:api -.-> :core:designsystem
|
||||||
|
:feature:topic:api --> :core:navigation
|
||||||
|
:feature:topic:api -.-> :core:ui
|
||||||
|
:feature:topic:impl -.-> :core:data
|
||||||
|
:feature:topic:impl -.-> :core:designsystem
|
||||||
|
:feature:topic:impl -.-> :core:ui
|
||||||
|
:feature:topic:impl -.-> :feature:topic:api
|
||||||
|
:sync:work -.-> :core:analytics
|
||||||
|
:sync:work -.-> :core:data
|
||||||
|
:sync:work -.-> :core:notifications
|
||||||
|
|
||||||
|
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,34 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* 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.google.samples.apps.nowinandroid.libs
|
||||||
|
import org.gradle.api.Plugin
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.kotlin.dsl.apply
|
||||||
|
import org.gradle.kotlin.dsl.dependencies
|
||||||
|
|
||||||
|
class AndroidFeatureApiConventionPlugin : Plugin<Project> {
|
||||||
|
override fun apply(target: Project) {
|
||||||
|
with(target) {
|
||||||
|
apply(plugin = "nowinandroid.android.library")
|
||||||
|
apply(plugin = "org.jetbrains.kotlin.plugin.serialization")
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
"api"(project(":core:navigation"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.google.samples.apps.nowinandroid.configureGraphTasks
|
||||||
|
import org.gradle.api.Plugin
|
||||||
|
import org.gradle.api.Project
|
||||||
|
|
||||||
|
class RootPlugin : Plugin<Project> {
|
||||||
|
override fun apply(target: Project) {
|
||||||
|
require(target.path == ":")
|
||||||
|
target.subprojects { configureGraphTasks() }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,353 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import com.android.utils.associateWithNotNull
|
||||||
|
import com.google.samples.apps.nowinandroid.PluginType.Unknown
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.artifacts.Configuration
|
||||||
|
import org.gradle.api.artifacts.ProjectDependency
|
||||||
|
import org.gradle.api.file.RegularFileProperty
|
||||||
|
import org.gradle.api.provider.MapProperty
|
||||||
|
import org.gradle.api.provider.Property
|
||||||
|
import org.gradle.api.tasks.CacheableTask
|
||||||
|
import org.gradle.api.tasks.Input
|
||||||
|
import org.gradle.api.tasks.InputFile
|
||||||
|
import org.gradle.api.tasks.OutputFile
|
||||||
|
import org.gradle.api.tasks.PathSensitive
|
||||||
|
import org.gradle.api.tasks.PathSensitivity.NONE
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
import org.gradle.kotlin.dsl.assign
|
||||||
|
import org.gradle.kotlin.dsl.register
|
||||||
|
import org.gradle.kotlin.dsl.withType
|
||||||
|
import kotlin.text.RegexOption.DOT_MATCHES_ALL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates module dependency graphs with `graphDump` task, and update the corresponding `README.md` file with `graphUpdate`.
|
||||||
|
*
|
||||||
|
* This is not an optimal implementation and could be improved if needed:
|
||||||
|
* - [Graph.invoke] is **recursively** searching through dependent projects (although in practice it will never reach a stack overflow).
|
||||||
|
* - [Graph.invoke] is entirely re-executed for all projects, without re-using intermediate values.
|
||||||
|
* - [Graph.invoke] is always executed during Gradle's Configuration phase (but takes in general less than 1 ms for a project).
|
||||||
|
*
|
||||||
|
* The resulting graphs can be configured with `graph.ignoredProjects` and `graph.supportedConfigurations` properties.
|
||||||
|
*/
|
||||||
|
private class Graph(
|
||||||
|
private val root: Project,
|
||||||
|
private val dependencies: MutableMap<Project, Set<Pair<Configuration, Project>>> = mutableMapOf(),
|
||||||
|
private val plugins: MutableMap<Project, PluginType> = mutableMapOf(),
|
||||||
|
private val seen: MutableSet<String> = mutableSetOf(),
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val ignoredProjects = root.providers.gradleProperty("graph.ignoredProjects")
|
||||||
|
.map { it.split(",").toSet() }
|
||||||
|
.orElse(emptySet())
|
||||||
|
private val supportedConfigurations =
|
||||||
|
root.providers.gradleProperty("graph.supportedConfigurations")
|
||||||
|
.map { it.split(",").toSet() }
|
||||||
|
.orElse(setOf("api", "implementation", "baselineProfile", "testedApks"))
|
||||||
|
|
||||||
|
operator fun invoke(project: Project = root): Graph {
|
||||||
|
if (project.path in seen) return this
|
||||||
|
seen += project.path
|
||||||
|
plugins.putIfAbsent(
|
||||||
|
project,
|
||||||
|
PluginType.entries.firstOrNull { project.pluginManager.hasPlugin(it.id) } ?: Unknown,
|
||||||
|
)
|
||||||
|
dependencies.compute(project) { _, u -> u.orEmpty() }
|
||||||
|
project.configurations
|
||||||
|
.matching { it.name in supportedConfigurations.get() }
|
||||||
|
.associateWithNotNull { it.dependencies.withType<ProjectDependency>().ifEmpty { null } }
|
||||||
|
.flatMap { (c, value) -> value.map { dep -> c to project.project(dep.path) } }
|
||||||
|
.filter { (_, p) -> p.path !in ignoredProjects.get() }
|
||||||
|
.forEach { (configuration: Configuration, projectDependency: Project) ->
|
||||||
|
dependencies.compute(project) { _, u -> u.orEmpty() + (configuration to projectDependency) }
|
||||||
|
invoke(projectDependency)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dependencies(): Map<String, Set<Pair<String, String>>> = dependencies
|
||||||
|
.mapKeys { it.key.path }
|
||||||
|
.mapValues { it.value.mapTo(mutableSetOf()) { (c, p) -> c.name to p.path } }
|
||||||
|
|
||||||
|
fun plugins() = plugins.mapKeys { it.key.path }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declaration order is important, as only the first match will be retained.
|
||||||
|
*/
|
||||||
|
internal enum class PluginType(val id: String, val ref: String, val style: String) {
|
||||||
|
AndroidApplication(
|
||||||
|
id = "nowinandroid.android.application",
|
||||||
|
ref = "android-application",
|
||||||
|
style = "fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000",
|
||||||
|
),
|
||||||
|
AndroidFeature(
|
||||||
|
id = "nowinandroid.android.feature",
|
||||||
|
ref = "android-feature",
|
||||||
|
style = "fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000",
|
||||||
|
),
|
||||||
|
AndroidLibrary(
|
||||||
|
id = "nowinandroid.android.library",
|
||||||
|
ref = "android-library",
|
||||||
|
style = "fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000",
|
||||||
|
),
|
||||||
|
AndroidTest(
|
||||||
|
id = "nowinandroid.android.test",
|
||||||
|
ref = "android-test",
|
||||||
|
style = "fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000",
|
||||||
|
),
|
||||||
|
Jvm(
|
||||||
|
id = "nowinandroid.jvm.library",
|
||||||
|
ref = "jvm-library",
|
||||||
|
style = "fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000",
|
||||||
|
),
|
||||||
|
Unknown(
|
||||||
|
id = "?",
|
||||||
|
ref = "unknown",
|
||||||
|
style = "fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Project.configureGraphTasks() {
|
||||||
|
if (!buildFile.exists()) return // Ignore root modules without build file
|
||||||
|
val dumpTask = tasks.register<GraphDumpTask>("graphDump") {
|
||||||
|
val graph = Graph(this@configureGraphTasks).invoke()
|
||||||
|
projectPath = this@configureGraphTasks.path
|
||||||
|
dependencies = graph.dependencies()
|
||||||
|
plugins = graph.plugins()
|
||||||
|
output = this@configureGraphTasks.layout.buildDirectory.file("mermaid/graph.txt")
|
||||||
|
legend = this@configureGraphTasks.layout.buildDirectory.file("mermaid/legend.txt")
|
||||||
|
}
|
||||||
|
tasks.register<GraphUpdateTask>("graphUpdate") {
|
||||||
|
projectPath = this@configureGraphTasks.path
|
||||||
|
input = dumpTask.flatMap { it.output }
|
||||||
|
legend = dumpTask.flatMap { it.legend }
|
||||||
|
output = this@configureGraphTasks.layout.projectDirectory.file("README.md")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CacheableTask
|
||||||
|
private abstract class GraphDumpTask : DefaultTask() {
|
||||||
|
|
||||||
|
@get:Input
|
||||||
|
abstract val projectPath: Property<String>
|
||||||
|
|
||||||
|
@get:Input
|
||||||
|
abstract val dependencies: MapProperty<String, Set<Pair<String, String>>>
|
||||||
|
|
||||||
|
@get:Input
|
||||||
|
abstract val plugins: MapProperty<String, PluginType>
|
||||||
|
|
||||||
|
@get:OutputFile
|
||||||
|
abstract val output: RegularFileProperty
|
||||||
|
|
||||||
|
@get:OutputFile
|
||||||
|
abstract val legend: RegularFileProperty
|
||||||
|
|
||||||
|
override fun getDescription() = "Dumps project dependencies to a mermaid file."
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
operator fun invoke() {
|
||||||
|
output.get().asFile.writeText(mermaid())
|
||||||
|
legend.get().asFile.writeText(legend())
|
||||||
|
logger.lifecycle(output.get().asFile.toPath().toUri().toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mermaid() = buildString {
|
||||||
|
val dependencies: Set<Dependency> = dependencies.get()
|
||||||
|
.flatMapTo(mutableSetOf()) { (project, entries) -> entries.map { it.toDependency(project) } }
|
||||||
|
// FrontMatter configuration (not supported yet on GitHub.com)
|
||||||
|
appendLine(
|
||||||
|
// language=YAML
|
||||||
|
"""
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: SIMPLE
|
||||||
|
---
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
// Graph declaration
|
||||||
|
appendLine("graph TB")
|
||||||
|
// Nodes and subgraphs
|
||||||
|
val (rootProjects, nestedProjects) = dependencies
|
||||||
|
.map { listOf(it.project, it.dependency) }.flatten().toSet()
|
||||||
|
.plus(projectPath.get()) // Special case when this specific module has no other dependency
|
||||||
|
.groupBy { it.substringBeforeLast(":") }
|
||||||
|
.entries.partition { it.key.isEmpty() }
|
||||||
|
|
||||||
|
val orderedGroups = nestedProjects.groupBy {
|
||||||
|
if (it.key.count { char -> char == ':' } > 1) it.key.substringBeforeLast(":") else ""
|
||||||
|
}
|
||||||
|
|
||||||
|
orderedGroups.forEach { (outerGroup, innerGroups) ->
|
||||||
|
if (outerGroup.isNotEmpty()) {
|
||||||
|
appendLine(" subgraph $outerGroup")
|
||||||
|
appendLine(" direction TB")
|
||||||
|
}
|
||||||
|
innerGroups.sortedWith(
|
||||||
|
compareBy(
|
||||||
|
{ (group, _) ->
|
||||||
|
dependencies.filter { dep ->
|
||||||
|
val toGroup = dep.dependency.substringBeforeLast(":")
|
||||||
|
toGroup == group && dep.project.substringBeforeLast(":") != group
|
||||||
|
}.count()
|
||||||
|
},
|
||||||
|
{ -it.value.size },
|
||||||
|
),
|
||||||
|
).forEach { (group, projects) ->
|
||||||
|
val indent = if (outerGroup.isNotEmpty()) 4 else 2
|
||||||
|
appendLine(" ".repeat(indent) + "subgraph $group")
|
||||||
|
appendLine(" ".repeat(indent) + " direction TB")
|
||||||
|
projects.sorted().forEach {
|
||||||
|
appendLine(it.alias(indent = indent + 2, plugins.get().getValue(it)))
|
||||||
|
}
|
||||||
|
appendLine(" ".repeat(indent) + "end")
|
||||||
|
}
|
||||||
|
if (outerGroup.isNotEmpty()) {
|
||||||
|
appendLine(" end")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProjects.flatMap { it.value }.sortedDescending().forEach {
|
||||||
|
appendLine(it.alias(indent = 2, plugins.get().getValue(it)))
|
||||||
|
}
|
||||||
|
// Links
|
||||||
|
if (dependencies.isNotEmpty()) appendLine()
|
||||||
|
dependencies
|
||||||
|
.sortedWith(compareBy({ it.project }, { it.dependency }, { it.configuration }))
|
||||||
|
.forEach { appendLine(it.link(indent = 2)) }
|
||||||
|
// Classes
|
||||||
|
appendLine()
|
||||||
|
PluginType.entries.forEach { appendLine(it.classDef()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun legend() = buildString {
|
||||||
|
appendLine("graph TB")
|
||||||
|
listOf(
|
||||||
|
"application" to PluginType.AndroidApplication,
|
||||||
|
"feature" to PluginType.AndroidFeature,
|
||||||
|
"library" to PluginType.AndroidLibrary,
|
||||||
|
"jvm" to PluginType.Jvm,
|
||||||
|
).forEach { (name, type) ->
|
||||||
|
appendLine(name.alias(indent = 2, type))
|
||||||
|
}
|
||||||
|
appendLine()
|
||||||
|
listOf(
|
||||||
|
Dependency("application", "implementation", "feature"),
|
||||||
|
Dependency("library", "api", "jvm"),
|
||||||
|
).forEach {
|
||||||
|
appendLine(it.link(indent = 2))
|
||||||
|
}
|
||||||
|
appendLine()
|
||||||
|
PluginType.entries.forEach { appendLine(it.classDef()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Dependency(val project: String, val configuration: String, val dependency: String)
|
||||||
|
|
||||||
|
private fun Pair<String, String>.toDependency(project: String) =
|
||||||
|
Dependency(project, configuration = first, dependency = second)
|
||||||
|
|
||||||
|
private fun String.alias(indent: Int, pluginType: PluginType): String = buildString {
|
||||||
|
append(" ".repeat(indent))
|
||||||
|
append(this@alias)
|
||||||
|
append("[").append(substringAfterLast(":")).append("]:::")
|
||||||
|
append(pluginType.ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Dependency.link(indent: Int) = buildString {
|
||||||
|
append(" ".repeat(indent))
|
||||||
|
append(project).append(" ")
|
||||||
|
append(
|
||||||
|
when (configuration) {
|
||||||
|
"api" -> "-->"
|
||||||
|
"implementation" -> "-.->"
|
||||||
|
else -> "-.->|$configuration|"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
append(" ").append(dependency)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PluginType.classDef() = "classDef $ref $style;"
|
||||||
|
}
|
||||||
|
|
||||||
|
@CacheableTask
|
||||||
|
private abstract class GraphUpdateTask : DefaultTask() {
|
||||||
|
|
||||||
|
@get:Input
|
||||||
|
abstract val projectPath: Property<String>
|
||||||
|
|
||||||
|
@get:InputFile
|
||||||
|
@get:PathSensitive(NONE)
|
||||||
|
abstract val input: RegularFileProperty
|
||||||
|
|
||||||
|
@get:InputFile
|
||||||
|
@get:PathSensitive(NONE)
|
||||||
|
abstract val legend: RegularFileProperty
|
||||||
|
|
||||||
|
@get:OutputFile
|
||||||
|
abstract val output: RegularFileProperty
|
||||||
|
|
||||||
|
override fun getDescription() = "Updates Markdown file with the corresponding dependency graph."
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
operator fun invoke() = with(output.get().asFile) {
|
||||||
|
if (!exists()) {
|
||||||
|
createNewFile()
|
||||||
|
writeText(
|
||||||
|
"""
|
||||||
|
# `${projectPath.get()}`
|
||||||
|
|
||||||
|
## Module dependency graph
|
||||||
|
|
||||||
|
<!--region graph--> <!--endregion-->
|
||||||
|
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val mermaid = input.get().asFile.readText().trimTrailingNewLines()
|
||||||
|
val legend = legend.get().asFile.readText().trimTrailingNewLines()
|
||||||
|
val regex = """(<!--region graph-->)(.*?)(<!--endregion-->)""".toRegex(DOT_MATCHES_ALL)
|
||||||
|
val text = readText().replace(regex) { match ->
|
||||||
|
val (start, _, end) = match.destructured
|
||||||
|
"""
|
||||||
|
|$start
|
||||||
|
|```mermaid
|
||||||
|
|$mermaid
|
||||||
|
|```
|
||||||
|
|
|
||||||
|
|<details><summary>📋 Graph legend</summary>
|
||||||
|
|
|
||||||
|
|```mermaid
|
||||||
|
|$legend
|
||||||
|
|```
|
||||||
|
|
|
||||||
|
|</details>
|
||||||
|
|$end
|
||||||
|
""".trimMargin()
|
||||||
|
}
|
||||||
|
writeText(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.trimTrailingNewLines() = lines()
|
||||||
|
.dropLastWhile(String::isBlank)
|
||||||
|
.joinToString(System.lineSeparator())
|
||||||
|
}
|
||||||
@ -1,3 +1,48 @@
|
|||||||
# :core:analytics module
|
# `:core:analytics`
|
||||||
## Dependency graph
|
|
||||||

|
## Module dependency graph
|
||||||
|
|
||||||
|
<!--region graph-->
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: SIMPLE
|
||||||
|
---
|
||||||
|
graph TB
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:analytics[analytics]:::android-library
|
||||||
|
end
|
||||||
|
|
||||||
|
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-->
|
||||||
|
|||||||
@ -1,3 +1,48 @@
|
|||||||
# :core:common module
|
# `:core:common`
|
||||||
## Dependency graph
|
|
||||||

|
## Module dependency graph
|
||||||
|
|
||||||
|
<!--region graph-->
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: SIMPLE
|
||||||
|
---
|
||||||
|
graph TB
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:common[common]:::jvm-library
|
||||||
|
end
|
||||||
|
|
||||||
|
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-->
|
||||||
|
|||||||
@ -1,3 +1,73 @@
|
|||||||
# :core:data-test module
|
# `:core:data-test`
|
||||||
## Dependency graph
|
|
||||||

|
## Module dependency graph
|
||||||
|
|
||||||
|
<!--region graph-->
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: SIMPLE
|
||||||
|
---
|
||||||
|
graph TB
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:analytics[analytics]:::android-library
|
||||||
|
:core:common[common]:::jvm-library
|
||||||
|
:core:data[data]:::android-library
|
||||||
|
:core:data-test[data-test]:::android-library
|
||||||
|
:core:database[database]:::android-library
|
||||||
|
:core:datastore[datastore]:::android-library
|
||||||
|
:core:datastore-proto[datastore-proto]:::android-library
|
||||||
|
:core:model[model]:::jvm-library
|
||||||
|
:core:network[network]:::android-library
|
||||||
|
:core:notifications[notifications]:::android-library
|
||||||
|
end
|
||||||
|
|
||||||
|
:core:data -.-> :core:analytics
|
||||||
|
:core:data --> :core:common
|
||||||
|
:core:data --> :core:database
|
||||||
|
:core:data --> :core:datastore
|
||||||
|
:core:data --> :core:network
|
||||||
|
:core:data -.-> :core:notifications
|
||||||
|
:core:data-test --> :core:data
|
||||||
|
:core:database --> :core:model
|
||||||
|
:core:datastore -.-> :core:common
|
||||||
|
:core:datastore --> :core:datastore-proto
|
||||||
|
:core:datastore --> :core:model
|
||||||
|
:core:network --> :core:common
|
||||||
|
:core:network --> :core:model
|
||||||
|
:core:notifications -.-> :core:common
|
||||||
|
:core:notifications --> :core:model
|
||||||
|
|
||||||
|
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-->
|
||||||
|
|||||||
@ -1,3 +1,71 @@
|
|||||||
# :core:data module
|
# `:core:data`
|
||||||
## Dependency graph
|
|
||||||

|
## Module dependency graph
|
||||||
|
|
||||||
|
<!--region graph-->
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: SIMPLE
|
||||||
|
---
|
||||||
|
graph TB
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:analytics[analytics]:::android-library
|
||||||
|
:core:common[common]:::jvm-library
|
||||||
|
:core:data[data]:::android-library
|
||||||
|
:core:database[database]:::android-library
|
||||||
|
:core:datastore[datastore]:::android-library
|
||||||
|
:core:datastore-proto[datastore-proto]:::android-library
|
||||||
|
:core:model[model]:::jvm-library
|
||||||
|
:core:network[network]:::android-library
|
||||||
|
:core:notifications[notifications]:::android-library
|
||||||
|
end
|
||||||
|
|
||||||
|
:core:data -.-> :core:analytics
|
||||||
|
:core:data --> :core:common
|
||||||
|
:core:data --> :core:database
|
||||||
|
:core:data --> :core:datastore
|
||||||
|
:core:data --> :core:network
|
||||||
|
:core:data -.-> :core:notifications
|
||||||
|
:core:database --> :core:model
|
||||||
|
:core:datastore -.-> :core:common
|
||||||
|
:core:datastore --> :core:datastore-proto
|
||||||
|
:core:datastore --> :core:model
|
||||||
|
:core:network --> :core:common
|
||||||
|
:core:network --> :core:model
|
||||||
|
:core:notifications -.-> :core:common
|
||||||
|
:core:notifications --> :core:model
|
||||||
|
|
||||||
|
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-->
|
||||||
|
|||||||
@ -1,3 +1,51 @@
|
|||||||
# :core:database module
|
# `:core:database`
|
||||||
## Dependency graph
|
|
||||||

|
## Module dependency graph
|
||||||
|
|
||||||
|
<!--region graph-->
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: SIMPLE
|
||||||
|
---
|
||||||
|
graph TB
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:database[database]:::android-library
|
||||||
|
:core:model[model]:::jvm-library
|
||||||
|
end
|
||||||
|
|
||||||
|
:core:database --> :core:model
|
||||||
|
|
||||||
|
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-->
|
||||||
|
|||||||
@ -1,3 +1,48 @@
|
|||||||
# :core:datastore-proto module
|
# `:core:datastore-proto`
|
||||||
## Dependency graph
|
|
||||||

|
## Module dependency graph
|
||||||
|
|
||||||
|
<!--region graph-->
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: SIMPLE
|
||||||
|
---
|
||||||
|
graph TB
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:datastore-proto[datastore-proto]:::android-library
|
||||||
|
end
|
||||||
|
|
||||||
|
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-->
|
||||||
|
|||||||
@ -1,3 +1,58 @@
|
|||||||
# :core:datastore-test module
|
# `:core:datastore-test`
|
||||||
## Dependency graph
|
|
||||||

|
## Module dependency graph
|
||||||
|
|
||||||
|
<!--region graph-->
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: SIMPLE
|
||||||
|
---
|
||||||
|
graph TB
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:common[common]:::jvm-library
|
||||||
|
:core:datastore[datastore]:::android-library
|
||||||
|
:core:datastore-proto[datastore-proto]:::android-library
|
||||||
|
:core:datastore-test[datastore-test]:::android-library
|
||||||
|
:core:model[model]:::jvm-library
|
||||||
|
end
|
||||||
|
|
||||||
|
:core:datastore -.-> :core:common
|
||||||
|
:core:datastore --> :core:datastore-proto
|
||||||
|
:core:datastore --> :core:model
|
||||||
|
:core:datastore-test -.-> :core:common
|
||||||
|
:core:datastore-test -.-> :core:datastore
|
||||||
|
|
||||||
|
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-->
|
||||||
|
|||||||
@ -1,3 +1,55 @@
|
|||||||
# :core:datastore module
|
# `:core:datastore`
|
||||||
## Dependency graph
|
|
||||||

|
## Module dependency graph
|
||||||
|
|
||||||
|
<!--region graph-->
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: SIMPLE
|
||||||
|
---
|
||||||
|
graph TB
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:common[common]:::jvm-library
|
||||||
|
:core:datastore[datastore]:::android-library
|
||||||
|
:core:datastore-proto[datastore-proto]:::android-library
|
||||||
|
:core:model[model]:::jvm-library
|
||||||
|
end
|
||||||
|
|
||||||
|
:core:datastore -.-> :core:common
|
||||||
|
:core:datastore --> :core:datastore-proto
|
||||||
|
:core:datastore --> :core:model
|
||||||
|
|
||||||
|
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-->
|
||||||
|
|||||||
@ -1,3 +1,48 @@
|
|||||||
# :core:designsystem module
|
# `:core:designsystem`
|
||||||
## Dependency graph
|
|
||||||

|
## Module dependency graph
|
||||||
|
|
||||||
|
<!--region graph-->
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: SIMPLE
|
||||||
|
---
|
||||||
|
graph TB
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:designsystem[designsystem]:::android-library
|
||||||
|
end
|
||||||
|
|
||||||
|
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,17 @@
|
|||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
sdk = 35
|
||||||
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
@ -1,3 +1,74 @@
|
|||||||
# :core:domain module
|
# `:core:domain`
|
||||||
## Dependency graph
|
|
||||||

|
## Module dependency graph
|
||||||
|
|
||||||
|
<!--region graph-->
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: SIMPLE
|
||||||
|
---
|
||||||
|
graph TB
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:analytics[analytics]:::android-library
|
||||||
|
:core:common[common]:::jvm-library
|
||||||
|
:core:data[data]:::android-library
|
||||||
|
:core:database[database]:::android-library
|
||||||
|
:core:datastore[datastore]:::android-library
|
||||||
|
:core:datastore-proto[datastore-proto]:::android-library
|
||||||
|
:core:domain[domain]:::android-library
|
||||||
|
:core:model[model]:::jvm-library
|
||||||
|
:core:network[network]:::android-library
|
||||||
|
:core:notifications[notifications]:::android-library
|
||||||
|
end
|
||||||
|
|
||||||
|
:core:data -.-> :core:analytics
|
||||||
|
:core:data --> :core:common
|
||||||
|
:core:data --> :core:database
|
||||||
|
:core:data --> :core:datastore
|
||||||
|
:core:data --> :core:network
|
||||||
|
:core:data -.-> :core:notifications
|
||||||
|
:core:database --> :core:model
|
||||||
|
:core:datastore -.-> :core:common
|
||||||
|
:core:datastore --> :core:datastore-proto
|
||||||
|
:core:datastore --> :core:model
|
||||||
|
:core:domain --> :core:data
|
||||||
|
:core:domain --> :core:model
|
||||||
|
:core:network --> :core:common
|
||||||
|
:core:network --> :core:model
|
||||||
|
:core:notifications -.-> :core:common
|
||||||
|
:core:notifications --> :core:model
|
||||||
|
|
||||||
|
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-->
|
||||||
|
|||||||
@ -1,3 +1,48 @@
|
|||||||
# :core:model module
|
# `:core:model`
|
||||||
## Dependency graph
|
|
||||||

|
## Module dependency graph
|
||||||
|
|
||||||
|
<!--region graph-->
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: SIMPLE
|
||||||
|
---
|
||||||
|
graph TB
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:model[model]:::jvm-library
|
||||||
|
end
|
||||||
|
|
||||||
|
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,3 @@
|
|||||||
|
# :core:navigation module
|
||||||
|
## Dependency graph
|
||||||
|

|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.nowinandroid.android.library)
|
||||||
|
alias(libs.plugins.nowinandroid.hilt)
|
||||||
|
alias(libs.plugins.hilt)
|
||||||
|
alias(libs.plugins.kotlin.serialization)
|
||||||
|
alias(libs.plugins.compose)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.google.samples.apps.nowinandroid.core.navigation"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(libs.androidx.navigation3.runtime)
|
||||||
|
implementation(libs.androidx.savedstate.compose)
|
||||||
|
implementation(libs.androidx.lifecycle.viewModel.navigation3)
|
||||||
|
|
||||||
|
testImplementation(libs.truth)
|
||||||
|
|
||||||
|
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
||||||
|
androidTestImplementation(libs.androidx.test.ext)
|
||||||
|
androidTestImplementation(libs.androidx.compose.ui.testManifest)
|
||||||
|
androidTestImplementation(libs.androidx.lifecycle.viewModel.testing)
|
||||||
|
androidTestImplementation(libs.truth)
|
||||||
|
}
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* 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.core.navigation
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
|
import androidx.compose.runtime.toMutableStateList
|
||||||
|
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
|
||||||
|
import androidx.navigation3.runtime.NavBackStack
|
||||||
|
import androidx.navigation3.runtime.NavEntry
|
||||||
|
import androidx.navigation3.runtime.NavKey
|
||||||
|
import androidx.navigation3.runtime.rememberDecoratedNavEntries
|
||||||
|
import androidx.navigation3.runtime.rememberNavBackStack
|
||||||
|
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a navigation state that persists config changes and process death.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun rememberNavigationState(
|
||||||
|
startKey: NavKey,
|
||||||
|
topLevelKeys: Set<NavKey>,
|
||||||
|
): NavigationState {
|
||||||
|
val topLevelStack = rememberNavBackStack(startKey)
|
||||||
|
val subStacks = topLevelKeys.associateWith { key -> rememberNavBackStack(key) }
|
||||||
|
|
||||||
|
return remember(startKey, topLevelKeys) {
|
||||||
|
NavigationState(
|
||||||
|
startKey = startKey,
|
||||||
|
topLevelStack = topLevelStack,
|
||||||
|
subStacks = subStacks,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State holder for navigation state.
|
||||||
|
*
|
||||||
|
* @param startKey - the starting navigation key. The user will exit the app through this key.
|
||||||
|
* @param topLevelStack - the top level back stack. It holds only top level keys.
|
||||||
|
* @param subStacks - the back stacks for each top level key
|
||||||
|
*/
|
||||||
|
class NavigationState(
|
||||||
|
val startKey: NavKey,
|
||||||
|
val topLevelStack: NavBackStack<NavKey>,
|
||||||
|
val subStacks: Map<NavKey, NavBackStack<NavKey>>,
|
||||||
|
) {
|
||||||
|
val currentTopLevelKey: NavKey by derivedStateOf { topLevelStack.last() }
|
||||||
|
|
||||||
|
val topLevelKeys
|
||||||
|
get() = subStacks.keys
|
||||||
|
|
||||||
|
@get:VisibleForTesting
|
||||||
|
val currentSubStack: NavBackStack<NavKey>
|
||||||
|
get() = subStacks[currentTopLevelKey]
|
||||||
|
?: error("Sub stack for $currentTopLevelKey does not exist")
|
||||||
|
|
||||||
|
@get:VisibleForTesting
|
||||||
|
val currentKey: NavKey by derivedStateOf { currentSubStack.last() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert NavigationState into NavEntries.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun NavigationState.toEntries(
|
||||||
|
entryProvider: (NavKey) -> NavEntry<NavKey>,
|
||||||
|
): SnapshotStateList<NavEntry<NavKey>> {
|
||||||
|
val decoratedEntries = subStacks.mapValues { (_, stack) ->
|
||||||
|
val decorators = listOf(
|
||||||
|
rememberSaveableStateHolderNavEntryDecorator<NavKey>(),
|
||||||
|
rememberViewModelStoreNavEntryDecorator<NavKey>(),
|
||||||
|
)
|
||||||
|
rememberDecoratedNavEntries(
|
||||||
|
backStack = stack,
|
||||||
|
entryDecorators = decorators,
|
||||||
|
entryProvider = entryProvider,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return topLevelStack
|
||||||
|
.flatMap { decoratedEntries[it] ?: emptyList() }
|
||||||
|
.toMutableStateList()
|
||||||
|
}
|
||||||
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* 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.core.navigation
|
||||||
|
|
||||||
|
import androidx.navigation3.runtime.NavKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles navigation events (forward and back) by updating the navigation state.
|
||||||
|
*
|
||||||
|
* @param state - The navigation state that will be updated in response to navigation events.
|
||||||
|
*/
|
||||||
|
class Navigator(val state: NavigationState) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to a navigation key
|
||||||
|
*
|
||||||
|
* @param key - the navigation key to navigate to.
|
||||||
|
*/
|
||||||
|
fun navigate(key: NavKey) {
|
||||||
|
when (key) {
|
||||||
|
state.currentTopLevelKey -> clearSubStack()
|
||||||
|
in state.topLevelKeys -> goToTopLevel(key)
|
||||||
|
else -> goToKey(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go back to the previous navigation key.
|
||||||
|
*/
|
||||||
|
fun goBack() {
|
||||||
|
when (state.currentKey) {
|
||||||
|
state.startKey -> error("You cannot go back from the start route")
|
||||||
|
state.currentTopLevelKey -> {
|
||||||
|
// We're at the base of the current sub stack, go back to the previous top level
|
||||||
|
// stack.
|
||||||
|
state.topLevelStack.removeLastOrNull()
|
||||||
|
}
|
||||||
|
else -> state.currentSubStack.removeLastOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to a non top level key.
|
||||||
|
*/
|
||||||
|
private fun goToKey(key: NavKey) {
|
||||||
|
state.currentSubStack.apply {
|
||||||
|
// Remove it if it's already in the stack so it's added at the end.
|
||||||
|
remove(key)
|
||||||
|
add(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to a top level stack.
|
||||||
|
*/
|
||||||
|
private fun goToTopLevel(key: NavKey) {
|
||||||
|
state.topLevelStack.apply {
|
||||||
|
if (key == state.startKey) {
|
||||||
|
// This is the start key. Clear the stack so it's added as the only key.
|
||||||
|
clear()
|
||||||
|
} else {
|
||||||
|
// Remove it if it's already in the stack so it's added at the end.
|
||||||
|
remove(key)
|
||||||
|
}
|
||||||
|
add(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clearing all but the root key in the current sub stack.
|
||||||
|
*/
|
||||||
|
private fun clearSubStack() {
|
||||||
|
state.currentSubStack.run {
|
||||||
|
if (size > 1) subList(1, size).clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,256 @@
|
|||||||
|
/*
|
||||||
|
* 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.core.navigation
|
||||||
|
|
||||||
|
import androidx.navigation3.runtime.NavBackStack
|
||||||
|
import androidx.navigation3.runtime.NavKey
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
|
||||||
|
private object TestFirstTopLevelKey : NavKey
|
||||||
|
private object TestSecondTopLevelKey : NavKey
|
||||||
|
private object TestThirdTopLevelKey : NavKey
|
||||||
|
private object TestKeyFirst : NavKey
|
||||||
|
private object TestKeySecond : NavKey
|
||||||
|
|
||||||
|
class NavigatorTest {
|
||||||
|
|
||||||
|
private lateinit var navigationState: NavigationState
|
||||||
|
private lateinit var navigator: Navigator
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
val startKey = TestFirstTopLevelKey
|
||||||
|
val topLevelStack = NavBackStack<NavKey>(startKey)
|
||||||
|
val topLevelKeys = listOf(
|
||||||
|
startKey,
|
||||||
|
TestSecondTopLevelKey,
|
||||||
|
TestThirdTopLevelKey,
|
||||||
|
)
|
||||||
|
val subStacks = topLevelKeys.associateWith { key -> NavBackStack(key) }
|
||||||
|
|
||||||
|
navigationState = NavigationState(
|
||||||
|
startKey = startKey,
|
||||||
|
topLevelStack = topLevelStack,
|
||||||
|
subStacks = subStacks,
|
||||||
|
)
|
||||||
|
navigator = Navigator(navigationState)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testStartKey() {
|
||||||
|
assertThat(navigationState.startKey).isEqualTo(TestFirstTopLevelKey)
|
||||||
|
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNavigate() {
|
||||||
|
navigator.navigate(TestKeyFirst)
|
||||||
|
|
||||||
|
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||||
|
assertThat(navigationState.subStacks[TestFirstTopLevelKey]?.last()).isEqualTo(TestKeyFirst)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNavigateTopLevel() {
|
||||||
|
navigator.navigate(TestSecondTopLevelKey)
|
||||||
|
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestSecondTopLevelKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNavigateSingleTop() {
|
||||||
|
navigator.navigate(TestKeyFirst)
|
||||||
|
|
||||||
|
assertThat(navigationState.currentSubStack).containsExactly(
|
||||||
|
TestFirstTopLevelKey,
|
||||||
|
TestKeyFirst,
|
||||||
|
).inOrder()
|
||||||
|
|
||||||
|
navigator.navigate(TestKeyFirst)
|
||||||
|
|
||||||
|
assertThat(navigationState.currentSubStack).containsExactly(
|
||||||
|
TestFirstTopLevelKey,
|
||||||
|
TestKeyFirst,
|
||||||
|
).inOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNavigateTopLevelSingleTop() {
|
||||||
|
navigator.navigate(TestSecondTopLevelKey)
|
||||||
|
navigator.navigate(TestKeyFirst)
|
||||||
|
|
||||||
|
assertThat(navigationState.currentSubStack).containsExactly(
|
||||||
|
TestSecondTopLevelKey,
|
||||||
|
TestKeyFirst,
|
||||||
|
).inOrder()
|
||||||
|
|
||||||
|
navigator.navigate(TestSecondTopLevelKey)
|
||||||
|
|
||||||
|
assertThat(navigationState.currentSubStack).containsExactly(
|
||||||
|
TestSecondTopLevelKey,
|
||||||
|
).inOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSubStack() {
|
||||||
|
navigator.navigate(TestKeyFirst)
|
||||||
|
|
||||||
|
assertThat(navigationState.currentKey).isEqualTo(TestKeyFirst)
|
||||||
|
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||||
|
|
||||||
|
navigator.navigate(TestKeySecond)
|
||||||
|
|
||||||
|
assertThat(navigationState.currentKey).isEqualTo(TestKeySecond)
|
||||||
|
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMultiStack() {
|
||||||
|
// add to start stack
|
||||||
|
navigator.navigate(TestKeyFirst)
|
||||||
|
|
||||||
|
assertThat(navigationState.currentKey).isEqualTo(TestKeyFirst)
|
||||||
|
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||||
|
|
||||||
|
// navigate to new top level
|
||||||
|
navigator.navigate(TestSecondTopLevelKey)
|
||||||
|
|
||||||
|
assertThat(navigationState.currentKey).isEqualTo(TestSecondTopLevelKey)
|
||||||
|
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestSecondTopLevelKey)
|
||||||
|
|
||||||
|
// add to new stack
|
||||||
|
navigator.navigate(TestKeySecond)
|
||||||
|
|
||||||
|
assertThat(navigationState.currentKey).isEqualTo(TestKeySecond)
|
||||||
|
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestSecondTopLevelKey)
|
||||||
|
|
||||||
|
// go back to start stack
|
||||||
|
navigator.navigate(TestFirstTopLevelKey)
|
||||||
|
|
||||||
|
assertThat(navigationState.currentKey).isEqualTo(TestKeyFirst)
|
||||||
|
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPopOneNonTopLevel() {
|
||||||
|
navigator.navigate(TestKeyFirst)
|
||||||
|
navigator.navigate(TestKeySecond)
|
||||||
|
|
||||||
|
assertThat(navigationState.currentSubStack).containsExactly(
|
||||||
|
TestFirstTopLevelKey,
|
||||||
|
TestKeyFirst,
|
||||||
|
TestKeySecond,
|
||||||
|
).inOrder()
|
||||||
|
|
||||||
|
navigator.goBack()
|
||||||
|
|
||||||
|
assertThat(navigationState.currentSubStack).containsExactly(
|
||||||
|
TestFirstTopLevelKey,
|
||||||
|
TestKeyFirst,
|
||||||
|
).inOrder()
|
||||||
|
|
||||||
|
assertThat(navigationState.currentKey).isEqualTo(TestKeyFirst)
|
||||||
|
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPopOneTopLevel() {
|
||||||
|
navigator.navigate(TestKeyFirst)
|
||||||
|
navigator.navigate(TestSecondTopLevelKey)
|
||||||
|
|
||||||
|
assertThat(navigationState.currentSubStack).containsExactly(
|
||||||
|
TestSecondTopLevelKey,
|
||||||
|
).inOrder()
|
||||||
|
|
||||||
|
assertThat(navigationState.currentKey).isEqualTo(TestSecondTopLevelKey)
|
||||||
|
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestSecondTopLevelKey)
|
||||||
|
|
||||||
|
// remove TopLevel
|
||||||
|
navigator.goBack()
|
||||||
|
|
||||||
|
assertThat(navigationState.currentSubStack).containsExactly(
|
||||||
|
TestFirstTopLevelKey,
|
||||||
|
TestKeyFirst,
|
||||||
|
).inOrder()
|
||||||
|
|
||||||
|
assertThat(navigationState.currentKey).isEqualTo(TestKeyFirst)
|
||||||
|
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun popMultipleNonTopLevel() {
|
||||||
|
navigator.navigate(TestKeyFirst)
|
||||||
|
navigator.navigate(TestKeySecond)
|
||||||
|
|
||||||
|
assertThat(navigationState.currentSubStack).containsExactly(
|
||||||
|
TestFirstTopLevelKey,
|
||||||
|
TestKeyFirst,
|
||||||
|
TestKeySecond,
|
||||||
|
).inOrder()
|
||||||
|
|
||||||
|
navigator.goBack()
|
||||||
|
navigator.goBack()
|
||||||
|
|
||||||
|
assertThat(navigationState.currentSubStack).containsExactly(
|
||||||
|
TestFirstTopLevelKey,
|
||||||
|
).inOrder()
|
||||||
|
|
||||||
|
assertThat(navigationState.currentKey).isEqualTo(TestFirstTopLevelKey)
|
||||||
|
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun popMultipleTopLevel() {
|
||||||
|
// second sub-stack
|
||||||
|
navigator.navigate(TestSecondTopLevelKey)
|
||||||
|
navigator.navigate(TestKeyFirst)
|
||||||
|
|
||||||
|
assertThat(navigationState.currentSubStack).containsExactly(
|
||||||
|
TestSecondTopLevelKey,
|
||||||
|
TestKeyFirst,
|
||||||
|
).inOrder()
|
||||||
|
|
||||||
|
// third sub-stack
|
||||||
|
navigator.navigate(TestThirdTopLevelKey)
|
||||||
|
navigator.navigate(TestKeySecond)
|
||||||
|
|
||||||
|
assertThat(navigationState.currentSubStack).containsExactly(
|
||||||
|
TestThirdTopLevelKey,
|
||||||
|
TestKeySecond,
|
||||||
|
).inOrder()
|
||||||
|
|
||||||
|
repeat(4) {
|
||||||
|
navigator.goBack()
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(navigationState.currentSubStack).containsExactly(
|
||||||
|
TestFirstTopLevelKey,
|
||||||
|
).inOrder()
|
||||||
|
|
||||||
|
assertThat(navigationState.currentKey).isEqualTo(TestFirstTopLevelKey)
|
||||||
|
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun throwOnEmptyBackStack() {
|
||||||
|
assertFailsWith<IllegalStateException> {
|
||||||
|
navigator.goBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,53 @@
|
|||||||
# :core:network module
|
# `:core:network`
|
||||||
## Dependency graph
|
|
||||||

|
## Module dependency graph
|
||||||
|
|
||||||
|
<!--region graph-->
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: SIMPLE
|
||||||
|
---
|
||||||
|
graph TB
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:common[common]:::jvm-library
|
||||||
|
:core:model[model]:::jvm-library
|
||||||
|
:core:network[network]:::android-library
|
||||||
|
end
|
||||||
|
|
||||||
|
:core:network --> :core:common
|
||||||
|
:core:network --> :core:model
|
||||||
|
|
||||||
|
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-->
|
||||||
|
|||||||
@ -1,3 +1,53 @@
|
|||||||
# :core:notifications module
|
# `:core:notifications`
|
||||||
## Dependency graph
|
|
||||||

|
## Module dependency graph
|
||||||
|
|
||||||
|
<!--region graph-->
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: SIMPLE
|
||||||
|
---
|
||||||
|
graph TB
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:common[common]:::jvm-library
|
||||||
|
:core:model[model]:::jvm-library
|
||||||
|
:core:notifications[notifications]:::android-library
|
||||||
|
end
|
||||||
|
|
||||||
|
:core:notifications -.-> :core:common
|
||||||
|
:core:notifications --> :core:model
|
||||||
|
|
||||||
|
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-->
|
||||||
|
|||||||
@ -1,3 +1,51 @@
|
|||||||
# :core:screenshot-testing module
|
# `:core:screenshot-testing`
|
||||||
## Dependency graph
|
|
||||||

|
## Module dependency graph
|
||||||
|
|
||||||
|
<!--region graph-->
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: SIMPLE
|
||||||
|
---
|
||||||
|
graph TB
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:designsystem[designsystem]:::android-library
|
||||||
|
:core:screenshot-testing[screenshot-testing]:::android-library
|
||||||
|
end
|
||||||
|
|
||||||
|
:core:screenshot-testing -.-> :core:designsystem
|
||||||
|
|
||||||
|
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-->
|
||||||
|
|||||||
@ -1,3 +1,77 @@
|
|||||||
# :core:testing module
|
# `:core:testing`
|
||||||
## Dependency graph
|
|
||||||

|
## Module dependency graph
|
||||||
|
|
||||||
|
<!--region graph-->
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: SIMPLE
|
||||||
|
---
|
||||||
|
graph TB
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:analytics[analytics]:::android-library
|
||||||
|
:core:common[common]:::jvm-library
|
||||||
|
:core:data[data]:::android-library
|
||||||
|
:core:database[database]:::android-library
|
||||||
|
:core:datastore[datastore]:::android-library
|
||||||
|
:core:datastore-proto[datastore-proto]:::android-library
|
||||||
|
:core:model[model]:::jvm-library
|
||||||
|
:core:network[network]:::android-library
|
||||||
|
:core:notifications[notifications]:::android-library
|
||||||
|
:core:testing[testing]:::android-library
|
||||||
|
end
|
||||||
|
|
||||||
|
:core:data -.-> :core:analytics
|
||||||
|
:core:data --> :core:common
|
||||||
|
:core:data --> :core:database
|
||||||
|
:core:data --> :core:datastore
|
||||||
|
:core:data --> :core:network
|
||||||
|
:core:data -.-> :core:notifications
|
||||||
|
:core:database --> :core:model
|
||||||
|
:core:datastore -.-> :core:common
|
||||||
|
:core:datastore --> :core:datastore-proto
|
||||||
|
:core:datastore --> :core:model
|
||||||
|
:core:network --> :core:common
|
||||||
|
:core:network --> :core:model
|
||||||
|
:core:notifications -.-> :core:common
|
||||||
|
:core:notifications --> :core:model
|
||||||
|
:core:testing --> :core:analytics
|
||||||
|
:core:testing --> :core:common
|
||||||
|
:core:testing --> :core:data
|
||||||
|
:core:testing --> :core:model
|
||||||
|
:core:testing --> :core:notifications
|
||||||
|
|
||||||
|
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-->
|
||||||
|
|||||||
@ -1,3 +1,55 @@
|
|||||||
# :core:ui module
|
# `:core:ui`
|
||||||
## Dependency graph
|
|
||||||

|
## Module dependency graph
|
||||||
|
|
||||||
|
<!--region graph-->
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: SIMPLE
|
||||||
|
---
|
||||||
|
graph TB
|
||||||
|
subgraph :core
|
||||||
|
direction TB
|
||||||
|
:core:analytics[analytics]:::android-library
|
||||||
|
:core:designsystem[designsystem]:::android-library
|
||||||
|
:core:model[model]:::jvm-library
|
||||||
|
:core:ui[ui]:::android-library
|
||||||
|
end
|
||||||
|
|
||||||
|
:core:ui --> :core:analytics
|
||||||
|
:core:ui --> :core:designsystem
|
||||||
|
:core:ui --> :core:model
|
||||||
|
|
||||||
|
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-->
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 459 B |
|
Before Width: | Height: | Size: 456 B |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 920 B |