diff --git a/.github/workflows/build.yml b/.github/workflows/build_1.x.yml similarity index 93% rename from .github/workflows/build.yml rename to .github/workflows/build_1.x.yml index b60563577..bd3a823e9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build_1.x.yml @@ -1,10 +1,9 @@ -name: Build +name: Build_1.x on: push: branches: - 1.x - - 2.x pull_request: workflow_dispatch: @@ -130,14 +129,3 @@ jobs: path: | **/build/reports logcat.out - - check-documentation: - name: Check documentation - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.x' - - run: pip install mkdocs-material - - run: mkdocs build --strict \ No newline at end of file diff --git a/.github/workflows/build_2.x.yml b/.github/workflows/build_2.x.yml new file mode 100644 index 000000000..2205306cb --- /dev/null +++ b/.github/workflows/build_2.x.yml @@ -0,0 +1,161 @@ +name: Build + +on: + push: + branches: + - 2.x + pull_request: + workflow_dispatch: + +env: + MAIN_BRANCH: ${{ github.ref == 'refs/heads/1.x' || github.ref == 'refs/heads/2.x' }} + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + - uses: gradle/wrapper-validation-action@v2 + - uses: gradle/actions/setup-gradle@v3 + with: + cache-read-only: ${{ env.MAIN_BRANCH != 'true' }} + - name: Build + run: > + ./gradlew + buildNonMkdocs + projectHealth + mergeLintSarif + mergeDetektSarif + :plugins:buildPlugins + --continue + - name: Deploy snapshot + if: env.MAIN_BRANCH == 'true' && github.repository == 'bumble-tech/appyx' + env: + SIGNING_KEY: ${{ secrets.SIGNING_KEY }} + run: > + ./gradlew + publishAllPublicationsToSonatypeSnapshotRepository + -Psnapshot=true + --no-parallel + -Psigning.password=${{ secrets.SIGNING_PASSWORD }} + -Psonatype.username=${{ secrets.SONATYPE_USERNAME }} + -Psonatype.password=${{ secrets.SONATYPE_PASSWORD }} + - uses: github/codeql-action/upload-sarif@v3 + if: success() || failure() + with: + sarif_file: build/lint-merged.sarif + category: lint + - uses: github/codeql-action/upload-sarif@v3 + if: success() || failure() + with: + sarif_file: build/detekt-merged.sarif + category: detekt + - name: Upload failure artifacts + if: failure() + uses: actions/upload-artifact@v4 + with: + name: reports + path: | + **/build/reports/ + !**/build/reports/dependency-analysis/ + + publication-verification: + name: Publication verification + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + - uses: gradle/wrapper-validation-action@v2 + - uses: gradle/actions/setup-gradle@v3 + with: + cache-read-only: ${{ env.MAIN_BRANCH != 'true' }} + - name: Check publication setup + run: > + ./gradlew + publishAllPublicationsToOSSRHRepository + publishAllPublicationsToSonatypeSnapshotRepository + --dry-run + --no-parallel + - name: Publish locally + run: ./gradlew publishToMavenLocal -Psigning.required=false --no-parallel --continue + + instrumentation-tests: + name: Instrumentation tests + runs-on: macOS-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + - uses: gradle/wrapper-validation-action@v2 + - uses: gradle/actions/setup-gradle@v3 + with: + cache-read-only: ${{ env.MAIN_BRANCH != 'true' }} + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + - name: Package Debug Android Test + # Flags from https://github.com/android/nowinandroid/blob/main/.github/workflows/Build.yaml + run: ./gradlew packageDebugAndroidTest + - name: Instrumentation tests + # Flags from https://github.com/android/nowinandroid/blob/main/.github/workflows/Build.yaml + run: > + adb logcat > logcat.out & + ./gradlew cleanManagedDevices --unused-only && + ./gradlew uiTestsDeviceDebugAndroidTest + -Dorg.gradle.workers.max=1 + -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" + -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true + -Dorg.gradle.jvmargs=-Xmx2048m + - name: Upload failed instrumentation artifacts + if: failure() + uses: actions/upload-artifact@v4 + with: + name: instrumentation-failures + path: | + **/build/reports + logcat.out + + screenshot-tests: + name: Screenshot tests + runs-on: macOS-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + - uses: gradle/wrapper-validation-action@v2 + - uses: gradle/actions/setup-gradle@v3 + with: + cache-read-only: ${{ env.MAIN_BRANCH != 'true' }} + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + - name: Screenshot tests + # Flags from https://github.com/android/nowinandroid/blob/main/.github/workflows/Build.yaml + run: > + adb logcat > logcat.out & + ./gradlew cleanManagedDevices --unused-only && + ./gradlew screenshotTestsCompareBaseline + -Dorg.gradle.workers.max=1 + -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" + -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true + - name: Upload failed screenshot artifacts + if: failure() + uses: actions/upload-artifact@v4 + with: + name: screenshot-failures + path: | + **/build/reports + **/build/outputs/managed_device_android_test_additional_output + logcat.out diff --git a/.github/workflows/check_documentation.yml b/.github/workflows/check_documentation.yml new file mode 100644 index 000000000..0a8b7c7cb --- /dev/null +++ b/.github/workflows/check_documentation.yml @@ -0,0 +1,33 @@ +name: Check Documentation on Branch + +on: + pull_request: + branches: + - documentation + types: [opened, synchronize, reopened] + +jobs: + check-documentation: + name: Check documentation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + - uses: gradle/wrapper-validation-action@v2 + - uses: gradle/actions/setup-gradle@v3 + with: + cache-read-only: ${{ env.MAIN_BRANCH != 'true' }} + - name: Generate distributions + run: ./gradlew wasmJsBrowserDistributionMkdocs --continue + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + - run: pip install mkdocs-material + - run: pip install mkdocs-macros-plugin + - run: pip install mkdocs-redirects + - run: pip install mkdocs-include-markdown-plugin + - run: pip install pillow cairosvg + - run: mkdocs build --strict diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 000000000..bcd9c8d34 --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,31 @@ +name: Deploy mkdocs to GitHub Pages + +on: + push: + branches: + - documentation + workflow_dispatch: + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + - uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + - uses: gradle/wrapper-validation-action@v2 + - uses: gradle/actions/setup-gradle@v3 + with: + cache-read-only: ${{ env.MAIN_BRANCH != 'true' }} + - run: pip install mkdocs-material + - run: pip install mkdocs-macros-plugin + - run: pip install mkdocs-redirects + - run: pip install mkdocs-include-markdown-plugin + - run: pip install pillow cairosvg + - run: mkdocs gh-deploy --force diff --git a/documentation/apps/childaware.md b/documentation/apps/childaware.md deleted file mode 100644 index 1b5e1b2b8..000000000 --- a/documentation/apps/childaware.md +++ /dev/null @@ -1,72 +0,0 @@ -# ChildAware API - -The framework includes the `ChildAware` interface which comes with a powerful API. - -It allows you to scope communication with (or between) dynamically available child nodes easily. - -## Baseline - -In the next examples: - -1. Let's assume that `SomeNode` can host multiple child nodes: `Child1`, `Child2`, etc. -2. `SomeInteractor` belongs to `SomeNode` and is passed as a [Plugin](plugins.md) - to it -3. `SomeInteractor` extends the `Interactor` helper class from the framework: - - It implements `NodeLifecycleAware`, which makes sure it will receive the `onCreate` callback - from the framework - - It implements `ChildAware`, which unlocks the DSL we'll see in the following - snippets - -## Single child scenario - -```kotlin -import androidx.lifecycle.Lifecycle -import com.bumble.appyx.core.children.whenChildAttached -import com.bumble.appyx.core.children.whenChildrenAttached -import com.bumble.appyx.core.clienthelper.interactor.Interactor - - -class SomeInteractor : Interactor() { - - override fun onCreate(lifecycle: Lifecycle) { - lifecycle.subscribe(onCreate = { - - // This lambda is executed every time a node of type Child1Node is attached: - whenChildAttached { commonLifecycle: Lifecycle, child1: Child1Node -> - // TODO: - // - establish communication with child1 - // - use commonLifecycle for scoping - // - it will be capped by the lifecycles of child1 and the parent - } - }) - } -} -``` - - -## Multiple children - -```kotlin -import androidx.lifecycle.Lifecycle -import com.bumble.appyx.core.children.whenChildAttached -import com.bumble.appyx.core.children.whenChildrenAttached -import com.bumble.appyx.core.clienthelper.interactor.Interactor - - -class SomeInteractor : Interactor() { - - override fun onCreate(lifecycle: Lifecycle) { - lifecycle.subscribe(onCreate = { - - // This lambda is executed every time these two nodes are attached at the same time: - whenChildrenAttached { commonLifecycle: Lifecycle, child1: Child1Node, child2: Child2Node -> - // TODO - // - establish communication between child1 & child2 - // - use commonLifecycle for scoping - // - it will be capped by the lifecycles of child1, child2 and the parent - } - }) - } -} -``` - diff --git a/documentation/apps/configuration.md b/documentation/apps/configuration.md deleted file mode 100644 index 3f84d4e33..000000000 --- a/documentation/apps/configuration.md +++ /dev/null @@ -1,49 +0,0 @@ -# Configuration change - -To retain objects during configuration change you can use the `RetainedInstanceStore` class. - -## How does it work? - -The `RetainedInstanceStore` stores the objects within a singleton. The node manages whether the content should be removed by checking whether the `Activity` is being recreated due to a configuration change or not. - -These are the following scenarios: -- If the `Activity` is recreated: the retained instance is returned instead of a new instance. -- If the `Activity` is destroyed: the retained instance is removed and disposed. - -## Example - -Here is an example of how you can use the `RetainedInstanceStore`: - -```kotlin -import com.bumble.appyx.core.builder.Builder -import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.store.getRetainedInstance -import com.bumble.appyx.interop.rx2.store.getRetainedDisposable - -class RetainedInstancesBuilder : Builder() { - - override fun build(buildContext: BuildContext, payload: String): Node { - val retainedNonDisposable = buildContext.getRetainedInstance( - factory = { NonDisposableClass(payload) }, - disposer = { feature.cleanUp() } - ) - val retainedFeature = buildContext.getRetainedDisposable { - RetainedInstancesFeature(payload) - } - - val view = RetainedInstancesViewImpl() - val interactor = RetainedInstancesInteractor( - feature = retainedFeature, - nonDisposable = retainedNonDisposable, - view = view - ) - - return RetainedInstancesNode( - buildContext = buildContext, - view = view, - plugins = listOf(interactor) - ) - } -} -``` diff --git a/documentation/apps/lifecycle.md b/documentation/apps/lifecycle.md deleted file mode 100644 index 53e7800ad..000000000 --- a/documentation/apps/lifecycle.md +++ /dev/null @@ -1,45 +0,0 @@ -# Lifecycle - -Nodes have their own lifecycles, directly using the related classes of `androidx.lifecycle`. - -## Capping - -No node can be in a higher lifecycle state than any of its parents or the Android Activity it lives in. - -## On-screen & off-screen - -`NavModel` controls which children should be rendered on the screen and which should not with `NavModel.screenState`. -The behaviour is customisable in `BaseNavModel` via `OnScreenStateResolver`. - -When a `NavElement` of the node is marked as on-screen, its lifecycle follows the parent node's lifecycle. -The rendering status does not affect it – the node might not be added to Compose view and still be in a `RESUMED` state. - -When a `NavElement` of the node is marked as off-screen, the following might happen: - -- Its lifecycle is capped with `CREATED` (or `STOPPED`) in case of `ChildEntry.KeepMode.KEEP`. -- The node is destroyed and its state is saved in case of `ChildEntry.KeepMode.SUSPEND`. - -`ChildEntry.KeepMode` settings can be configured for each `ParentNode` individually or globally via `Appyx.defaultChildKeepMode`. - -When a node is removed completely from `NavModel`, it will be in `DESTROYED` state. - -## Lifecycle changes - -The lifecycle state can be affected by: - -- The NavModel of the parent (adding or removing child `Nodes` and changing their on-screen status) -- The parent's lifecycle state capping its children (transitive in the tree) -- Android lifecycle (Activity) capping the whole tree - -## Back stack node lifecycle - -An example demonstrating the above: - - - -Note that NavModels might have their slight differences (e.g. whether their operations remove a `Node` only from the view, or completely destroy it). - -In the case of the back stack: - -- The `Push` operation adds a new element and stashes the currently active one – the stashed one will be removed from the view & `STOPPED` -- The `Pop` operation removes an element, the child `Node` will be `DESTROYED` diff --git a/documentation/apps/plugins.md b/documentation/apps/plugins.md deleted file mode 100644 index 60857c81c..000000000 --- a/documentation/apps/plugins.md +++ /dev/null @@ -1,117 +0,0 @@ -# Plugins - -## Keeping extra concerns out of Node - -```Nodes``` are meant to be simple structural elements, and should be kept lean. - -To keep the framework agnostic of any specific approach / pattern you want to use, there aren't any fixed parts. Rather, the ```Node``` offers an extension point using ```Plugins``` in its constructor: - -```kotlin -abstract class Node( - buildContext: BuildContext, - val view: NodeView = EmptyNodeView, - plugins: List = emptyList() // <-- -) -``` - -So what is a ```Plugin```? - -A ```Plugin``` is an empty interface extended by many actual ones: - -```kotlin -interface Plugin - -``` - -## Plugins - -### Lifecycle-related plugins - -```kotlin -interface NodeLifecycleAware : Plugin { - fun onCreate(lifecycle: Lifecycle) {} -} - -fun interface Destroyable : Plugin { - fun destroy() -} -``` - - -### Component level plugins - -Sometimes you need to grab a reference to the component as a whole, either as an interface, or its implementation, the ```Node```. - -This will come especially handy when working with workflows. - - -```kotlin -interface NodeAware : Plugin { - val node: Node<*> - - fun init(node: Node<*>) {} -} -``` - -There are helper classes found in the library, so you don't have to implement the above interfaces, you can just use delegation: - -```kotlin -class SomeClass( - private val nodeAware: NodeAware = NodeAwareImpl() -) : NodeAware by nodeAware { - - fun foo() { - // [node] is an automatically available property coming from the NodeAware interface - // the reference is automatically set for you by the framework + the NodeAwareImpl class - // so you can use it right away: - node.doSomething() - } -} -``` - -⚠️ Note: the reference to ```node``` is set by ```Node``` automatically, and isn't available immediately after constructing your object, but only after the construction of the ```Node``` itself. - - -### Navigation plugins - -In case if you need to control navigation behaviour, you can use these plugins: - -```kotlin -interface UpNavigationHandler : Plugin { - fun handleUpNavigation(): Boolean = false -} - -interface BackPressHandler : Plugin { - val onBackPressedCallback: OnBackPressedCallback? get() = null -} -``` - -`UpNavigationHandler` controls `Node.navigateUp` behaviour and allows to intercept its invocation. - -`BackPressHandler` controls device back press behaviour via `androidx.activity.OnBackPressedCallback`. -You can read more about it [here](https://developer.android.com/guide/navigation/navigation-custom-back). - -⚠️ Note: `OnBackPressedCallback` are invoked in the following order: -1. From children to parents. Render order of children matters! The last rendered child will be the first to handle back press. -2. Direct order of plugins within a node. Plugins are invoked in order they appears in `Node(plugins = ...)` before the NavModel. - - -## Using Plugins - -All plugins are designed to have empty ```{}``` default implementations (or other sensible defaults when a return value is defined), so it's convenient to implement them only if you need. - -Don't forget to pass your ```Plugins``` to your ```Node```: - -```kotlin -internal class MyNode( - // ... - plugins: List = emptyList() - // ... -) : Node( - // ... - plugins = plugins - // ... -) -``` - -⚠️ Note: ```plugins``` is a ```List```, as the order matters here. All ```Plugin``` instances are invoked in the order they appear in the list. diff --git a/documentation/apps/structure.md b/documentation/apps/structure.md deleted file mode 100644 index f7058ea71..000000000 --- a/documentation/apps/structure.md +++ /dev/null @@ -1,67 +0,0 @@ -# Structuring your app navigation - -As seen in [Composable navigation](../navigation/composable-navigation.md), you can make `NavModels` composable. - -To achieve this, Appyx offers the `Node` class as the structural element. - - -## Node illustration - -In many of the examples you'll see this panel as an illustration of a very simple `Node` – it has some local state (id, colour, and a counter). - - - -If you launch the sample app in the `:app` module, you can also change its state (colour) by tapping it. Its counter is stepped automatically. This is to illustrate that it has its own state, persisted and restored. - - -## Node overview - -You can think of a `Node` as a standalone component with: - -- Its own simplified lifecycle -- State restoration -- A `@Composable` view -- Business logic that's kept alive even when the view isn't added to the composition -- The ability to host generic [Plugins](../apps/plugins.md) to extract extra concerns without enforcing any particular architectural pattern - - -## Parent nodes, child nodes - -`ParentNodes` can have other `Nodes` as children. This means you can represent your whole application as a tree of Appyx nodes. - - - -You can go as granular or as high-level as it fits you. This allows to keep the complexity low in individual `Nodes` by extracting responsibilities to children, as well as composing other components to build more complex functionality. - - -## Composable navigation - - - -`Nodes` offer the structure – `NavModels` add dynamism to it. - -Read more in [Composable navigation](../navigation/composable-navigation.md) - - -## Lifecycle - -Nodes have their own lifecycles, directly using the related classes of `androidx.lifecycle`. - -Read more in [Lifecycle](../apps/lifecycle.md) - - -## ChildAware API - -React to dynamically added child nodes in the tree: [ChildAware API](childaware.md) - - -## Summary - -A summary of Appyx's approach to structuring applications: - -- Compose your app out of `Nodes` with their own lifecycles and state -- Navigation is local, composed of individual pieces of `NavModels` -- Navigation is stateful -- Navigation is unit-testable -- You're free to implement your own navigable components by utilising `NavModels` -- Avoid global navigation concerns, like shared modules needing to know about the application, or the application needing to know about all its possible modules diff --git a/documentation/assets/favicon.svg b/documentation/assets/favicon.svg deleted file mode 100644 index a8351167e..000000000 --- a/documentation/assets/favicon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/documentation/assets/logo.png b/documentation/assets/logo.png deleted file mode 100644 index acccfe135..000000000 Binary files a/documentation/assets/logo.png and /dev/null differ diff --git a/documentation/faq.md b/documentation/faq.md deleted file mode 100644 index 265806d5b..000000000 --- a/documentation/faq.md +++ /dev/null @@ -1,115 +0,0 @@ -# FAQ - - -## Navigation-related - -#### **Q: How does Appyx relate to Jetpack Compose Navigation?** - -We wrote an article on this: [Appyx vs Jetpack Compose Navigation](https://medium.com/bumble-tech/appyx-vs-jetpack-compose-navigation-b91bd23369f2) - -While Appyx represents a different paradigm, it can also co-exist with Jetpack Compose Navigation. This can be helpful if you want to use Appyx for in-screen mechanisms only, or if you plan to migrate gradually. - -See [Sample apps](how-to-use-appyx/sample-apps.md) for more details. - ---- - -#### **Q: How does Appyx compare against other navigation solutions?** - -The core concepts of navigation in Appyx differ from most navigation libraries: - -1. You don't have a concept of the "screen" present in the model -2. You can define your own navigation models -3. On the UI level you can transform what feels like the "screen" itself - -See [Model-driven navigation](navigation/model-driven-navigation.md) for more details. - ---- - - -#### **Q: How can I navigate to a specific part of my Appyx tree?** - -In most cases [Implicit navigation](navigation/implicit-navigation.md) can be your primary choice, and you don't need to explicitly specify a remote point in the tree. This is helpful to avoid coupling. - -For those cases when you can't avoid it, [Explicit navigation](navigation/explicit-navigation.md) and [Deep linking](navigation/deep-linking.md) covers you. - ---- - - -#### **Q: What about dialogs & bottom sheets?** - -You can use Appyx in conjunction with Accompanist or any other Compose mechanism. - -If you wish, you can model your own Modal with Appyx too. We'll add an example soon. - ---- - -#### **Q: Can I have a bottom sheet conditionally?** - -You could use a similar approach as we do with back buttons in `SamplesContainerNode` you can find in the `:app` module: store a flag in the `NavTarget` that can be different per instance. - ---- - -## Using Appyx in an app - - -#### **Q: Is it an all or nothing approach?** - -No, you can adopt Appyx gradually: - -- Plug it in to one screen and just utilise its screen transformation capabilities (e.g. [Cards](navmodel/cards.md)) -- Plug it in to a few screens and substitute another navigation mechanism with it, such as [Jetpack Compose Navigation](how-to-use-appyx/sample-apps.md#appyx-jetpack-compose-navigation-example) - ---- - -#### **Q: What architectural patterns can I use?** - -Appyx is agnostic of architectural patterns. You can use any architectural pattern in the `Nodes` you'd like. You can even use a different one in each. - ---- - -#### **Q: Can I use it with ViewModel?** - -Yes, we'll add an example soon. - ---- - - -#### **Q: Can I use it with Hilt?** - -- Our draft PR: [#115](https://github.com/bumble-tech/appyx/pull/115) (Feel free to provide feedback!) -- [https://github.com/jbreitfeller-sfix/appyx-playground](https://github.com/jbreitfeller-sfix/appyx-playground) another approach on this topic - ---- - -## Performance-related - -#### **Q: Are `Nodes` kept alive?** - -In short: you can decide whether a `Node`: - -- is on-screen -- is off-screen but kept alive -- is off-screen and becomes destroyed - -Check the [Lifecycle](apps/lifecycle.md#on-screen-off-screen) for more details. - ---- - - -## On the project itself - -#### **Q: Is it production ready?** - -Yes, Appyx matured to its stable version. We also use it at Bumble in production, and as such, we're committed to maintaining and improving it. - ---- - -#### **Q: What's your roadmap?** - -We're full with ideas where to take Appyx further! A more detailed roadmap will be added later. Come back for more updates. - ---- - -## Other - -Have a question? Raise it in [Discussions](https://github.com/bumble-tech/appyx/discussions)!. diff --git a/documentation/how-to-use-appyx/codelabs.md b/documentation/how-to-use-appyx/codelabs.md deleted file mode 100644 index f157a0492..000000000 --- a/documentation/how-to-use-appyx/codelabs.md +++ /dev/null @@ -1,25 +0,0 @@ -# Appyx codelabs - -When you feel ready, try our [Coding challenges](coding-challenges.md) too! - - -## 1. Hello World with Appyx - -[Learn the basics of Appyx](https://bumble-tech.github.io/appyx-codelabs/appyx-hello-world) - - - - -## 2. Navigation - -[Learn how Appyx navigation works](https://bumble-tech.github.io/appyx-codelabs/appyx-navigation) - - - - -## 3. Custom animation - -[Learn how to unleash powerful transitions](https://bumble-tech.github.io/appyx-codelabs/appyx-custom-animation) - - - diff --git a/documentation/how-to-use-appyx/coding-challenges.md b/documentation/how-to-use-appyx/coding-challenges.md deleted file mode 100644 index 2b642f60c..000000000 --- a/documentation/how-to-use-appyx/coding-challenges.md +++ /dev/null @@ -1,14 +0,0 @@ -# Appyx coding challenges - -## Droidcon London 22 – Coding challenge 1 - -[Test your knowledge of the key pieces of Appyx](https://github.com/bumble-tech/dcldn22-challenge/tree/main/Challenge1) - - - - -## Droidcon London 22 – Coding challenge 2 - -[A challenge with custom animations and a new operation](https://github.com/bumble-tech/dcldn22-challenge/tree/main/Challenge2) - - diff --git a/documentation/how-to-use-appyx/quick-start.md b/documentation/how-to-use-appyx/quick-start.md deleted file mode 100644 index 260121411..000000000 --- a/documentation/how-to-use-appyx/quick-start.md +++ /dev/null @@ -1,242 +0,0 @@ -# Quick start guide - -!!! info - - - You can check out [App structure](../apps/structure.md), which explains the concepts you'll encounter in this guide. - - You can check out the project and launch the `:app` module for a quick demonstration - -!!! tip - - Once you're familiar with Appyx, you can also clone the [https://github.com/bumble-tech/appyx-starter-kit](https://github.com/bumble-tech/appyx-starter-kit) when starting a new project, instead of following this guide below. - -## Scope of this guide - -The steps below will cover: - -1. Integrating Appyx into your project -2. Creating a very simple `Node` hierarchy -3. We'll use a simple back stack for navigation -4. We'll add some simple transitions to it - -This should be enough to get you started as a rudimentary application structure. - -Tutorials & codelabs on more advanced topics & the full power of Appyx to follow soon. - - - - - -## 1. Add Appyx to your project - -You can find the related Gradle dependencies in [Downloads](../releases/downloads.md). - - -## 2. Create a root Node - -```kotlin -class RootNode( - buildContext: BuildContext -) : Node( - buildContext = buildContext -) { - @Composable - override fun View(modifier: Modifier) { - Text("Hello world!") - } -} -``` - -Since this is the root of your tree, you'll also need to plug it in to your Activity, so that system events (Android lifecycle, back press, etc.) reach your components in the tree. - -```kotlin -// Please note we are extending NodeActivity -class MainActivity : NodeActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - AppTheme { - NodeHost(integrationPoint = appyxV1IntegrationPoint) { - RootNode(buildContext = it) - } - } - } - } -} -``` - -You only need to do this for the root of the tree. - - -## 3. Define children - -A single leaf node isn't all that interesting. Let's add some children to the root! - -First, let's define the possible set of children using a sealed class. We'll refer them via these navigation targets: - -```kotlin - -/** - * You can create this class inside the body of RootNode - * - * Note: You must apply the 'kotlin-parcelize' plugin to use @Parcelize - * https://developer.android.com/kotlin/parcelize - */ -sealed class NavTarget : Parcelable { - @Parcelize - object Child1 : NavTarget() - - @Parcelize - object Child2 : NavTarget() - - @Parcelize - object Child3 : NavTarget() -} -``` - -Next, let's modify `RootNode` so it extends `ParentNode`: - -```kotlin -class RootNode( - buildContext: BuildContext -) : ParentNode( - navModel = TODO("We will come back to this in Step 4"), - buildContext = buildContext -) { -``` - -`ParentNode` expects us to implement the abstract method `resolve`. This is how we relate navigation targets to actual children. Let's use these helper methods to define some placeholders for the time being – we'll soon make them more appealing: - -```kotlin -override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node = - when (navTarget) { - NavTarget.Child1 -> node(buildContext) { Text(text = "Placeholder for child 1") } - NavTarget.Child2 -> node(buildContext) { Text(text = "Placeholder for child 2") } - NavTarget.Child3 -> node(buildContext) { Text(text = "Placeholder for child 3") } - } -``` - -Great! With this mapping created, we can now just refer to children using the sealed class elements, and Appyx will be able to relate them to other nodes. - -## 4. Add a back stack - -The project wouldn't compile just yet. `ParentNode` expects us to pass an instance of a `NavModel` – the main control structure in any case when we want to add children. No need to worry now – for simplicity, let's just go with a simple `BackStack` implementation here: - -```kotlin -class RootNode( - buildContext: BuildContext, - private val backStack: BackStack = BackStack( - initialElement = NavTarget.Child1, - savedStateMap = buildContext.savedStateMap, - ) -) : ParentNode( - navModel = backStack, // pass it here - buildContext = buildContext -) { -``` - -With this simple addition we've immediately gained a lot of power! Now we can use the back stack's API to add, replace, pop children with operations like: - -```kotlin -backStack.push(NavTarget.Child2) // will add a new navigation target to the end of the stack and make it active -backStack.replace(NavTarget.Child3) // will replace the currently active child -backStack.pop() // will remove the currently active child and restore the one before it -``` - -Since we passed the back stack to the `ParentNode`, all such changes will be immediately reflected. We only need to add it to the composition: - -```kotlin -@Composable -override fun View(modifier: Modifier) { - Column { - Text("Hello world!") - // Let's add the children to the composition - Children( - navModel = backStack - ) - - // Let's also add some controls so we can test it - Row { - TextButton(onClick = { backStack.push(NavTarget.Child1) }) { - Text(text = "Push child 1") - } - TextButton(onClick = { backStack.push(NavTarget.Child2) }) { - Text(text = "Push child 2") - } - TextButton(onClick = { backStack.push(NavTarget.Child3) }) { - Text(text = "Push child 3") - } - TextButton(onClick = { backStack.pop() }) { - Text(text = "Pop") - } - } - } -} -``` - -## 5. Add transitions - -Adding some transitions is a one-liner: - -```kotlin -Children( - navModel = backStack, - transitionHandler = rememberBackstackSlider() -) -``` - -You can also use a fader instead: ```rememberBackstackFader()```, and you can supply a transition spec in both cases: ```rememberBackStackSlider { spring(stiffness = Spring.StiffnessLow) }``` - -Need something more custom? - -1. Instead of a back stack, you can find other [NavModels](../navmodel) in the library, or you can [implement your own](../navmodel/custom.md) -2. Instead of the default transition handlers, you can also [use Jetpack Compose provided ones, or supply your own](../ui/transitions.md) - -You can also read the [Back stack documentation](../navmodel/backstack.md) for more info on the specific options for the back stack. - -## 6. Proper child nodes - -As a last step, let's replace at least one of the child placeholders with another proper node. - -Let's create a dedicated class: - -```kotlin -class SomeChildNode( - buildContext: BuildContext -) : Node( - buildContext = buildContext -) { - @Composable - override fun View(modifier: Modifier) { - Text("This is SomeChildNode") - } -} -``` - -Now we can update the `resolve` method in `RootNode` so that the target `Child3` refers to this node. It should work out of the box: - -```kotlin -override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node = - when (navTarget) { - NavTarget.Child1 -> node(buildContext) { Text(text = "Placeholder for child 1") } - NavTarget.Child2 -> node(buildContext) { Text(text = "Placeholder for child 2") } - NavTarget.Child3 -> SomeChildNode(buildContext) - } -``` - -## What's next? - -Congrats, you've just built your first Appyx tree! - -You can repeat the same pattern and make any embedded children also a `ParentNode` with their own children, navigation models, and transitions. As complexity grows, generally you would: - -1. Have a `Node` -2. At some point make it a `ParentNode` and add children to it -3. At some point extract the increasing complexity from a placeholder to another `Node` -4. Repeat the same on children, go to `1.` - - -## Further reading - -- Check out [Model-driven navigation](../navigation/model-driven-navigation.md) how to take your navigation to the next level -- You can (and probably should) also extract local business logic, the view, any any other components into separate classes and [Plugins](../apps/plugins.md). diff --git a/documentation/how-to-use-appyx/sample-apps.md b/documentation/how-to-use-appyx/sample-apps.md deleted file mode 100644 index 3b7725bf0..000000000 --- a/documentation/how-to-use-appyx/sample-apps.md +++ /dev/null @@ -1,30 +0,0 @@ -# Appyx sample apps - -## Where to find the sample apps - -1. Go to the [GitHub project](https://github.com/bumble-tech/appyx) -2. Fork and check out the code locally -3. Import the project to Android Studio - -You can find the pre-built sample app apks here: - -- [Latest release](https://github.com/bumble-tech/appyx/releases) -- [Latest 1.x](https://github.com/bumble-tech/appyx/actions/runs/${POST_MERGE_RUN_ID}#artifacts) - -## Showcase app - -The `:app` module showcases **Appyx** itself with multiple levels of navigation, NavModel demos, etc. See it in action, then check the related code how it works. - -## Appyx + Jetpack Compose Navigation example - -The `:samples:navigation-compose` module demonstrates how to use Appyx within Google's Jetpack Compose Navigation library. -This example may be useful if you need to migrate to Appyx gradually. - -## Appyx + Hilt example - -Coming soon! - -Meanwhile: - -- Our draft PR: [#115](https://github.com/bumble-tech/appyx/pull/115) (Feel free to provide feedback!) -- [https://github.com/jbreitfeller-sfix/appyx-playground](https://github.com/jbreitfeller-sfix/appyx-playground) another approach on this topic diff --git a/documentation/index.md b/documentation/index.md deleted file mode 100644 index 00169401b..000000000 --- a/documentation/index.md +++ /dev/null @@ -1,76 +0,0 @@ -# Appyx - - -Model-driven navigation for Jetpack Compose - -[https://github.com/bumble-tech/appyx](https://github.com/bumble-tech/appyx) - - -## Navigation for your Compose app on steroids - -- **Navigate directly from code** – In a type-safe way, without boilerplate -- **Gain control of navigation state** – Making your navigation unit-testable -- **Complete control over operations and behaviour** – Use and extend the back stack or the view pager from the library, or build your own -- **Your own navigation** – With Appyx, you can define your own navigation models -- **Use any animation for transitions** – Anything you can represent with Compose `Modifiers` - - -## Model-driven navigation – a different paradigm with superpowers - -Using Appyx you gain navigation superpowers in addition to screen-to-screen navigation: - -- **Break down screen boundaries** – Using `NavModels` you can navigate inside the screen as well as between them -- **Make your navigation composable** – Navigate whole scopes of your application - -Using a model-driven approach, navigation states are yours to define – Appyx makes it happen with any animation you can represent using Compose `Modifiers`. Back stacks, card stacks, view pagers are just the beginning: - - - - -You can create custom navigable components in no time: - - - - -With Appyx you can break down screen boundaries and transform the screen itself: - - - -See [Model-driven navigation](navigation/model-driven-navigation.md) for more details. - - -## Launch the demo app - -Check out the project and launch the `:app` module for a quick demonstration! - - -## Download - -See the [Downloads page](releases/downloads.md). - - -## Learning Appyx - -Check out the [Quick start guide](how-to-use-appyx/quick-start.md), [Codelabs](how-to-use-appyx/codelabs.md), and [Sample apps](how-to-use-appyx/sample-apps.md)! - - -## Articles - -1. [Appyx is released! (1.0-alpha02)](https://medium.com/bumble-tech/appyx-is-released-1-0-alpha02-41a27ad3b0cd) -2. [Appyx vs Jetpack Compose Navigation](https://medium.com/bumble-tech/appyx-vs-jetpack-compose-navigation-b91bd23369f2) -3. [Modelling dating cards navigation with Appyx](https://medium.com/bumble-tech/modelling-dating-cards-navigation-with-appyx-ab68313d27f6) - -## Videos - - [Model-driven navigation with Jetpack Compose](https://www.droidcon.com/2022/09/29/model-driven-navigation-with-jetpack-compose-from-zero-to-hero/) - - [Model-driven navigation with Appyx](https://www.droidcon.com/2022/11/15/model-driven-navigation-with-appyx-from-zero-to-hero/) - -## News & updates - -See our [News & updates](news.md) page - -You can also follow some of us tweeting about Appyx on these accounts: - -- [@ZsoltKocsi](https://twitter.com/ZsoltKocsi) -- [@andreyk_nn](https://twitter.com/andreyk_nn) diff --git a/documentation/navigation/composable-navigation.md b/documentation/navigation/composable-navigation.md deleted file mode 100644 index 3dd483d72..000000000 --- a/documentation/navigation/composable-navigation.md +++ /dev/null @@ -1,49 +0,0 @@ -# Composable navigation - -[NavModels](../navmodel/index.md) in Appyx are composable. - -As a single `NavModel` won't be enough for the whole of your whole app, you can use many in a composable way. That is, any navigation target of a `NavModel` can also host its own `NavModel`. - - -## Structural element for composing navigation - -```Nodes``` are the main structural element in Appyx. They can host `NavModels`, and they form a tree. - -This allows you to make your app's business logic also composable by leveraging `Nodes` as lifecycled components. - -Read more in [Structuring your app navigation](../apps/structure.md) - - -## Navigation in the tree - - - -Once you've structured your navigation in a composable way, you can add `NavModels` to `Node` of this tree and make it dynamic: - -- Some parts in this tree are active while others ore not -- The active parts define what state the application is in, and what the user sees on the screen -- We can change what's active by using `NavModels` on each level of the tree -- Changes will feel like navigation to the user - -See [Implicit navigation](implicit-navigation.md) and [Explicit navigation](explicit-navigation.md) for building complex navigation behaviours with this approach. - - - -## How NavModels affect Nodes - -NavModel operations will typically result in: - -- Adding or removing child `Nodes` of a `ParentNode` -- Move them on and off the screen -- Change their states - -As an illustration: - - - -Here: - -- `Back stack` illustrates adding and removing child `Nodes` -- `Tiles` illustrates changing the state of children and removing them from the `ParentNode` - -These are just two examples, you're of course not limited to using them. diff --git a/documentation/navigation/deep-linking.md b/documentation/navigation/deep-linking.md deleted file mode 100644 index 648976e7c..000000000 --- a/documentation/navigation/deep-linking.md +++ /dev/null @@ -1,44 +0,0 @@ -# Deep linking - -Building on top of [explicit navigation](explicit-navigation.md), implementing deep links is straightforward: - -```kotlin -class ExplicitNavigationExampleActivity : NodeActivity(), Navigator { - - lateinit var rootNode: RootNode - - fun handleDeepLink(intent: Intent) { - if (intent.action == Intent.ACTION_VIEW) { - when { - (it.data?.host == "onboarding") -> navigateToOnBoarding() - else -> Unit - } - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - NodeHost(integrationPoint = appyxV1IntegrationPoint) { - RootNode( - buildContext = it, - plugins = listOf(object : NodeReadyObserver { - override fun init(node: RootNode) { - rootNode = node - handleDeepLink(intent = intent) - } - }) - ) - } - } - } - - private fun navigateToOnBoarding() { - // implement explicit navigation - } -} -``` - -Check `ExplicitNavigationExampleActivity` in the samples to inspect the full code. - - diff --git a/documentation/navigation/explicit-navigation.md b/documentation/navigation/explicit-navigation.md deleted file mode 100644 index 4439d4733..000000000 --- a/documentation/navigation/explicit-navigation.md +++ /dev/null @@ -1,209 +0,0 @@ -# Explicit navigation - -When [Implicit navigation](implicit-navigation.md) doesn't fit your use case, you can try an explicit approach. - -!!! info "Relevant methods" - - - ParentNode.attachChild() - - ParentNode.waitForChildAttached() - -Using these methods we can chain together a path which leads from the root of the tree to a specific `Node`. - -## Use case - -We want to navigate from `Chat` - - - -to onboarding's first screen `O1`: - - - -This time we'll want to do this explicitly by calling a function. - - -## The plan - -1. Create a public method on `Root` that attaches `Onboarding` -2. Create a public method on `Onboarding` that attaches the first onboarding screen -3. Create a `Navigator`, that starting from an instance of `Root`, can chain these public methods together into a single action: `navigateToO1()` -4. Capture an instance of `Root` to use with `Navigator` -5. Call `navigateToO1()` on our `Navigator` instance - - -## Step 1 – `Root` → `Onboarding` - -First, we need to define how to programmatically attach `Onboarding` to the `Root`: - -```kotlin -class RootNode( - buildContext: BuildContext, - backStack: BackStack -) : ParentNode( - buildContext = buildContext, - navModel = backStack, -) { - - suspend fun attachOnboarding(): OnboardingNode { - return attachChild { - backStack.replace(NavTarget.Onboarding) - } - } -} -``` - -Let's break down what happens here: - -1. Since `attachChild` has a generic `` return type, it will conform to the defined `OnboardingNode` type -2. However, `attachChild` doesn't know how to create navigation to `OnboardingNode` – that's something only we can do with the provided lambda -3. We replace `NavTarget.Onboarding` into the back stack -4. Doing this _should_ result in `OnboardingNode` being created and added to `RootNode` as a child -5. `attachChild` expects an instance of `OnboardingNode` to appear as a child of `Root` as a consequence of executing our lambda -6. Once it appears, `attachChild` returns it - - -!!! info "Important" - - It's our responsibility to make sure that the provided lambda actually results in the expected child being added. If we accidentally do something else instead, for example: - - ```kotlin - suspend fun attachOnboarding(): OnboardingNode { - return attachChild { - backStack.replace(NavTarget.Main) // Wrong NavTarget - } - } - ``` - - Then an exception will be thrown after a timeout. - - -## Step 2 – `Onboarding` → `O1` - -Unlike `Root`, `Onboarding` uses [Spotlight](../navmodel/spotlight.md) instead of [BackStack](../navmodel/backstack.md) as a `NavModel`, so navigation to the first screen is slightly different: - -```kotlin -class OnboardingNode( - buildContext: BuildContext, - spotlight: Spotlight -) : ParentNode( - buildContext = buildContext, - navModel = spotlight, -) { - - suspend fun attachO1(): O1Node { - return attachChild { - spotlight.activate(index = 0) - } - } -} -``` - - -## Step 3 – Our `Navigator` - -```kotlin -interface Navigator { - fun navigateToO1() -} -``` - -In this case we'll implement it directly with our activity: - -```kotlin -class ExplicitNavigationExampleActivity : NodeActivity(), Navigator { - - lateinit var rootNode: RootNode // See the next step - - override fun navigateToO1() { - lifecycleScope.launch { - rootNode - .attachOnboarding() - .attachO1() - } - } -} -``` - -## Step 4 – An instance of `RootNode` - -As the last piece of the puzzle, we'll also need to capture the instance of `RootNode` to make it all work. We can do that by a `NodeReadyObserver` plugin when setting up our tree: - - -```kotlin -class ExplicitNavigationExampleActivity : NodeActivity(), Navigator { - - lateinit var rootNode: RootNode - - override fun navigateToO1() { /*...*/ } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - NodeHost(integrationPoint = appyxV1IntegrationPoint) { - RootNode( - buildContext = it, - navigator = this@ExplicitNavigationExampleActivity, - plugins = listOf(object : NodeReadyObserver { - override fun init(node: RootNode) { - rootNode = node - } - }) - ) - } - } - } -} -``` - -## Step 5 – Using the `Navigator` - -See how in the previous snippet `RootNode` receives a `navigator` dependency. - -It can pass it further down the tree as a dependency to other nodes. Those nodes can call the methods of the `Navigator`, which will change the global navigation state directly. - - ---- - -## Bonus: Wait for a child to be attached - -There might be cases when we want to wait for a certain action to be _performed by the user_, rather than us, to result in a child being attached. - -In these cases we can use `ParentNode.waitForChildAttached()` instead. - - -### Use case – Wait for login - -A typical case building an explicit navigation chain that relies on `Logged in` being attached. Most probably `Logged in` has a dependency on some kind of a `User` object. Here we want to wait for the user to authenticate themselves, rather than creating a dummy user object ourselves. - - -```kotlin -class RootNode( - buildContext: BuildContext, -) : ParentNode( - buildContext = buildContext -) { - - suspend fun waitForLoggedIn(): LoggedInNode = - waitForChildAttached() -} -``` - -This method will wait for `LoggedInNode` to appear in the child list of `RootNode` and return with it. If it's already there, it returns immediately. - -A navigation chain using it could look like: - -```kotlin -class ExplicitNavigationExampleActivity : NodeActivity(), Navigator { - - override fun navigateToProfile() { - lifecycleScope.launch { - rootNode - .waitForLoggedIn() - .attachMain() - .attachProfile() - } - } -} -``` - -You can find related code examples in `ExplicitNavigationExampleActivity` in our samples. diff --git a/documentation/navigation/implicit-navigation.md b/documentation/navigation/implicit-navigation.md deleted file mode 100644 index f10f38892..000000000 --- a/documentation/navigation/implicit-navigation.md +++ /dev/null @@ -1,61 +0,0 @@ -# Implicit navigation - - - -How can we go from one part of the tree to another? In almost all cases navigation can be implicit instead of explicit. We don't need to specify the target – navigation will happen as a consequence of individual pieces of the puzzle. - -!!! info "Relevant methods" - - - `ParentNode.onChildFinished(child: Node)` can be overridden by client code to handle a child finishing - - `Node.finish()` invokes the above method on its parent - - -## Use-case 1 - - - -### Requirement - -After onboarding finishes, the user should land in the message list screen. - -### Solution - -1. `O3` calls its `finish()` method -2. `Onboarding` notices `O3` finished; if it had more children, it could switch to another; now it calls `finish()` too -3. `Logged in` notices `Onboarding` finished, and switches its navigation to `Main` -4. `Main` is initialised, and loads its default navigation target (based on product requirements) to be `Messages` -5. `Messages` is initialised, and loads its default navigation target to be `List` - -!!! success "Bonus" - - Every `Node` in the above sequence only needed to care about its own local concern. - - -## Use-case 2 - - - -### Requirement - -Pressing the logout button on the profile screen should land us back to the login screen. - -### Solution - -1. `Root` either implements a `logout` callback, or subscribes to the changes of a user repository; in both cases, either the callback or the repository is passed down the tree as a dependency -2. `Profile` invokes the callback or a `logout` method on the repository -3. `Root` notices the state change, and switches its navigation to the `Logged out` scope -4. `Logged out` loads its initial navigation target, `Login` - -!!! success "Bonus" - - Note how the entire `Logged in` scope is destroyed without any extra effort. The next time a login happens, all state is created anew. - - -## Summary - -Implicit navigation allows you to implement navigation without introducing unnecessary coupling in the tree, and successfully covers the majority of navigation scenarios. - -In case it's not enough to meet your needs, see the next chapter, [Explicit navigation](explicit-navigation.md) - - - diff --git a/documentation/navigation/model-driven-navigation.md b/documentation/navigation/model-driven-navigation.md deleted file mode 100644 index a2931bcf7..000000000 --- a/documentation/navigation/model-driven-navigation.md +++ /dev/null @@ -1,41 +0,0 @@ -# Model-driven navigation - - -## Your own navigation model - -Generally speaking, most navigation solutions have fixed navigation mechanisms (e.g. a back stack). - -Appyx gives you the freedom to define your own navigation model. For example, you can implement any of the examples you see here with the same approach: - - - - -## No screen, only a viewport - -Generally speaking, most navigation solutions model a "Screen" and focus on how to get from one screen to another. - -Appyx does not have the concept of the screen in its model – there's only a viewport, and whatever fills the available space will feel like the screen to the user. - -This freedom allows you to implement: - -- navigation that feels like going from "screen to screen" -- navigation "inside the screen" -- navigation that bridges between the two - -For example, you can transform the screen itself as part of navigation: - - - - -## NavModels - -A `NavModel` implements any of the above mechanisms. - -See [NavModels](../navmodel/index.md) for more details. - - -## Composable navigation - -`NavModels` in Appyx are composable. - -See [Composable navigation](composable-navigation.md) for more details. diff --git a/documentation/navmodel/backstack.md b/documentation/navmodel/backstack.md deleted file mode 100644 index a63bba753..000000000 --- a/documentation/navmodel/backstack.md +++ /dev/null @@ -1,172 +0,0 @@ -# Back stack - - - -Implements a simple linear history: - -- The last element at the end of the stack is considered "active". -- All other elements are considered stashed. -- Children associated with stashed elements are off the screen but kept alive (see how the counter values reflect this on the video) - -The back stack can never be empty – it always contains at least one element. - -The back stack also supports different back press and operation strategies (see further down below). - - -## States - -```kotlin -enum class State { - CREATED, ACTIVE, STASHED, DESTROYED, -} -``` - -## Visualisation of states - - - -Check out the apps in our [Coding challenges](../how-to-use-appyx/coding-challenges.md) – they have an embedded visualisation of what happens to all the elements inside the back stack (look at the row of orange boxes below the logo). - - -## Constructing the back stack - -As the back stack can never be empty, it's required to define an initial target. - -```kotlin -class BackStack( - initialElement: NavTarget, - savedStateMap: SavedStateMap?, - // Optional parameters are omitted -) -``` - -## Default on screen resolution - -As a default, only the active element is considered on screen. - -```kotlin -object BackStackOnScreenResolver : OnScreenStateResolver { - override fun isOnScreen(state: State): Boolean = - when (state) { - State.CREATED, - State.STASHED, - State.DESTROYED -> false - State.ACTIVE -> true - } -} -``` - -## Default transition handlers - -#### BackStackFader - -`rememberBackstackFader()` - -Adds simple cross-fading transitions - - -#### BackStackSlider - -`rememberBackstackSlider()` - -Adds horizontal sliding transitions so that the `ACTIVE` element is in the center; other states are animated from / to the left or the right edge of the screen. - - -## Operations - -#### Push - -`backStack.push(navTarget)` - -Effect on stack: -``` -[A, B, C] + Push(D) = [A, B, C, D] -``` - -Transitions the active element `ACTIVE` -> `STASHED`. -Adds a new element at the end of the stack with a `CREATED` -> `ACTIVE` transition. - - -#### Replace - -`backStack.replace(navTarget)` - -Effect on stack: -``` -[A, B, C] + Replace(D) = [A, B, D] -``` - -Transitions the active element `ACTIVE` -> `DESTROYED`, which will be removed when the transition finishes. -Adds a new element at the end of the stack with a `CREATED` -> `ACTIVE` transition. - - -#### Pop - -`backStack.pop(navTarget)` - -Effect on stack: -``` -[A, B, C] + Pop = [A, B] -``` - -Transitions the active element `ACTIVE` -> `DESTROYED`, which will be removed when the transition finishes. -Transitions the last stashed element `STASHED` -> `ACTIVE`. - - -#### Single top - -`backStack.singleTop(navTarget)` - -Effect on stack: depends on the contents of the stack: - -``` -[A, B, C, D] + SingleTop(B) = [A, B] // of same type and equals, acts as n * Pop -[A, B, C, D] + SingleTop(B') = [A, B'] // of same type but not equals, acts as n * Pop + Replace -[A, B, C, D] + SingleTop(E) = [A, B, C, D, E] // not found, acts as Push -``` - - -## Back press strategy - -You can override the default strategy in the constructor. You're not limited to using the provided classes, feel free to implement your own. - -```kotlin -class BackStack( - /* ... */ - backPressHandler: BackPressHandlerStrategy = PopBackPressHandler(), - /* ... */ -) -``` - -#### PopBackPressHandler - -The default back press handling strategy. Runs a `Pop` operation. - -#### DontHandleBackPress - -Serves as a no-op. - - -## Operation strategy - -You can override the default strategy in the constructor. You're not limited to using the provided classes, feel free to implement your own. - -```kotlin -class BackStack( - /* ... */ - operationStrategy: OperationStrategy = ExecuteImmediately(), - /* ... */ -) -``` - -#### ExecuteImmediately -The default strategy. New operations are executed without any questions, regardless of any already running transitions. - -#### FinishTransitionsOnNewOperation -All running transitions are abruptly finished when a new one is started - -#### QueueOperations -The new operation is queued and executed after the current one finishes - -#### IgnoreIfThereAreUnfinishedTransitions -Runs the new one only if there are no transitions happening currently; ignore and discard it otherwise diff --git a/documentation/navmodel/cards.md b/documentation/navmodel/cards.md deleted file mode 100644 index ce2d8f8b6..000000000 --- a/documentation/navmodel/cards.md +++ /dev/null @@ -1,88 +0,0 @@ -# Cards - - - -Implements a dating-cards-like mechanism. - -Intended only as an illustration, but it should be easy enough to tailor it to your needs if you find it useful. - -## Where can I find this NavModel? - -The `Cards` NavModel is not currently published, however you can try it in `:samples:app`. Launch the sample app and check the `Dating cards NavModel` item to see it in action. - - - -## States - -```kotlin -sealed class State { - data class Queued(val queueNumber: Int) : State() - object Bottom : State() - object Top : State() - object IndicateLike : State() - object IndicatePass : State() - object VoteLike : State() - object VotePass : State() -} -``` - -## State transitions - - - -## Constructing `Cards` - -Requires defining items that will be converted to profile cards. The first one in the list will become a `Top` card, the second one a `Bottom` card, the rest will be `Queued`. - -```kotlin -class Cards( - initialItems: List = listOf(), -) : BaseNavModel( - screenResolver = CardsOnScreenResolver, - finalStates = FINAL_STATES, - savedStateMap = null -) { - companion object { - internal val FINAL_STATES = setOf(VoteLike, VotePass) - internal val TOP_STATES = setOf(Top, IndicateLike, IndicatePass) - } -} -``` - -## Default on screen resolution - -```kotlin -internal object CardsOnScreenResolver : OnScreenStateResolver { - override fun isOnScreen(state: State): Boolean = - when (state) { - is State.Bottom, - is State.Top, - is State.IndicateLike, - is State.IndicatePass -> true - is State.Queued, - is State.VoteLike, - is State.VotePass -> false - } -} -``` - -## Default transition handlers - -#### CardsTransitionHandler - -`rememberCardsTransitionHandler()` - -Adds scale-up, swipe and rotation animations. - - -## Operations - -#### PromoteAll - -Internal operation. Automatically invoked whenever a top card is moved to a vote-related state. Causes all other cards to come forward in the queue, become the `Bottom` card, then the `Top` card. - - -#### IndicateLike, IndicatePass, VoteLike, VotePass - -Transitions the `Top` card directly to these states. - diff --git a/documentation/navmodel/custom.md b/documentation/navmodel/custom.md deleted file mode 100644 index 40be939ae..000000000 --- a/documentation/navmodel/custom.md +++ /dev/null @@ -1,167 +0,0 @@ -# Implementing your own navigation models - -A step-by-step guide. You can also take a look at other existing examples to see these in practice. - -## Step 1 - -Create the class; define your possible states; define your initial state. - -```kotlin -class Foo( - initialItems: List = listOf(), - savedStateMap: SavedStateMap? -) : BaseNavModel( - screenResolver = FooOnScreenResolver, // We'll see about this shortly - finalState = DESTROYED, // Anything transitioning towards this state will be discarded eventually - savedStateMap = savedStateMap // It's nullable if you don't need state restoration -) { - - // Your possible states for any single navigation target - enum class State { - CREATED, FOO, BAR, BAZ, DESTROYED; - } - - // You can go about it any other way. - // Back stack for example defines only a single element. - // Here we take all the elements and make them transition CREATED -> FOO immediately. - override val initialElements = initialItems.map { - FooElement( - key = NavKey(it), - fromState = State.CREATED, - targetState = State.FOO, - operation = Operation.Noop() - ) - } -} -``` - -## (optional) Step 2 - -Add some convenience aliases: - -```kotlin -typealias FooElement = NavElement - -typealias FooElements = NavElements - -sealed interface FooOperation : Operation -``` - - -## Step 3 - -Define one or more operations. - -```kotlin -@Parcelize -class SomeOperation : FooOperation { - - override fun isApplicable(elements: FooElements): Boolean = - TODO("Define whether this operation is applicable given the current state") - - override fun invoke( - elements: FooElements, - ): NavElements = - // TODO: Mutate elements however you please. Add, remove, change. - // In this example we're changing all elements to transition to BAR. - // You can also use helper methods elements.transitionTo & elements.transitionToIndexed - elements.map { - it.transitionTo( - newTargetState = BAR, - operation = this - ) - } -} - -// You can add an extension method for a leaner API -fun Foo.someOperation() { - accept(FooOperation()) -} -``` - -## Step 4 - -Add the screen resolver to define which states should be / should not be part of the composition in the end: - -```kotlin -object FooOnScreenResolver : OnScreenStateResolver { - override fun isOnScreen(state: State): Boolean = - when (state) { - Foo.State.CREATED, - Foo.State.DESTROYED -> false - Foo.State.FOO, - Foo.State.BAR, - Foo.State.BAZ, -> true - } -} -``` - -## Step 5 - -Add one or more transition handlers to interpret different states and translate them to Jetpack Compose `Modifiers`. - -```kotlin -class FooTransitionHandler( - private val transitionSpec: TransitionSpec = { spring() } -) : ModifierTransitionHandler() { - - // TODO define a Modifier depending on the state. - // Here we'll just mutate scaling: - override fun createModifier( - modifier: Modifier, - transition: Transition, - descriptor: TransitionDescriptor - ): Modifier = modifier.composed { - val scale = transition.animateFloat( - transitionSpec = transitionSpec, - targetValueByState = { - when (it) { - Foo.State.CREATED -> 0f - Foo.State.FOO -> 0.33f - Foo.State.BAR -> 0.66f - Foo.State.BAZ -> 1.0f - Foo.State.DESTROYED -> 0f - } - }) - - scale(scale.value) - } -} - -// TODO remember to add: -@Composable -fun rememberFooTransitionHandler( - transitionSpec: TransitionSpec = { spring() } -): ModifierTransitionHandler = remember { - FooTransitionHandler(transitionSpec) -} -``` - - -## Test it - -Add `Children` to your `Node`. Pass your NavModel and the transition handler: - -```kotlin -@Composable -override fun View(modifier: Modifier) { - Children( - modifier = Modifier.fillMaxSize(), - navModel = foo, - transitionHandler = rememberFooTransitionHandler() - ) -} -``` - -Somewhere else in your business logic trigger the operations you defined. Make sure they're called on the same `foo` instance that you pass to the `Children` composable: - -```kotlin -foo.someOperation() -``` - -As soon as this is triggered, elements should transition to the `BAR` state in this example, and you should see them scale up defined by the transition handler. - - -## Created something cool? - -Let us know! diff --git a/documentation/navmodel/index.md b/documentation/navmodel/index.md deleted file mode 100644 index 57c314416..000000000 --- a/documentation/navmodel/index.md +++ /dev/null @@ -1,34 +0,0 @@ -# Navigation models - -Navigation model is a core concept of Appyx. - - -## What's a NavModel? - -Navigation models describe navigation itself – by the states and operations they define, any custom navigation mechanism can be implemented. - -`NavModel` capabilities differ across implementations, however, typically: - -1. They store information on the states of all children -2. They behave like a state machine -3. They offer some public API to trigger changing the state of children - - -## Some examples of navigation models - -You can take a look at some of these examples: - -1. [Back stack](backstack.md) -2. [Spotlight](spotlight.md) -3. [Tiles](tiles.md) -4. [Promoter carousel](promoter.md) - -When you feel ready, you can try to [implement your own NavModel](custom.md). - - -## What does a NavModel not do? - -The `NavModel` represents only the model, not the looks: - -- UI representation depends on your `@Composable` view hosting the children – See [Adding children to the view](../ui/children-view.md) -- [Transition animations](../ui/transitions.md) (if any) is a separate concern diff --git a/documentation/navmodel/promoter.md b/documentation/navmodel/promoter.md deleted file mode 100644 index 78b5fe8a3..000000000 --- a/documentation/navmodel/promoter.md +++ /dev/null @@ -1,65 +0,0 @@ -# Promoter carousel - - - -Intended only as an illustration. - -## Where can I find this NavModel? - -The `Promoter` NavModel is not currently published, however you can fork the Appyx repository and try it out yourself! -If you feel that this functionality should be part of the main library, please let us know. - -## States - -```kotlin -enum class State { - CREATED, STAGE1, STAGE2, STAGE3, STAGE4, SELECTED, DESTROYED -} -``` - -## Default on screen resolution - -```kotlin -internal object PromoterOnScreenResolver : OnScreenStateResolver { - override fun isOnScreen(state: State): Boolean = - when (state) { - State.DESTROYED -> false - else -> true - } -} - -``` - -## Default transition handler - -As elements are promoted to next stages, they're: - -- animated on a circular path -- scaled up -- rotated in the selection / discard stages - -You can check `PromoterTransitionHandler` for implementation details. - - -## Operations - -#### Add first - -`promoter.addFirst(navTarget)` - -Adds a new element at the start of the element list with a `CREATED` state. - - -#### Promote all - -`promoter.promoteAll()` - -All elements are transitioned to the next state: - -- `CREATED` -> `STAGE1` -- `STAGE1` -> `STAGE2` -- `STAGE2` -> `STAGE3` -- `STAGE3` -> `STAGE4` -- `STAGE4` -> `SELECTED` -- `SELECTED` -> `DESTROYED` - diff --git a/documentation/navmodel/spotlight.md b/documentation/navmodel/spotlight.md deleted file mode 100644 index 27ebc105e..000000000 --- a/documentation/navmodel/spotlight.md +++ /dev/null @@ -1,129 +0,0 @@ -# Spotlight - - - -Implements a mechanism analogous to a view pager; has a single active element ("it's in the spotlight", hence the name), but unlike the back stack, it does not remove other elements. - -It's great for flows or tabbed containers. - -## States - -```kotlin -enum class State { - INACTIVE_BEFORE, ACTIVE, INACTIVE_AFTER; -} -``` - -## Constructing spotlight - -Requires defining items and an active index. - -```kotlin -class Spotlight( - items: List, - initialActiveIndex: Int = 0, - savedStateMap: SavedStateMap?, - // Optional parameters are omitted -) -``` - -## Default on screen resolution - -As a default, only the active element is considered on screen. - -```kotlin -object SpotlightOnScreenResolver : OnScreenStateResolver { - override fun isOnScreen(state: Spotlight.State): Boolean = - when (state) { - Spotlight.State.INACTIVE_BEFORE, - Spotlight.State.INACTIVE_AFTER -> false - Spotlight.State.ACTIVE -> true - } -} -``` - -## Default transition handlers - -#### SpotlightFader - -`rememberSpotlightFader()` - -Adds simple cross-fading transitions - - -#### SpotlightSlider - -`rememberSpotlightSlider()` - -Adds horizontal sliding transitions so that the `ACTIVE` element is in the center; other states are animated from / to the left or the right edge of the screen, depending on the order of them in the `items` property. - - -## Operations - -#### Activate - -`spotlight.activate(navTarget)` - -Transitions the element to `ACTIVE`. Transitions other elements to `INACTIVE_BEFORE` or `INACTIVE_AFTER` depending on their relative position to the activated element. - - -#### Next - -`spotlight.next()` - -Transitions the currently active element to `INACTIVE_BEFORE`. -Transitions the element after the currently active one to `ACTIVE`. - - -#### Previous - -`spotlight.previous()` - -Transitions the currently active element to `INACTIVE_AFTER`. -Transitions the element before the currently active one to `ACTIVE`. - - -#### Update elements - -`spotlight.updateElements(items, activeIndex)` - -Replaces elements held by the spotlight instance with a new list. Transitions new elements to `INACTIVE_BEFORE`, `ACTIVE`, or `INACTIVE_AFTER` depending on their position in the provided list relative to `activeIndex`. - - -## Back press strategy - -You can override the default strategy in the constructor. You're not limited to using the provided classes, feel free to implement your own. - -```kotlin -class Spotlight( - /* ... */ - backPressHandler: BackPressHandlerStrategy = GoToDefault( - initialActiveIndex - ) - /* ... */ -) -``` - -#### GoToDefault - -The default back press handling strategy. Activates the default index. - -#### GoToPrevious - -Runs a `Previous` operation. - - -## Operation strategy - -You can override the default strategy in the constructor. You're not limited to using the provided classes, feel free to implement your own. - -```kotlin -class Spotlight( - /* ... */ - operationStrategy: OperationStrategy = ExecuteImmediately(), - /* ... */ -) -``` - -#### ExecuteImmediately -The default strategy. New operations are executed without any questions, regardless of any already running transitions. diff --git a/documentation/navmodel/tiles.md b/documentation/navmodel/tiles.md deleted file mode 100644 index 6f98aec44..000000000 --- a/documentation/navmodel/tiles.md +++ /dev/null @@ -1,82 +0,0 @@ -# Tiles - - - -Intended only as an illustration, but it should be easy enough to tailor it to your needs if you find it useful. - -## Where can I find this NavModel? - -The `Tiles` NavModel is not currently published, however you can fork the Appyx repository and try it out yourself! -If you feel that this functionality should be part of the main library, please let us know. - -## States - -```kotlin -enum class State { - CREATED, STANDARD, SELECTED, DESTROYED -} -``` - -## Default on screen resolution - -```kotlin -internal object TilesOnScreenResolver : OnScreenStateResolver { - override fun isOnScreen(state: State): Boolean = - when (state) { - State.CREATED, - State.STANDARD, - State.SELECTED -> true - State.DESTROYED -> false - } -} -``` - -## Default transition handler - -Selection translates to scaling. -Destroying makes elements fly off the screen with rotation and downscaling. - - -## Operations - -#### Add - -`tiles.add(navTarget)` - -Adds a new element to the NavModel immediately transitioning from `CREATED` -> `STANDARD`. - - -#### Destroy - -`tiles.destroy(navTarget)` - -Transitions a given element to `DESTROYED`. - - -#### Select - -`tiles.select(navTarget)` - -Transitions a given element `STANDARD` -> `SELECTED`. - - -#### Deselect - -`tiles.deselect(navTarget)` - -Transitions a given element `SELECTED` -> `STANDARD`. - - -#### Deselect all - -`tiles.deselectAll()` - -Transitions all elements `SELECTED` -> `STANDARD`. - - - -#### Remove selected - -`tiles.removeSelected()` - -Transitions all elements that have `SELECTED` state to `DESTROYED`. diff --git a/documentation/news.md b/documentation/news.md deleted file mode 100644 index 3a97cef0d..000000000 --- a/documentation/news.md +++ /dev/null @@ -1,47 +0,0 @@ -# News & updates - -This section contains noteworthy updates related to the project and the project page. For a detailed list of changes to the codebase, check [Changelog](releases/changelog.md). - ---- - -## 28 Nov 2022 - - -- Added [Coding challenges](how-to-use-appyx/coding-challenges.md) -- Updated [Transitions](ui/transitions.md) - ---- - -## 21 Nov 2022 - -[FAQ](faq.md) received a major update - ---- - -## 31 Oct 2022 - -🎉 Released 1.0.0 🎉 - ---- - -## 27-28 Oct 2022 - - - - -[Model-driven navigation with Appyx](https://www.droidcon.com/2022/11/15/model-driven-navigation-with-appyx-from-zero-to-hero/) - - ---- - -## 22 Sep 2022 - -Added documentation on [ChildAware API](apps/childaware.md). You can use it to scope communication with (or between) dynamically available child nodes easily. - ---- - -## 1-2 Sep 2022 - - - -[Model-driven navigation with Jetpack Compose](https://www.droidcon.com/2022/09/29/model-driven-navigation-with-jetpack-compose-from-zero-to-hero/) diff --git a/documentation/releases/changelog.md b/documentation/releases/changelog.md deleted file mode 120000 index 699cc9e7b..000000000 --- a/documentation/releases/changelog.md +++ /dev/null @@ -1 +0,0 @@ -../../CHANGELOG.md \ No newline at end of file diff --git a/documentation/releases/downloads.md b/documentation/releases/downloads.md deleted file mode 100644 index 322f482d0..000000000 --- a/documentation/releases/downloads.md +++ /dev/null @@ -1,64 +0,0 @@ -# Downloads - -## Latest version - -![Maven Central](https://img.shields.io/maven-central/v/com.bumble.appyx/core) - -## Repository - -```groovy -repositories { - mavenCentral() -} -``` - -## Core dependencies - -```groovy -dependencies { - // Core - implementation "com.bumble.appyx:core:$version" - - // Test rules and utility classes for testing on Android - debugImplementation "com.bumble.appyx:testing-ui-activity:$version" - androidTestImplementation "com.bumble.appyx:testing-ui:$version" - - // Utility classes for unit testing - testImplementation "com.bumble.appyx:testing-unit-common:$version" - - // Test rules and utility classes for unit testing using JUnit4 - testImplementation "com.bumble.appyx:testing-junit4:$version" - - // Test extensions and utility classes for unit testing using JUnit5 - testImplementation "com.bumble.appyx:testing-junit5:$version" -} -``` - -## Interop with other libraries - -```groovy -dependencies { - // Optional support for RxJava 2/3 - implementation "com.bumble.appyx:interop-rx2:$version" - implementation "com.bumble.appyx:interop-rx3:$version" - - // Optional interoperability layer between Appyx and badoo/RIBs - // You have to add https://jitpack.io repository to use it because badoo/RIBs is hosted there - implementation "com.bumble.appyx:interop-ribs:$version" - -} -``` - -## Snapshot - -Snapshot version is available for all modules, use the provided repository url and `1-SNAPSHOT` version. - -```groovy -repositories { - maven { url = 'https://s01.oss.sonatype.org/content/repositories/snapshots/' } -} - -dependencies { - implementation "com.bumble.appyx:core:v1-SNAPSHOT" -} -``` diff --git a/documentation/stylesheets/extra.css b/documentation/stylesheets/extra.css deleted file mode 100644 index 1b01b449c..000000000 --- a/documentation/stylesheets/extra.css +++ /dev/null @@ -1,22 +0,0 @@ -:root > * { - --md-primary-fg-color: #1F2126; - --md-primary-fg-color--light: #FFC629; - --md-primary-fg-color--dark: #FFC629; -} - -.md-header { - --md-primary-bg-color: white; -} - -.md-nav label { - --md-primary-bg-color: white; -} - -.md-search ::placeholder { - color: white; - opacity: 0.5; -} - -.md-search .md-icon { - opacity: 0.5; -} diff --git a/documentation/ui/children-view.md b/documentation/ui/children-view.md deleted file mode 100644 index 3265c24b2..000000000 --- a/documentation/ui/children-view.md +++ /dev/null @@ -1,100 +0,0 @@ -# Adding children to the view - -Navigation models define only the abstract model, not how that model will look on the screen. This section describes different ways of adding children (navigation targets) to the composition. - -All the below mentioned composables should be added to the `View` of the parent node. - - -## Children - -Renders all visible children of a NavModel. This is the simplest and most common case. - -```kotlin -@Composable -override fun View(modifier: Modifier) { - Children( - modifier = Modifier, // optional - navModel = TODO(), - transitionHandler = TODO() // optional - ) -} -``` - -When rendering children you can have access to `TransitionDescriptor` which provides the following information: - -```kotlin -@Immutable -data class TransitionDescriptor( - val params: TransitionParams, - val operation: Operation, - val element: NavTarget, - val fromState: State, - val toState: State -) -``` - -Additionally, you can supply custom modifier to a child `Node`. In this example, we're supplying different `Modifier` -to a child `Node` depending on the `NavTarget`: - -```kotlin -@Composable -override fun View(modifier: Modifier) { - Children( - modifier = Modifier, // optional - navModel = TODO(), - transitionHandler = TODO() // optional - ) { - children { child, descriptor -> - child( - modifier = Modifier - .background( - color = getBackgroundColor(descriptor.element) - ) - ) - } - } -} -``` - -## Child - -Renders a single child associated to a `NavElement`. Useful if you want to define different child placements in the layout individually. - -```kotlin -@Composable -override fun View(modifier: Modifier) { - Child( - navElement = element, - transitionHandler = TODO() - ) { child, _ -> - // TODO wrap in your own composables - child() - } -} -``` - -## Lazy lists / grids - -```kotlin -@Composable -override fun View(modifier: Modifier) { - // TODO grab all visible children from the navModel manually - val children by navModel.visibleChildrenAsState() - GridExample(children) -} - -@Composable -private fun GridExample(elements: List>) { - LazyVerticalGrid( - columns = Fixed(2), - modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(horizontal = 16.dp), - ) { - items(elements) { element -> - // TODO use Child composable to render them individually inside the list / grid - Child(navElement = element) - } - } -} -``` - diff --git a/documentation/ui/transitions.md b/documentation/ui/transitions.md deleted file mode 100644 index 1c782f896..000000000 --- a/documentation/ui/transitions.md +++ /dev/null @@ -1,84 +0,0 @@ -# Transitions - -You can have arbitrary visualisations and transitions for any [NavModel](../navmodel/index.md). For example, all of these are different representations of the same [Back stack](../navmodel/backstack.md): - - - - - - -Below you can find the different options how to visualise `NavModel` state changes. - - -## No transitions - -Using the provided [Child-related composables](children-view.md) you'll see no transitions as a default – UI changes resulting from the NavModel's state update will be rendered instantly. - - -## Jetpack Compose default animations - -You can use [standard Compose animations](https://developer.android.com/jetpack/compose/animation) for embedded child `Nodes` in the view, e.g. `AnimatedVisibility`: - -```kotlin -var visibility by remember { mutableStateOf(true) } - -Child(navElement) { child, _ -> - AnimatedVisibility(visible = visibility) { - child() - } -} -``` - -## Appyx transition handlers - -All the [child composables](children-view.md) provided by Appyx accept an optional `transitionHandler` argument too: - -- You can use the provided ones as they're a one-liner to add – you can check the individual [NavModels](../navmodel/index.md) for the ones they come shipped with. -- You can also implement your own. - -The benefit of using transition handlers is you can represent any custom state of elements defined by your NavModel with Compose `Modifiers`. - -The example below is taken from [custom navigation models](../navmodel/custom.md). It matches custom transition states to different scaling values, and returns a `scale` `Modifier`. - -```kotlin -class FooTransitionHandler( - private val transitionSpec: TransitionSpec = { spring() } -) : ModifierTransitionHandler() { - - // TODO define a Modifier depending on the state. - // Here we'll just mutate scaling: - override fun createModifier( - modifier: Modifier, - transition: Transition, - descriptor: TransitionDescriptor - ): Modifier = modifier.composed { - val scale = transition.animateFloat( - transitionSpec = transitionSpec, - targetValueByState = { - when (it) { - Foo.State.CREATED -> 0f - Foo.State.FOO -> 0.33f - Foo.State.BAR -> 0.66f - Foo.State.BAZ -> 1.0f - Foo.State.DESTROYED -> 0f - } - }) - - scale(scale.value) - } -} - -// TODO remember to add: -@Composable -fun rememberFooTransitionHandler( - transitionSpec: TransitionSpec = { spring() } -): ModifierTransitionHandler = remember { - FooTransitionHandler(transitionSpec) -} -``` - -## More info - -1. You can find more complex examples in the implementations of other NavModels, such as the [Promoter carousel](../navmodel/promoter.md) -2. You can find [Codelabs tutorials](../how-to-use-appyx/codelabs.md) that help you master custom transitions -3. You can find [Coding challenges](../how-to-use-appyx/coding-challenges.md) related to custom transitions diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index 079469e67..000000000 --- a/mkdocs.yml +++ /dev/null @@ -1,98 +0,0 @@ -# General setup -site_name: Appyx -site_url: https://github.com/bumble-tech/appyx -site_author: Bumble -site_description: Model-driven navigation for Jetpack Compose - -# Repository -repo_name: Appyx -repo_url: https://github.com/bumble-tech/appyx -edit_uri: "" - -# Copyright -copyright: Copyright © 2022 - 2022 Bumble - -# Navigation -docs_dir: documentation - -nav: - - Overview: index.md - - News & updates: news.md - - Releases: - - Downloads: releases/downloads.md - - Changelog: releases/changelog.md - - Using Appyx: - - Quick start guide: how-to-use-appyx/quick-start.md - - Codelabs: how-to-use-appyx/codelabs.md - - Coding challenges: how-to-use-appyx/coding-challenges.md - - Sample apps: how-to-use-appyx/sample-apps.md - - Navigation: - - Model-driven navigation: navigation/model-driven-navigation.md - - NavModel: - - Overview: navmodel/index.md - - Back stack: navmodel/backstack.md - - Spotlight: navmodel/spotlight.md - - Cards: navmodel/cards.md - - Tiles: navmodel/tiles.md - - Promoter: navmodel/promoter.md - - Writing your own: navmodel/custom.md - - Composable navigation: navigation/composable-navigation.md - - Implicit navigation: navigation/implicit-navigation.md - - Explicit navigation: navigation/explicit-navigation.md - - Deep linking: navigation/deep-linking.md - - UI: - - Children: ui/children-view.md - - Transitions: ui/transitions.md - - App: - - Structuring your app navigation: apps/structure.md - - Lifecycle: apps/lifecycle.md - - Plugins: apps/plugins.md - - ChildAware API: apps/childaware.md - - Configuration changes: apps/configuration.md - - FAQ: faq.md - - -# theme configuration -theme: - name: 'material' - logo: assets/logo.png - icon: - repo: fontawesome/brands/github - language: en - include_search_page: false - search_index_only: true - features: -# - navigation.tabs - - search.highlight - - search.share - - search.suggest - palette: - scheme: default - primary: amber - accent: amber - font: - text: Roboto - code: Roboto Mono - favicon: assets/favicon.svg - -extra_css: - - stylesheets/extra.css - -plugins: - - search - -# extensions -markdown_extensions: - - admonition - - codehilite - - footnotes - - meta - - toc: - permalink: true - - pymdownx.betterem: - smart_enable: all - - pymdownx.caret - - pymdownx.inlinehilite - - pymdownx.magiclink - - pymdownx.smartsymbols - - pymdownx.superfences