diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 29f8ece1..9339ed08 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -54,17 +54,9 @@
-
+
@@ -243,19 +235,12 @@
+
-
-
-
-
-
-
-
-
diff --git a/.travis.yml b/.travis.yml
index f4185af6..08271aba 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,8 +4,8 @@ android:
components:
- tools
- platform-tools
- - build-tools-29.0.2
- - android-29
+ - build-tools-30.0.2
+ - android-30
- extra
before_install:
diff --git a/RELEASING.md b/RELEASING.md
index cbec0b65..6ef74c54 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -18,4 +18,4 @@ If step 6 or 7 fails, drop the Sonatype repo, fix the problem, commit, and start
1. Set up [signatory credentials](https://docs.gradle.org/current/userguide/signing_plugin.html#sec:signatory_credentials), or temporarily comment out the `signing` block in `gradle/gradle-mvn-push.gradle`.
2. Run `./gradlew publishToMavenLocal`.
3. In the other project, add `mavenLocal()` as a repository (likely in `allProjects.repositories` of the root `build.gradle` file).
-4. Update `com.wealthfront:magellan:X.Y.Z` to `com.wealthfront:magellan-library:SNAPSHOT_VERSION`, where `SNAPSHOT_VERSION` is the `VERSION_NAME` defined in this project's `./gradle.properties`.
+4. Update `com.wealthfront:magellan-library:X.Y.Z` to `com.wealthfront:magellan-library:SNAPSHOT_VERSION`, where `SNAPSHOT_VERSION` is the `VERSION_NAME` defined in this project's `./gradle.properties`.
diff --git a/build.gradle b/build.gradle
index b43aa426..b304ffaa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,19 +2,20 @@
buildscript {
repositories {
- jcenter()
- mavenCentral()
google()
+ mavenCentral()
+ jcenter()
}
dependencies {
classpath(Dependencies.kotlinGradle)
+ classpath(Dependencies.androidGradle)
classpath(Dependencies.kotlinterGradle)
classpath(Dependencies.kotlinAllOpen)
}
}
plugins {
- id 'io.gitlab.arturbosch.detekt' version '1.5.1'
+ id 'io.gitlab.arturbosch.detekt' version '1.18.0-RC1'
}
allprojects {
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 8bce6e09..6f88c839 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -1,5 +1,5 @@
plugins {
- id 'org.jetbrains.kotlin.jvm' version '1.3.72'
+ id 'org.jetbrains.kotlin.jvm' version '1.4.32'
}
repositories {
@@ -9,18 +9,22 @@ repositories {
}
dependencies {
- implementation "com.android.tools.build:gradle:4.1.1"
+ implementation 'com.android.tools.build:gradle:7.0.0-beta05'
+ // See https://issuetracker.google.com/issues/176079157#comment11
+ implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
+ useIR = true
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
+ useIR = true
}
}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt
index 999357a0..e1ab69db 100644
--- a/buildSrc/src/main/kotlin/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/Dependencies.kt
@@ -1,5 +1,8 @@
+import Versions.activityComposeVersion
+import Versions.androidGradleVersion
import Versions.archVersion
import Versions.butterKnifeVersion
+import Versions.composeVersion
import Versions.coroutinesVersion
import Versions.daggerVersion
import Versions.espressoVersion
@@ -30,12 +33,14 @@ import Versions.uiAutomatorVersion
object Dependencies {
const val kotlinStdLib = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
+ const val kotlinReflect = "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
const val appCompat = "androidx.appcompat:appcompat:$supportLibVersion"
const val lifecycle = "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
const val kotlinGradle = "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
const val kotlinterGradle = "org.jmailen.gradle:kotlinter-gradle:$kotlinterVersion"
const val kotlinAllOpen = "org.jetbrains.kotlin:kotlin-allopen:$kotlinVersion"
+ const val androidGradle = "com.android.tools.build:gradle:$androidGradleVersion"
const val material = "com.google.android.material:material:$materialVersion"
const val junit = "junit:junit:$junitVersion"
@@ -52,6 +57,16 @@ object Dependencies {
const val coroutinesAndroid = "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
const val coroutinesTest = "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
+ const val composeCompiler = "androidx.compose.compiler:compiler:$composeVersion"
+ const val composeUi = "androidx.compose.ui:ui:$composeVersion"
+ const val composeUiUtil = "androidx.compose.ui:ui-util:$composeVersion"
+ const val composeUiTooling = "androidx.compose.ui:ui-tooling:$composeVersion"
+ const val composeFoundation = "androidx.compose.foundation:foundation:$composeVersion"
+ const val composeFoundationLayout = "androidx.compose.foundation:foundation-layout:$composeVersion"
+ const val composeRuntime = "androidx.compose.runtime:runtime:$composeVersion"
+ const val composeMaterial = "androidx.compose.material:material:$composeVersion"
+ const val activityCompose = "androidx.activity:activity-compose:$activityComposeVersion"
+
const val glide = "com.github.bumptech.glide:glide:$glideVersion"
const val retrofit = "com.squareup.retrofit2:retrofit:$retrofitVersion"
const val rxjava = "io.reactivex:rxjava:$rxjavaVersion"
diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt
index ab03da7c..117bed77 100644
--- a/buildSrc/src/main/kotlin/Versions.kt
+++ b/buildSrc/src/main/kotlin/Versions.kt
@@ -1,12 +1,14 @@
object Versions {
const val compileSdkVersion = 30
- const val minSdkVersion = 15
+ const val minSdkVersion = 21
const val targetSdkVersion = 30
- const val kotlinVersion = "1.4.30"
- const val kotlinterVersion = "3.4.0"
- const val buildToolsVersion = "29.0.2"
- const val detektVersion = "1.5.1"
+ const val kotlinVersion = "1.5.10"
+ const val androidGradleVersion = "7.0.0-beta05"
+ const val coroutinesVersion = "1.5.0"
+ const val kotlinterVersion = "3.4.5"
+ const val buildToolsVersion = "31.0.0-rc5"
+ const val detektVersion = "1.17.1"
const val supportLibVersion = "1.1.0"
const val robolectricVersion = "4.3.1"
const val archVersion = "2.1.0"
@@ -24,7 +26,8 @@ object Versions {
const val okhttpVersion = "4.4.0"
const val javaInjectVersion = "1"
const val materialVersion = "1.1.0"
- const val coroutinesVersion = "1.4.3"
+ const val composeVersion = "1.0.0-rc02"
+ const val activityComposeVersion = "1.3.0-rc02"
const val testCoreVersion = "1.2.0"
const val junitVersion = "4.13"
diff --git a/gradle.properties b/gradle.properties
index b628faa5..9ddda3cb 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,5 +1,5 @@
GROUP=com.wealthfront
-VERSION_NAME=2.1.1
+VERSION_NAME=2.2.0-SNAPSHOT
POM_DESCRIPTION=The simplest navigation library for Android
@@ -20,3 +20,8 @@ SNAPSHOT_REPOSITORY_URL=https://oss.sonatype.org/content/repositories/snapshots/
android.useAndroidX=true
android.enableJetifier=true
+
+# Turn on parallel compilation, caching and on-demand configuration
+org.gradle.configureondemand=true
+org.gradle.caching=true
+org.gradle.parallel=true
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 4a14f202..34576f11 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
diff --git a/magellan-compose/.gitignore b/magellan-compose/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/magellan-compose/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/magellan-compose/build.gradle b/magellan-compose/build.gradle
new file mode 100644
index 00000000..0c88cd30
--- /dev/null
+++ b/magellan-compose/build.gradle
@@ -0,0 +1,77 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+group = GROUP
+version = VERSION_NAME
+
+android {
+ compileSdk Versions.compileSdkVersion
+ buildToolsVersion = Versions.buildToolsVersion
+
+ resourcePrefix 'magellan_'
+
+ defaultConfig {
+ minSdkVersion Versions.minSdkVersion
+ targetSdkVersion Versions.targetSdkVersion
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildFeatures {
+ compose true
+ }
+
+ kotlinOptions {
+ jvmTarget = "1.8"
+ useIR = true
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion Versions.composeVersion
+ }
+
+ compileOptions {
+ setSourceCompatibility(JavaVersion.VERSION_1_8)
+ setTargetCompatibility(JavaVersion.VERSION_1_8)
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ }
+ }
+}
+
+tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
+ if (!name.contains("UnitTest")) {
+ kotlinOptions.freeCompilerArgs = ['-Xjvm-default=compatibility', '-Xexplicit-api=strict', '-Xopt-in=kotlin.RequiresOptIn']
+ }
+ kotlinOptions.allWarningsAsErrors = true
+ kotlinOptions.jvmTarget = "1.8"
+ kotlinOptions.useIR = true
+}
+
+dependencies {
+ implementation project(':magellan-library')
+
+ implementation Dependencies.appCompat
+ implementation Dependencies.kotlinStdLib
+ implementation Dependencies.kotlinReflect
+ implementation Dependencies.inject
+ implementation Dependencies.coroutines
+ implementation Dependencies.coroutinesAndroid
+ implementation Dependencies.composeUi
+ implementation Dependencies.composeUiUtil
+ implementation Dependencies.composeUiTooling
+ implementation Dependencies.composeFoundation
+ implementation Dependencies.activityCompose
+ implementation Dependencies.lifecycle
+
+ testImplementation Dependencies.testCore
+ testImplementation Dependencies.junit
+ testImplementation Dependencies.truth
+ testImplementation Dependencies.mockito
+ testImplementation Dependencies.robolectric
+}
+
+apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
diff --git a/magellan-compose/gradle.properties b/magellan-compose/gradle.properties
new file mode 100644
index 00000000..5991bca3
--- /dev/null
+++ b/magellan-compose/gradle.properties
@@ -0,0 +1,4 @@
+POM_ARTIFACT_ID=magellan-compose
+POM_NAME=Magellan Compose
+POM_DESCRIPTION=Compose support for Magellan
+POM_PACKAGING=aar
\ No newline at end of file
diff --git a/magellan-compose/src/androidTest/java/com/example/magellan/compose/ExampleInstrumentedTest.kt b/magellan-compose/src/androidTest/java/com/example/magellan/compose/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..1f20bdc7
--- /dev/null
+++ b/magellan-compose/src/androidTest/java/com/example/magellan/compose/ExampleInstrumentedTest.kt
@@ -0,0 +1,25 @@
+package com.example.magellan.compose
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.example.magellan.compose.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/magellan-compose/src/main/AndroidManifest.xml b/magellan-compose/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..c846df87
--- /dev/null
+++ b/magellan-compose/src/main/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/magellan-compose/src/main/java/com/example/magellan/compose/ActivityLifecycleComposeAdapter.kt b/magellan-compose/src/main/java/com/example/magellan/compose/ActivityLifecycleComposeAdapter.kt
new file mode 100644
index 00000000..564c2780
--- /dev/null
+++ b/magellan-compose/src/main/java/com/example/magellan/compose/ActivityLifecycleComposeAdapter.kt
@@ -0,0 +1,48 @@
+package com.example.magellan.compose
+
+import android.app.Activity
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.runtime.Composable
+import androidx.lifecycle.DefaultLifecycleObserver
+import com.wealthfront.magellan.core.Navigable
+import com.wealthfront.magellan.lifecycle.LifecycleOwner
+import com.wealthfront.magellan.lifecycle.LifecycleState
+
+private typealias AndroidLifecycleOwner = androidx.lifecycle.LifecycleOwner
+
+public class ActivityLifecycleComposeAdapter(
+ private val navigable: Navigable<@Composable () -> Unit>,
+ private val context: Activity
+) : DefaultLifecycleObserver {
+
+ override fun onStart(owner: AndroidLifecycleOwner) {
+ navigable.show(context)
+ }
+
+ override fun onResume(owner: AndroidLifecycleOwner) {
+ navigable.resume(context)
+ }
+
+ override fun onPause(owner: AndroidLifecycleOwner) {
+ navigable.pause(context)
+ }
+
+ override fun onStop(owner: AndroidLifecycleOwner) {
+ navigable.hide(context)
+ }
+
+ override fun onDestroy(owner: AndroidLifecycleOwner) {
+ if (context.isFinishing) {
+ navigable.destroy(context.applicationContext)
+ }
+ }
+}
+
+public fun ComponentActivity.setContentNavigable(navigable: Navigable<@Composable () -> Unit>) {
+ if (navigable is LifecycleOwner && navigable.currentState == LifecycleState.Destroyed) {
+ navigable.create(applicationContext)
+ }
+ lifecycle.addObserver(ActivityLifecycleComposeAdapter(navigable, this))
+ setContent { navigable.view!!() }
+}
diff --git a/magellan-compose/src/main/java/com/example/magellan/compose/ComposeExtensions.kt b/magellan-compose/src/main/java/com/example/magellan/compose/ComposeExtensions.kt
new file mode 100644
index 00000000..308a959b
--- /dev/null
+++ b/magellan-compose/src/main/java/com/example/magellan/compose/ComposeExtensions.kt
@@ -0,0 +1,46 @@
+package com.example.magellan.compose
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import com.wealthfront.magellan.core.Displayable
+import com.wealthfront.magellan.lifecycle.LifecycleOwner
+import com.wealthfront.magellan.lifecycle.LifecycleState.Created
+import com.wealthfront.magellan.lifecycle.LifecycleState.Destroyed
+import com.wealthfront.magellan.lifecycle.LifecycleState.Resumed
+import com.wealthfront.magellan.lifecycle.LifecycleState.Shown
+import kotlinx.coroutines.flow.map
+
+@Composable
+public fun Displayable(
+ displayable: Displayable<@Composable () -> Unit>,
+ modifier: Modifier = Modifier
+) {
+ Box(modifier = modifier) {
+ displayable.view!!()
+ }
+}
+
+@Composable
+public fun Displayable<@Composable () -> Unit>.Content(modifier: Modifier = Modifier) {
+ Box(modifier = modifier) {
+ view!!()
+ }
+}
+
+@Composable
+public fun LifecycleOwner.WhenShown(Content: @Composable () -> Unit) {
+ val isShown by currentStateFlow
+ .map { lifecycleState ->
+ when (lifecycleState) {
+ is Destroyed, is Created -> false
+ is Shown, is Resumed -> true
+ }
+ }
+ .collectAsState(false)
+ if (isShown) {
+ Content()
+ }
+}
diff --git a/magellan-compose/src/main/java/com/example/magellan/compose/ComposeInterop.kt b/magellan-compose/src/main/java/com/example/magellan/compose/ComposeInterop.kt
new file mode 100644
index 00000000..5c19f732
--- /dev/null
+++ b/magellan-compose/src/main/java/com/example/magellan/compose/ComposeInterop.kt
@@ -0,0 +1,49 @@
+package com.example.magellan.compose
+
+import android.view.View
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.viewinterop.AndroidView
+import com.wealthfront.magellan.core.Displayable
+import com.wealthfront.magellan.core.Navigable
+import com.wealthfront.magellan.lifecycle.LifecycleAwareComponent
+import com.wealthfront.magellan.lifecycle.lifecycle
+import com.wealthfront.magellan.lifecycle.lifecycleWithContext
+
+@Composable
+public fun ViewDisplayable(displayable: Displayable, modifier: Modifier = Modifier) {
+ AndroidView(modifier = modifier, factory = {
+ if (displayable.view == null) {
+ throw IllegalStateException(
+ "View does not exist on ${displayable::class.java.simpleName}. " +
+ "Is it attached to the lifecycle?"
+ )
+ }
+ displayable.view!!
+ })
+}
+
+public class ViewStepWrappingComposeStep(
+ composeStep: Navigable<@Composable () -> Unit>
+) : LifecycleAwareComponent(), Navigable {
+
+ public val composeStep: Navigable<@Composable () -> Unit> by lifecycle(composeStep)
+
+ override val view: View? by lifecycleWithContext { context ->
+ ComposeView(context).also { composeView ->
+ composeView.setContent @Composable { Displayable(this.composeStep) }
+ }
+ }
+}
+
+public class ComposeStepWrappingViewStep(
+ viewStep: Navigable
+) : LifecycleAwareComponent(), Navigable<@Composable () -> Unit> {
+
+ public val viewStep: Navigable by lifecycle(viewStep)
+
+ override val view: @Composable () -> Unit = {
+ ViewDisplayable(this.viewStep)
+ }
+}
diff --git a/magellan-compose/src/main/java/com/example/magellan/compose/ComposeJourney.kt b/magellan-compose/src/main/java/com/example/magellan/compose/ComposeJourney.kt
new file mode 100644
index 00000000..ca9b4575
--- /dev/null
+++ b/magellan-compose/src/main/java/com/example/magellan/compose/ComposeJourney.kt
@@ -0,0 +1,13 @@
+package com.example.magellan.compose
+
+import androidx.compose.runtime.Composable
+import com.example.magellan.compose.navigation.ComposeNavigator
+import com.wealthfront.magellan.lifecycle.lifecycle
+
+public abstract class ComposeJourney : ComposeStep() {
+
+ protected var navigator: ComposeNavigator by lifecycle(ComposeNavigator())
+
+ @Composable
+ protected override fun Content(): Unit = Displayable(navigator)
+}
\ No newline at end of file
diff --git a/magellan-compose/src/main/java/com/example/magellan/compose/ComposeStep.kt b/magellan-compose/src/main/java/com/example/magellan/compose/ComposeStep.kt
new file mode 100644
index 00000000..13d18434
--- /dev/null
+++ b/magellan-compose/src/main/java/com/example/magellan/compose/ComposeStep.kt
@@ -0,0 +1,25 @@
+package com.example.magellan.compose
+
+import androidx.annotation.VisibleForTesting
+import androidx.compose.runtime.Composable
+import com.wealthfront.magellan.core.Navigable
+import com.wealthfront.magellan.coroutines.ShownLifecycleScope
+import com.wealthfront.magellan.lifecycle.LifecycleAwareComponent
+import com.wealthfront.magellan.lifecycle.lifecycle
+import kotlinx.coroutines.CoroutineScope
+
+public abstract class ComposeStep : LifecycleAwareComponent(), Navigable<@Composable () -> Unit> {
+
+ override val view: (@Composable () -> Unit)?
+ get() = {
+ WhenShown {
+ Content()
+ }
+ }
+
+ public var shownScope: CoroutineScope by lifecycle(ShownLifecycleScope()) { it }
+ @VisibleForTesting set
+
+ @Composable
+ protected abstract fun Content()
+}
\ No newline at end of file
diff --git a/magellan-compose/src/main/java/com/example/magellan/compose/SimpleComposeStep.kt b/magellan-compose/src/main/java/com/example/magellan/compose/SimpleComposeStep.kt
new file mode 100644
index 00000000..a93bf04f
--- /dev/null
+++ b/magellan-compose/src/main/java/com/example/magellan/compose/SimpleComposeStep.kt
@@ -0,0 +1,13 @@
+package com.example.magellan.compose
+
+import androidx.compose.runtime.Composable
+
+public class SimpleComposeStep(
+ private val SimpleContent: @Composable SimpleComposeStep.() -> Unit
+) : ComposeStep() {
+
+ @Composable
+ override fun Content() {
+ SimpleContent()
+ }
+}
\ No newline at end of file
diff --git a/magellan-compose/src/main/java/com/example/magellan/compose/navigation/ComposeNavigator.kt b/magellan-compose/src/main/java/com/example/magellan/compose/navigation/ComposeNavigator.kt
new file mode 100644
index 00000000..ed31fc65
--- /dev/null
+++ b/magellan-compose/src/main/java/com/example/magellan/compose/navigation/ComposeNavigator.kt
@@ -0,0 +1,191 @@
+package com.example.magellan.compose.navigation
+
+import android.content.Context
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import com.example.magellan.compose.Content
+import com.example.magellan.compose.WhenShown
+import com.example.magellan.compose.transitions.MagellanComposeTransition
+import com.example.magellan.compose.transitions.defaultTransition
+import com.example.magellan.compose.transitions.noTransition
+import com.wealthfront.magellan.Direction
+import com.wealthfront.magellan.Direction.BACKWARD
+import com.wealthfront.magellan.Direction.FORWARD
+import com.wealthfront.magellan.core.Displayable
+import com.wealthfront.magellan.core.Navigable
+import com.wealthfront.magellan.lifecycle.LifecycleAwareComponent
+import com.wealthfront.magellan.lifecycle.LifecycleLimit
+import com.wealthfront.magellan.lifecycle.LifecycleLimiter
+import com.wealthfront.magellan.lifecycle.lifecycle
+import com.wealthfront.magellan.navigation.NavigableCompat
+import com.wealthfront.magellan.navigation.NavigationPropagator
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.map
+
+@OptIn(ExperimentalAnimationApi::class)
+public class ComposeNavigator : LifecycleAwareComponent(), Displayable<@Composable () -> Unit> {
+
+ private val navigationPropagator = NavigationPropagator
+
+ private val lifecycleLimiter by lifecycle(LifecycleLimiter())
+
+ private val backStackFlow = MutableStateFlow>(emptyList())
+ public val backStack: List = backStackFlow.value
+ private val currentNavigable = backStack.lastOrNull()?.navigable
+
+ private val currentNavigationEventFlow = backStackFlow.map { it.lastOrNull() }
+
+ // TODO: make default transition configurable
+ private val transitionFlow: MutableStateFlow =
+ MutableStateFlow(defaultTransition)
+ private val directionFlow: MutableStateFlow = MutableStateFlow(FORWARD)
+
+ override val view: (@Composable () -> Unit)
+ get() = { Content() }
+
+ @Composable
+ private fun Content() {
+ WhenShown {
+ val currentNavigable by currentNavigationEventFlow
+ .map { it?.navigable }
+ .collectAsState(null)
+ val currentTransitionSpec by transitionFlow.collectAsState()
+ val currentDirection by directionFlow.collectAsState()
+
+ AnimatedContent(
+ targetState = currentNavigable,
+ transitionSpec = currentTransitionSpec.getTransitionForDirection(currentDirection)
+ ) { navigable ->
+ navigable?.Content()
+ }
+ }
+ }
+
+ override fun onDestroy(context: Context) {
+ backStack
+ .map { it.navigable }
+ .forEach { lifecycleLimiter.removeFromLifecycle(it) }
+ backStackFlow.value = emptyList()
+ }
+
+ public fun goTo(
+ navigable: Navigable<@Composable () -> Unit>,
+ overrideTransitionSpec: MagellanComposeTransition? = null
+ ) {
+ navigate(FORWARD) { backStack ->
+ backStack + ComposeNavigationEvent(
+ navigable = navigable,
+ transitionSpec = overrideTransitionSpec ?: defaultTransition
+ )
+ }
+ }
+
+ public fun replace(
+ navigable: Navigable<@Composable () -> Unit>,
+ overrideTransitionSpec: MagellanComposeTransition? = null
+ ) {
+ navigate(FORWARD) { backStack ->
+ backStack - backStack.last() + ComposeNavigationEvent(
+ navigable = navigable,
+ transitionSpec = overrideTransitionSpec ?: defaultTransition
+ )
+ }
+ }
+
+ public fun goBack(): Boolean {
+ return if (!atRoot()) {
+ navigate(BACKWARD) { backStack ->
+ backStack - backStack.last()
+ }
+ true
+ } else {
+ false
+ }
+ }
+
+ public fun goBackTo(navigable: Navigable<@Composable () -> Unit>) {
+ navigate(BACKWARD) { backStack ->
+ val mutableBackstack = backStack.toMutableList()
+ while (navigable !== mutableBackstack.last().navigable) {
+ mutableBackstack.removeLast()
+ }
+ mutableBackstack.toList()
+ }
+ }
+
+ public fun resetWithRoot(navigable: Navigable<@Composable () -> Unit>) {
+ navigate(FORWARD) {
+ listOf(ComposeNavigationEvent(navigable, noTransition))
+ }
+ }
+
+ public fun navigate(
+ direction: Direction,
+ backStackOperation: (backStack: List) -> List
+ ) {
+ // TODO: Intercept touch events, if necessary
+ navigationPropagator.beforeNavigation()
+ val from = backStack.lastOrNull()?.navigable
+ val oldBackStack = backStack
+ val newBackStack = backStackOperation(backStack)
+ directionFlow.value = direction
+ transitionFlow.value = when (direction) {
+ FORWARD -> newBackStack.last().transitionSpec
+ BACKWARD -> oldBackStack.last().transitionSpec
+ }
+ findBackstackChangesAndUpdateStates(
+ oldBackStack = oldBackStack.map { it.navigable },
+ newBackStack = newBackStack.map { it.navigable }
+ )
+ updateCurrentBackstackLifecycleLimits(newBackStack.map { it.navigable }, from)
+ navigationPropagator.afterNavigation()
+ }
+
+ private fun findBackstackChangesAndUpdateStates(
+ oldBackStack: List>,
+ newBackStack: List>
+ ) {
+ val oldNavigables = oldBackStack.toSet()
+ val newNavigables = newBackStack.toSet()
+
+ (oldNavigables - newNavigables).forEach { oldNavigable ->
+ lifecycleLimiter.removeFromLifecycle(oldNavigable)
+ }
+
+ (newNavigables - oldNavigables).forEach { newNavigable ->
+ lifecycleLimiter.attachToLifecycleWithMaxState(newNavigable, LifecycleLimit.CREATED)
+ }
+ }
+
+ private fun updateCurrentBackstackLifecycleLimits(
+ newBackStack: List Unit>>,
+ from: NavigableCompat<@Composable () -> Unit>?
+ ) {
+ newBackStack.forEachIndexed { index, navigableCompat ->
+ lifecycleLimiter.updateMaxStateForChild(
+ navigableCompat,
+ if (index != newBackStack.lastIndex) {
+ LifecycleLimit.CREATED
+ } else {
+ from?.let { navigationPropagator.onNavigatedFrom(from) }
+ LifecycleLimit.NO_LIMIT
+ }
+ )
+ }
+
+ navigationPropagator.onNavigatedTo(newBackStack.last())
+ }
+
+ override fun onBackPressed(): Boolean = currentNavigable?.backPressed() ?: false || goBack()
+
+ public fun atRoot(): Boolean = backStack.size <= 1
+}
+
+@ExperimentalAnimationApi
+public data class ComposeNavigationEvent(
+ val navigable: Navigable<@Composable () -> Unit>,
+ val transitionSpec: MagellanComposeTransition
+)
diff --git a/magellan-compose/src/main/java/com/example/magellan/compose/transitions/BuiltInTransitionSpecs.kt b/magellan-compose/src/main/java/com/example/magellan/compose/transitions/BuiltInTransitionSpecs.kt
new file mode 100644
index 00000000..48852980
--- /dev/null
+++ b/magellan-compose/src/main/java/com/example/magellan/compose/transitions/BuiltInTransitionSpecs.kt
@@ -0,0 +1,58 @@
+package com.example.magellan.compose.transitions
+
+import androidx.compose.animation.AnimatedContentScope
+import androidx.compose.animation.AnimatedContentScope.SlideDirection.Companion.Down
+import androidx.compose.animation.AnimatedContentScope.SlideDirection.Companion.Left
+import androidx.compose.animation.AnimatedContentScope.SlideDirection.Companion.Right
+import androidx.compose.animation.AnimatedContentScope.SlideDirection.Companion.Up
+import androidx.compose.animation.ContentTransform
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.with
+import androidx.compose.runtime.Composable
+import com.wealthfront.magellan.Direction
+import com.wealthfront.magellan.core.Navigable
+
+@OptIn(ExperimentalAnimationApi::class)
+public interface MagellanComposeTransition {
+
+ public fun getTransitionForDirection(
+ direction: Direction
+ ): AnimatedContentScope Unit>?>.() -> ContentTransform
+}
+
+@OptIn(ExperimentalAnimationApi::class)
+public class SimpleComposeTransition(
+ public val transitionSpec: AnimatedContentScope Unit>?>.(Direction) -> ContentTransform
+) : MagellanComposeTransition {
+
+ public override fun getTransitionForDirection(
+ direction: Direction
+ ): AnimatedContentScope Unit>?>.() -> ContentTransform {
+ return { transitionSpec(direction) }
+ }
+}
+
+@OptIn(ExperimentalAnimationApi::class)
+public val defaultTransition: SimpleComposeTransition = SimpleComposeTransition {
+ when (it) {
+ Direction.FORWARD -> slideIntoContainer(Left) with slideOutOfContainer(Left)
+ Direction.BACKWARD -> slideIntoContainer(Right) with slideOutOfContainer(Right)
+ }
+}
+
+@OptIn(ExperimentalAnimationApi::class)
+public val showTransition: SimpleComposeTransition = SimpleComposeTransition {
+ when (it) {
+ Direction.FORWARD -> slideIntoContainer(Up) with ExitTransition.None
+ Direction.BACKWARD -> EnterTransition.None with slideOutOfContainer(Down)
+ }
+}
+
+@OptIn(ExperimentalAnimationApi::class)
+public val noTransition: SimpleComposeTransition = SimpleComposeTransition {
+ EnterTransition.None with ExitTransition.None
+}
+
+// TODO: Add more transition specs
diff --git a/magellan-compose/src/test/java/com/example/magellan/compose/ExampleUnitTest.kt b/magellan-compose/src/test/java/com/example/magellan/compose/ExampleUnitTest.kt
new file mode 100644
index 00000000..9824535a
--- /dev/null
+++ b/magellan-compose/src/test/java/com/example/magellan/compose/ExampleUnitTest.kt
@@ -0,0 +1,18 @@
+package com.example.magellan.compose
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/magellan-legacy/build.gradle b/magellan-legacy/build.gradle
index e226ab11..217b8e3f 100644
--- a/magellan-legacy/build.gradle
+++ b/magellan-legacy/build.gradle
@@ -5,16 +5,14 @@ group = GROUP
version = VERSION_NAME
android {
- compileSdkVersion Versions.compileSdkVersion
- buildToolsVersion Versions.buildToolsVersion
+ compileSdk Versions.compileSdkVersion
+ buildToolsVersion = Versions.buildToolsVersion
resourcePrefix 'magellan_'
defaultConfig {
minSdkVersion Versions.minSdkVersion
targetSdkVersion Versions.targetSdkVersion
- versionCode 1
- versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -37,6 +35,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
}
kotlinOptions.allWarningsAsErrors = true
kotlinOptions.jvmTarget = "1.8"
+ kotlinOptions.useIR = true
}
dependencies {
@@ -44,6 +43,7 @@ dependencies {
implementation Dependencies.appCompat
implementation Dependencies.kotlinStdLib
+ implementation Dependencies.kotlinReflect
implementation Dependencies.inject
implementation Dependencies.coroutines
implementation Dependencies.coroutinesAndroid
diff --git a/magellan-library/build.gradle b/magellan-library/build.gradle
index 50287297..5cddb482 100644
--- a/magellan-library/build.gradle
+++ b/magellan-library/build.gradle
@@ -5,16 +5,14 @@ group = GROUP
version = VERSION_NAME
android {
- compileSdkVersion Versions.compileSdkVersion
- buildToolsVersion Versions.buildToolsVersion
+ compileSdk Versions.compileSdkVersion
+ buildToolsVersion = Versions.buildToolsVersion
resourcePrefix 'magellan_'
defaultConfig {
minSdkVersion Versions.minSdkVersion
targetSdkVersion Versions.targetSdkVersion
- versionCode 1
- versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -49,10 +47,12 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
}
kotlinOptions.allWarningsAsErrors = true
kotlinOptions.jvmTarget = "1.8"
+ kotlinOptions.useIR = true
}
dependencies {
implementation Dependencies.kotlinStdLib
+ implementation Dependencies.kotlinReflect
implementation Dependencies.appCompat
implementation Dependencies.lifecycle
implementation Dependencies.inject
diff --git a/magellan-library/src/main/java/com/wealthfront/magellan/lifecycle/LifecycleAwareComponent.kt b/magellan-library/src/main/java/com/wealthfront/magellan/lifecycle/LifecycleAwareComponent.kt
index 7a867ff3..b6b5abcd 100644
--- a/magellan-library/src/main/java/com/wealthfront/magellan/lifecycle/LifecycleAwareComponent.kt
+++ b/magellan-library/src/main/java/com/wealthfront/magellan/lifecycle/LifecycleAwareComponent.kt
@@ -1,6 +1,7 @@
package com.wealthfront.magellan.lifecycle
import android.content.Context
+import kotlinx.coroutines.flow.StateFlow
public abstract class LifecycleAwareComponent : LifecycleAware, LifecycleOwner {
@@ -9,6 +10,9 @@ public abstract class LifecycleAwareComponent : LifecycleAware, LifecycleOwner {
override val children: List
get() = lifecycleRegistry.listeners.toList()
+ override val currentStateFlow: StateFlow
+ get() = lifecycleRegistry.currentStateFlow
+
override val currentState: LifecycleState
get() = lifecycleRegistry.currentState
diff --git a/magellan-library/src/main/java/com/wealthfront/magellan/lifecycle/LifecycleLimiter.kt b/magellan-library/src/main/java/com/wealthfront/magellan/lifecycle/LifecycleLimiter.kt
index 404b6b0a..2764ad17 100644
--- a/magellan-library/src/main/java/com/wealthfront/magellan/lifecycle/LifecycleLimiter.kt
+++ b/magellan-library/src/main/java/com/wealthfront/magellan/lifecycle/LifecycleLimiter.kt
@@ -2,6 +2,7 @@ package com.wealthfront.magellan.lifecycle
import android.content.Context
import com.wealthfront.magellan.OpenForMocking
+import kotlinx.coroutines.flow.StateFlow
@OpenForMocking
public class LifecycleLimiter : LifecycleOwner, LifecycleAware {
@@ -11,6 +12,9 @@ public class LifecycleLimiter : LifecycleOwner, LifecycleAware {
override val children: List
get() = lifecycleRegistry.listenersToMaxStates.keys.toList()
+ override val currentStateFlow: StateFlow
+ get() = lifecycleRegistry.currentStateFlow
+
override val currentState: LifecycleState
get() = lifecycleRegistry.currentState
diff --git a/magellan-library/src/main/java/com/wealthfront/magellan/lifecycle/LifecycleOwner.kt b/magellan-library/src/main/java/com/wealthfront/magellan/lifecycle/LifecycleOwner.kt
index e71b6921..a105f9e4 100644
--- a/magellan-library/src/main/java/com/wealthfront/magellan/lifecycle/LifecycleOwner.kt
+++ b/magellan-library/src/main/java/com/wealthfront/magellan/lifecycle/LifecycleOwner.kt
@@ -1,8 +1,13 @@
package com.wealthfront.magellan.lifecycle
+import kotlinx.coroutines.flow.StateFlow
+
public interface LifecycleOwner {
+ public val currentStateFlow: StateFlow
+
public val currentState: LifecycleState
+ get() = currentStateFlow.value
public val children: List
diff --git a/magellan-library/src/main/java/com/wealthfront/magellan/lifecycle/LifecycleRegistry.kt b/magellan-library/src/main/java/com/wealthfront/magellan/lifecycle/LifecycleRegistry.kt
index 3d17fa6b..d39e34c0 100644
--- a/magellan-library/src/main/java/com/wealthfront/magellan/lifecycle/LifecycleRegistry.kt
+++ b/magellan-library/src/main/java/com/wealthfront/magellan/lifecycle/LifecycleRegistry.kt
@@ -5,19 +5,27 @@ import com.wealthfront.magellan.lifecycle.LifecycleLimit.CREATED
import com.wealthfront.magellan.lifecycle.LifecycleLimit.DESTROYED
import com.wealthfront.magellan.lifecycle.LifecycleLimit.NO_LIMIT
import com.wealthfront.magellan.lifecycle.LifecycleLimit.SHOWN
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
internal class LifecycleRegistry : LifecycleAware {
val listeners: Set
get() = listenersToMaxStates.keys
+
var listenersToMaxStates: Map = linkedMapOf()
private set
+
private val lifecycleStateMachine = LifecycleStateMachine()
- internal var currentState: LifecycleState = LifecycleState.Destroyed
+ private val _currentStateFlow: MutableStateFlow = MutableStateFlow(LifecycleState.Destroyed)
+ internal val currentStateFlow: StateFlow get() = _currentStateFlow.asStateFlow()
+
+ internal var currentState: LifecycleState
+ get() = currentStateFlow.value
private set(newState) {
- val oldState = field
- field = newState
+ val oldState = currentStateFlow.value
listenersToMaxStates.forEach { (lifecycleAware, maxState) ->
if (oldState.limitBy(maxState).order != newState.limitBy(maxState).order) {
lifecycleStateMachine.transition(
@@ -27,6 +35,7 @@ internal class LifecycleRegistry : LifecycleAware {
)
}
}
+ _currentStateFlow.value = newState
}
fun attachToLifecycle(
diff --git a/magellan-library/src/main/java/com/wealthfront/magellan/navigation/NavigationDelegate.kt b/magellan-library/src/main/java/com/wealthfront/magellan/navigation/NavigationDelegate.kt
index 8511f8c1..185b87b6 100644
--- a/magellan-library/src/main/java/com/wealthfront/magellan/navigation/NavigationDelegate.kt
+++ b/magellan-library/src/main/java/com/wealthfront/magellan/navigation/NavigationDelegate.kt
@@ -178,7 +178,7 @@ public class NavigationDelegate(
public fun goBackTo(navigable: NavigableCompat) {
navigate(BACKWARD) { backStack ->
- while (navigable != backStack.peek()!!.navigable) {
+ while (navigable !== backStack.peek()!!.navigable) {
backStack.pop()
}
backStack.peek()!!
diff --git a/magellan-library/src/main/java/com/wealthfront/magellan/navigation/NavigationPropagator.kt b/magellan-library/src/main/java/com/wealthfront/magellan/navigation/NavigationPropagator.kt
index a524edb8..4dc44f67 100644
--- a/magellan-library/src/main/java/com/wealthfront/magellan/navigation/NavigationPropagator.kt
+++ b/magellan-library/src/main/java/com/wealthfront/magellan/navigation/NavigationPropagator.kt
@@ -1,5 +1,7 @@
package com.wealthfront.magellan.navigation
+import androidx.annotation.RestrictTo
+
public object NavigationPropagator {
private var listeners: Set = emptySet()
@@ -14,25 +16,29 @@ public object NavigationPropagator {
listeners = listeners - navigationListener
}
- internal fun beforeNavigation() {
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ public fun beforeNavigation() {
listeners.forEach {
it.beforeNavigation()
}
}
- internal fun afterNavigation() {
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ public fun afterNavigation() {
listeners.forEach {
it.afterNavigation()
}
}
- internal fun onNavigatedTo(navigable: NavigableCompat<*>) {
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ public fun onNavigatedTo(navigable: NavigableCompat<*>) {
listeners.forEach {
it.onNavigatedTo(navigable)
}
}
- internal fun onNavigatedFrom(navigable: NavigableCompat<*>) {
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ public fun onNavigatedFrom(navigable: NavigableCompat<*>) {
listeners.forEach {
it.onNavigatedFrom(navigable)
}
diff --git a/magellan-rx/build.gradle b/magellan-rx/build.gradle
index 518aff8f..23292af7 100644
--- a/magellan-rx/build.gradle
+++ b/magellan-rx/build.gradle
@@ -5,14 +5,12 @@ group = GROUP
version = VERSION_NAME
android {
- compileSdkVersion Versions.compileSdkVersion
- buildToolsVersion Versions.buildToolsVersion
+ compileSdk Versions.compileSdkVersion
+ buildToolsVersion = Versions.buildToolsVersion
defaultConfig {
minSdkVersion Versions.minSdkVersion
targetSdkVersion Versions.targetSdkVersion
- versionCode 1
- versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -35,6 +33,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
}
kotlinOptions.allWarningsAsErrors = true
kotlinOptions.jvmTarget = "1.8"
+ kotlinOptions.useIR = true
}
dependencies {
@@ -43,6 +42,7 @@ dependencies {
implementation Dependencies.rxjava
implementation Dependencies.kotlinStdLib
+ implementation Dependencies.kotlinReflect
implementation Dependencies.inject
testImplementation Dependencies.junit
diff --git a/magellan-rx2/build.gradle b/magellan-rx2/build.gradle
index b8eabac6..979c42f7 100644
--- a/magellan-rx2/build.gradle
+++ b/magellan-rx2/build.gradle
@@ -5,14 +5,12 @@ group = GROUP
version = VERSION_NAME
android {
- compileSdkVersion Versions.compileSdkVersion
- buildToolsVersion Versions.buildToolsVersion
+ compileSdk Versions.compileSdkVersion
+ buildToolsVersion = Versions.buildToolsVersion
defaultConfig {
minSdkVersion Versions.minSdkVersion
targetSdkVersion Versions.targetSdkVersion
- versionCode 1
- versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -35,6 +33,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
}
kotlinOptions.allWarningsAsErrors = true
kotlinOptions.jvmTarget = "1.8"
+ kotlinOptions.useIR = true
}
dependencies {
@@ -42,6 +41,7 @@ dependencies {
implementation project(':magellan-library')
implementation Dependencies.kotlinStdLib
+ implementation Dependencies.kotlinReflect
implementation Dependencies.rxjava2
implementation Dependencies.inject
diff --git a/magellan-sample-advanced/build.gradle b/magellan-sample-advanced/build.gradle
index 8ebb58e9..6c89f5ce 100644
--- a/magellan-sample-advanced/build.gradle
+++ b/magellan-sample-advanced/build.gradle
@@ -3,8 +3,8 @@ apply plugin: 'kotlin-android'
apply plugin: "org.jetbrains.kotlin.kapt"
android {
- compileSdkVersion Versions.compileSdkVersion
- buildToolsVersion Versions.buildToolsVersion
+ compileSdk Versions.compileSdkVersion
+ buildToolsVersion = Versions.buildToolsVersion
defaultConfig {
applicationId "com.wealthfront.magellan.sample"
@@ -32,6 +32,7 @@ android {
kotlinOptions {
jvmTarget = "1.8"
+ useIR = true
}
packagingOptions {
diff --git a/magellan-sample/build.gradle b/magellan-sample/build.gradle
index 794015f1..ac108e68 100644
--- a/magellan-sample/build.gradle
+++ b/magellan-sample/build.gradle
@@ -3,8 +3,8 @@ apply plugin: 'kotlin-android'
apply plugin: "org.jetbrains.kotlin.kapt"
android {
- compileSdkVersion Versions.compileSdkVersion
- buildToolsVersion Versions.buildToolsVersion
+ compileSdk Versions.compileSdkVersion
+ buildToolsVersion = Versions.buildToolsVersion
defaultConfig {
applicationId "com.wealthfront.magellan.sample"
@@ -21,7 +21,12 @@ android {
}
buildFeatures {
- viewBinding = true
+ viewBinding true
+ compose true
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion Versions.composeVersion
}
lintOptions {
@@ -36,13 +41,28 @@ android {
}
}
+tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
+ kotlinOptions {
+ jvmTarget = "1.8"
+ useIR = true
+ }
+}
+
dependencies {
implementation project(':magellan-library')
implementation project(':magellan-legacy')
+ implementation project(':magellan-compose')
implementation Dependencies.appCompat
implementation Dependencies.kotlinStdLib
+ implementation Dependencies.kotlinReflect
implementation Dependencies.material
+ implementation Dependencies.composeUi
+ implementation Dependencies.composeUiUtil
+ implementation Dependencies.composeFoundation
+ implementation Dependencies.composeFoundationLayout
+ implementation Dependencies.composeMaterial
+ implementation Dependencies.composeUiTooling
implementation Dependencies.dagger
kapt Dependencies.daggerCompiler
diff --git a/magellan-sample/src/main/java/com/wealthfront/magellan/sample/Expedition.kt b/magellan-sample/src/main/java/com/wealthfront/magellan/sample/Expedition.kt
index 1c4dac84..133fff5c 100644
--- a/magellan-sample/src/main/java/com/wealthfront/magellan/sample/Expedition.kt
+++ b/magellan-sample/src/main/java/com/wealthfront/magellan/sample/Expedition.kt
@@ -1,20 +1,17 @@
package com.wealthfront.magellan.sample
import android.content.Context
-import com.wealthfront.magellan.core.Journey
+import com.example.magellan.compose.ComposeJourney
+import com.example.magellan.compose.ComposeStepWrappingViewStep
+import com.example.magellan.compose.transitions.showTransition
import com.wealthfront.magellan.lifecycle.lateinitLifecycle
import com.wealthfront.magellan.navigation.LoggingNavigableListener
import com.wealthfront.magellan.sample.App.Provider.appComponent
-import com.wealthfront.magellan.sample.databinding.ExpeditionBinding
-import com.wealthfront.magellan.transitions.ShowTransition
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
-class Expedition : Journey(
- ExpeditionBinding::inflate,
- ExpeditionBinding::container
-) {
+class Expedition : ComposeJourney() {
@set:Inject var navListener: LoggingNavigableListener by lateinitLifecycle()
@@ -24,6 +21,6 @@ class Expedition : Journey(
}
private fun goToSecondJourney() {
- navigator.goTo(SecondJourney(), ShowTransition())
+ navigator.goTo(ComposeStepWrappingViewStep(SecondJourney()), showTransition)
}
}
diff --git a/magellan-sample/src/main/java/com/wealthfront/magellan/sample/FirstJourney.kt b/magellan-sample/src/main/java/com/wealthfront/magellan/sample/FirstJourney.kt
index 29c6be4a..bcc64627 100644
--- a/magellan-sample/src/main/java/com/wealthfront/magellan/sample/FirstJourney.kt
+++ b/magellan-sample/src/main/java/com/wealthfront/magellan/sample/FirstJourney.kt
@@ -1,29 +1,44 @@
package com.wealthfront.magellan.sample
import android.content.Context
-import com.wealthfront.magellan.core.Journey
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Button
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.example.magellan.compose.ComposeJourney
+import com.example.magellan.compose.ComposeStepWrappingViewStep
+import com.example.magellan.compose.Displayable
import com.wealthfront.magellan.sample.App.Provider.appComponent
-import com.wealthfront.magellan.sample.databinding.FirstJourneyBinding
import javax.inject.Inject
class FirstJourney(
private val goToSecondJourney: () -> Unit
-) : Journey(FirstJourneyBinding::inflate, FirstJourneyBinding::container) {
+) : ComposeJourney() {
@Inject lateinit var toaster: Toaster
+ @Composable
+ override fun Content() {
+ Column {
+ Box(modifier = Modifier.padding(16.dp)) {
+ Button(onClick = { goToSecondJourney() }) {
+ Text("Start next journey")
+ }
+ }
+ Displayable(navigator)
+ }
+ }
+
override fun onCreate(context: Context) {
appComponent.inject(this)
navigator.goTo(IntroStep(::goToLearnMore))
}
- override fun onShow(context: Context, binding: FirstJourneyBinding) {
- binding.nextJourney.setOnClickListener {
- goToSecondJourney()
- }
- }
-
private fun goToLearnMore() {
- navigator.goTo(LearnMoreStep())
+ navigator.goTo(ComposeStepWrappingViewStep(LearnMoreStep()))
}
}
diff --git a/magellan-sample/src/main/java/com/wealthfront/magellan/sample/IntroStep.kt b/magellan-sample/src/main/java/com/wealthfront/magellan/sample/IntroStep.kt
index 118a1144..a341c202 100644
--- a/magellan-sample/src/main/java/com/wealthfront/magellan/sample/IntroStep.kt
+++ b/magellan-sample/src/main/java/com/wealthfront/magellan/sample/IntroStep.kt
@@ -1,16 +1,29 @@
package com.wealthfront.magellan.sample
-import android.content.Context
-import com.wealthfront.magellan.core.Step
-import com.wealthfront.magellan.sample.databinding.IntroBinding
+import androidx.compose.material.Button
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.tooling.preview.Devices
+import androidx.compose.ui.tooling.preview.Preview
+import com.example.magellan.compose.ComposeStep
internal class IntroStep(
private val goToLearnMore: () -> Unit
-) : Step(IntroBinding::inflate) {
+) : ComposeStep() {
- override fun onShow(context: Context, binding: IntroBinding) {
- binding.learnMore.setOnClickListener {
- goToLearnMore()
- }
+ @Composable
+ override fun Content() = IntroStepContent(goToLearnMore)
+}
+
+@Composable
+internal fun IntroStepContent(goToLearnMore: () -> Unit) {
+ Button(onClick = goToLearnMore) {
+ Text("Go to learn more")
}
}
+
+@Preview(device = Devices.PIXEL_3, name = "Intro step")
+@Composable
+internal fun IntroStepPreview() {
+ IntroStepContent { }
+}
diff --git a/magellan-sample/src/main/java/com/wealthfront/magellan/sample/MainActivity.kt b/magellan-sample/src/main/java/com/wealthfront/magellan/sample/MainActivity.kt
index de0c60cb..c3d8011d 100644
--- a/magellan-sample/src/main/java/com/wealthfront/magellan/sample/MainActivity.kt
+++ b/magellan-sample/src/main/java/com/wealthfront/magellan/sample/MainActivity.kt
@@ -1,19 +1,19 @@
package com.wealthfront.magellan.sample
import android.os.Bundle
-import androidx.appcompat.app.AppCompatActivity
-import com.wealthfront.magellan.lifecycle.setContentScreen
+import androidx.activity.ComponentActivity
+import com.example.magellan.compose.setContentNavigable
import com.wealthfront.magellan.sample.App.Provider.appComponent
import javax.inject.Inject
-class MainActivity : AppCompatActivity() {
+class MainActivity : ComponentActivity() {
@Inject lateinit var expedition: Expedition
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appComponent.inject(this)
- setContentScreen(expedition)
+ setContentNavigable(expedition)
}
override fun onBackPressed() {
diff --git a/magellan-support/build.gradle b/magellan-support/build.gradle
index ec6627e5..dc108065 100644
--- a/magellan-support/build.gradle
+++ b/magellan-support/build.gradle
@@ -4,14 +4,12 @@ group = GROUP
version = VERSION_NAME
android {
- compileSdkVersion Versions.compileSdkVersion
- buildToolsVersion Versions.buildToolsVersion
+ compileSdk Versions.compileSdkVersion
+ buildToolsVersion = Versions.buildToolsVersion
defaultConfig {
minSdkVersion Versions.minSdkVersion
targetSdkVersion Versions.targetSdkVersion
- versionCode 1
- versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
diff --git a/settings.gradle b/settings.gradle
index b1bb9570..486b3cda 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -6,3 +6,4 @@ include ':magellan-rx2'
include ':magellan-sample'
include ':magellan-sample-advanced'
include ':magellan-support'
+include ':magellan-compose'