Nav: First iteration integrating Appyx
This commit is contained in:
parent
4c88d8e3c2
commit
2de26a30d5
28 changed files with 566 additions and 280 deletions
|
|
@ -6,177 +6,48 @@
|
|||
package io.element.android.x
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.animation.AnimatedContentScope
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.navigation.NavHostController
|
||||
import com.airbnb.android.showkase.models.Showkase
|
||||
import com.airbnb.mvrx.compose.mavericksActivityViewModel
|
||||
import com.airbnb.mvrx.compose.mavericksViewModel
|
||||
import com.bumble.appyx.core.integration.NodeHost
|
||||
import com.bumble.appyx.core.integrationpoint.NodeComponentActivity
|
||||
import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
|
||||
import com.ramcosta.composedestinations.DestinationsNavHost
|
||||
import com.ramcosta.composedestinations.animations.defaults.RootNavGraphDefaultAnimations
|
||||
import com.ramcosta.composedestinations.animations.rememberAnimatedNavHostEngine
|
||||
import com.ramcosta.composedestinations.manualcomposablecalls.animatedComposable
|
||||
import com.ramcosta.composedestinations.navigation.dependency
|
||||
import com.ramcosta.composedestinations.spec.Route
|
||||
import io.element.android.x.core.compose.OnLifecycleEvent
|
||||
import io.element.android.x.core.di.DaggerComponentOwner
|
||||
import io.element.android.x.core.di.bindings
|
||||
import io.element.android.x.designsystem.ElementXTheme
|
||||
import io.element.android.x.destinations.OnBoardingScreenNavigationDestination
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import timber.log.Timber
|
||||
import io.element.android.x.di.AppBindings
|
||||
import io.element.android.x.node.RootFlowNode
|
||||
|
||||
private const val transitionAnimationDuration = 500
|
||||
class MainActivity : NodeComponentActivity(), DaggerComponentOwner {
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override val daggerComponent: Any
|
||||
get() = listOfNotNull((applicationContext as? DaggerComponentOwner)?.daggerComponent)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val appBindings = bindings<AppBindings>()
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
setContent {
|
||||
ElementXTheme {
|
||||
MainScreen(viewModel = mavericksActivityViewModel())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ShowkaseButton(
|
||||
isVisible: Boolean,
|
||||
onClick: () -> Unit,
|
||||
onCloseClicked: () -> Unit
|
||||
) {
|
||||
if (isVisible) {
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.padding(top = 32.dp, start = 16.dp),
|
||||
onClick = onClick
|
||||
) {
|
||||
Text(text = "Showkase Browser")
|
||||
IconButton(
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.size(16.dp),
|
||||
onClick = onCloseClicked,
|
||||
// A surface container using the 'background' color from the theme
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
Icon(imageVector = Icons.Filled.Close, contentDescription = "")
|
||||
NodeHost(integrationPoint = appyxIntegrationPoint) {
|
||||
RootFlowNode(
|
||||
buildContext = it,
|
||||
daggerComponentOwner = this,
|
||||
matrix = appBindings.matrix(),
|
||||
sessionComponentsOwner = appBindings.sessionComponentsOwner()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainScreen(viewModel: MainViewModel) {
|
||||
val startRoute = runBlocking {
|
||||
if (!viewModel.isLoggedIn()) {
|
||||
OnBoardingScreenNavigationDestination
|
||||
} else {
|
||||
viewModel.restoreSession()
|
||||
NavGraphs.root.startRoute
|
||||
}
|
||||
}
|
||||
|
||||
var isShowkaseButtonVisible by remember { mutableStateOf(BuildConfig.DEBUG) }
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.TopCenter) {
|
||||
MainContent(
|
||||
startRoute = startRoute
|
||||
)
|
||||
ShowkaseButton(
|
||||
isVisible = isShowkaseButtonVisible,
|
||||
onCloseClicked = { isShowkaseButtonVisible = false },
|
||||
onClick = { startActivity(Showkase.getBrowserIntent(this@MainActivity)) }
|
||||
)
|
||||
}
|
||||
OnLifecycleEvent { _, event ->
|
||||
Timber.v("OnLifecycleEvent: $event")
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainContent(startRoute: Route) {
|
||||
val engine = rememberAnimatedNavHostEngine(
|
||||
rootDefaultAnimations = RootNavGraphDefaultAnimations(
|
||||
enterTransition = {
|
||||
slideIntoContainer(
|
||||
AnimatedContentScope.SlideDirection.Left,
|
||||
animationSpec = tween(transitionAnimationDuration)
|
||||
)
|
||||
},
|
||||
exitTransition = {
|
||||
slideOutOfContainer(
|
||||
AnimatedContentScope.SlideDirection.Left,
|
||||
animationSpec = tween(transitionAnimationDuration)
|
||||
)
|
||||
},
|
||||
popEnterTransition = {
|
||||
slideIntoContainer(
|
||||
AnimatedContentScope.SlideDirection.Right,
|
||||
animationSpec = tween(transitionAnimationDuration)
|
||||
)
|
||||
},
|
||||
popExitTransition = {
|
||||
slideOutOfContainer(
|
||||
AnimatedContentScope.SlideDirection.Right,
|
||||
animationSpec = tween(transitionAnimationDuration)
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
val navController = engine.rememberNavController()
|
||||
LogNavigation(navController)
|
||||
|
||||
DestinationsNavHost(
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.background),
|
||||
engine = engine,
|
||||
navController = navController,
|
||||
navGraph = NavGraphs.root,
|
||||
startRoute = startRoute,
|
||||
dependenciesContainerBuilder = {
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LogNavigation(navController: NavHostController) {
|
||||
LaunchedEffect(key1 = navController) {
|
||||
navController.appCurrentDestinationFlow.collect {
|
||||
Timber.d("Navigating to ${it.route}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun MainContentPreview() {
|
||||
MainContent(startRoute = OnBoardingScreenNavigationDestination)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,90 +0,0 @@
|
|||
package io.element.android.x
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.ramcosta.composedestinations.navigation.popUpTo
|
||||
import io.element.android.x.core.di.bindings
|
||||
import io.element.android.x.destinations.ChangeServerScreenNavigationDestination
|
||||
import io.element.android.x.destinations.LoginScreenNavigationDestination
|
||||
import io.element.android.x.destinations.MessagesScreenNavigationDestination
|
||||
import io.element.android.x.destinations.OnBoardingScreenNavigationDestination
|
||||
import io.element.android.x.destinations.RoomListScreenNavigationDestination
|
||||
import io.element.android.x.di.AppBindings
|
||||
import io.element.android.x.features.login.LoginScreen
|
||||
import io.element.android.x.features.login.changeserver.ChangeServerScreen
|
||||
import io.element.android.x.features.messages.MessagesScreen
|
||||
import io.element.android.x.features.onboarding.OnBoardingScreen
|
||||
import io.element.android.x.features.roomlist.RoomListScreen
|
||||
import io.element.android.x.matrix.core.RoomId
|
||||
|
||||
@Destination
|
||||
@Composable
|
||||
fun OnBoardingScreenNavigation(navigator: DestinationsNavigator) {
|
||||
OnBoardingScreen(
|
||||
onSignUp = {
|
||||
// TODO
|
||||
},
|
||||
onSignIn = {
|
||||
navigator.navigate(LoginScreenNavigationDestination)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Destination
|
||||
@Composable
|
||||
fun LoginScreenNavigation(navigator: DestinationsNavigator) {
|
||||
val sessionComponentsOwner = LocalContext.current.bindings<AppBindings>().sessionComponentsOwner()
|
||||
LoginScreen(
|
||||
onChangeServer = {
|
||||
navigator.navigate(ChangeServerScreenNavigationDestination)
|
||||
},
|
||||
onLoginWithSuccess = {
|
||||
sessionComponentsOwner.create(it)
|
||||
navigator.navigate(RoomListScreenNavigationDestination) {
|
||||
popUpTo(OnBoardingScreenNavigationDestination) {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// TODO Create a subgraph in Login module
|
||||
@Destination
|
||||
@Composable
|
||||
fun ChangeServerScreenNavigation(navigator: DestinationsNavigator) {
|
||||
ChangeServerScreen(
|
||||
onChangeServerSuccess = {
|
||||
navigator.popBackStack()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@RootNavGraph(start = true)
|
||||
@Destination
|
||||
@Composable
|
||||
fun RoomListScreenNavigation(navigator: DestinationsNavigator) {
|
||||
val sessionComponentsOwner = LocalContext.current.bindings<AppBindings>().sessionComponentsOwner()
|
||||
RoomListScreen(
|
||||
onRoomClicked = { roomId: RoomId ->
|
||||
navigator.navigate(MessagesScreenNavigationDestination(roomId = roomId.value))
|
||||
},
|
||||
onSuccessLogout = {
|
||||
sessionComponentsOwner.releaseActiveSession()
|
||||
navigator.navigate(OnBoardingScreenNavigationDestination) {
|
||||
popUpTo(RoomListScreenNavigationDestination) {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Destination
|
||||
@Composable
|
||||
fun MessagesScreenNavigation(roomId: String, navigator: DestinationsNavigator) {
|
||||
MessagesScreen(roomId = roomId, onBackPressed = navigator::navigateUp)
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package io.element.android.x.component
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
internal fun ShowkaseButton(
|
||||
modifier: Modifier = Modifier,
|
||||
isVisible: Boolean,
|
||||
onClick: () -> Unit,
|
||||
onCloseClicked: () -> Unit
|
||||
) {
|
||||
if (isVisible) {
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.padding(top = 32.dp, start = 16.dp),
|
||||
onClick = onClick
|
||||
) {
|
||||
Text(text = "Showkase Browser")
|
||||
IconButton(
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.size(16.dp),
|
||||
onClick = onCloseClicked,
|
||||
) {
|
||||
Icon(imageVector = Icons.Filled.Close, contentDescription = "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ package io.element.android.x.di
|
|||
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import io.element.android.x.matrix.Matrix
|
||||
import io.element.android.x.node.LoggedInFlowNode
|
||||
import io.element.android.x.node.RootFlowNode
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@ContributesTo(AppScope::class)
|
||||
|
|
@ -9,4 +11,4 @@ interface AppBindings {
|
|||
fun coroutineScope(): CoroutineScope
|
||||
fun matrix(): Matrix
|
||||
fun sessionComponentsOwner(): SessionComponentsOwner
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,17 +3,18 @@ package io.element.android.x.di
|
|||
import android.content.Context
|
||||
import io.element.android.x.core.di.bindings
|
||||
import io.element.android.x.matrix.MatrixClient
|
||||
import io.element.android.x.matrix.core.SessionId
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
|
||||
@SingleIn(AppScope::class)
|
||||
class SessionComponentsOwner @Inject constructor(@ApplicationContext private val context: Context) {
|
||||
|
||||
private val sessionComponents = ConcurrentHashMap<String, SessionComponent>()
|
||||
private val sessionComponents = ConcurrentHashMap<SessionId, SessionComponent>()
|
||||
var activeSessionComponent: SessionComponent? = null
|
||||
private set
|
||||
|
||||
fun setActive(sessionId: String) {
|
||||
fun setActive(sessionId: SessionId) {
|
||||
val sessionComponent = sessionComponents[sessionId]
|
||||
if (activeSessionComponent != sessionComponent) {
|
||||
activeSessionComponent = sessionComponent
|
||||
|
|
@ -35,7 +36,7 @@ class SessionComponentsOwner @Inject constructor(@ApplicationContext private val
|
|||
}
|
||||
}
|
||||
|
||||
fun release(sessionId: String) {
|
||||
fun release(sessionId: SessionId) {
|
||||
val sessionComponent = sessionComponents.remove(sessionId)
|
||||
if (activeSessionComponent == sessionComponent) {
|
||||
activeSessionComponent = null
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class MatrixInitializer : Initializer<Unit> {
|
|||
}
|
||||
}
|
||||
|
||||
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
|
||||
override fun dependencies(): List<Class<out Initializer<*>>> = listOf(TimberInitializer::class.java)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,5 @@ class TimberInitializer : Initializer<Unit> {
|
|||
Timber.plant(Timber.DebugTree())
|
||||
}
|
||||
|
||||
override fun dependencies(): List<Class<out Initializer<*>>> =
|
||||
listOf(TimberInitializer::class.java)
|
||||
}
|
||||
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
package io.element.android.x.node
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.composable.Children
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.node.ParentNode
|
||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||
import com.bumble.appyx.navmodel.backstack.operation.pop
|
||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||
import io.element.android.x.core.di.viewModelSupportNode
|
||||
import io.element.android.x.features.messages.MessagesScreen
|
||||
import io.element.android.x.features.roomlist.RoomListScreen
|
||||
import io.element.android.x.matrix.core.RoomId
|
||||
import io.element.android.x.matrix.core.SessionId
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
class LoggedInFlowNode(
|
||||
buildContext: BuildContext,
|
||||
val sessionId: SessionId,
|
||||
private val backstack: BackStack<NavTarget> = BackStack(
|
||||
initialElement = NavTarget.RoomList,
|
||||
savedStateMap = buildContext.savedStateMap,
|
||||
),
|
||||
) : ParentNode<LoggedInFlowNode.NavTarget>(
|
||||
navModel = backstack,
|
||||
buildContext = buildContext
|
||||
) {
|
||||
|
||||
sealed interface NavTarget : Parcelable {
|
||||
@Parcelize
|
||||
object RoomList : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class Messages(val roomId: RoomId) : NavTarget
|
||||
}
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
NavTarget.RoomList -> viewModelSupportNode(buildContext) {
|
||||
RoomListScreen(
|
||||
onRoomClicked = { backstack.push(NavTarget.Messages(it)) }
|
||||
)
|
||||
}
|
||||
is NavTarget.Messages -> viewModelSupportNode(buildContext) {
|
||||
MessagesScreen(
|
||||
roomId = navTarget.roomId.value,
|
||||
onBackPressed = { backstack.pop() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
Children(navModel = backstack)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package io.element.android.x.node
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.composable.Children
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.node.ParentNode
|
||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||
import com.bumble.appyx.navmodel.backstack.operation.replace
|
||||
import io.element.android.x.core.di.viewModelSupportNode
|
||||
import io.element.android.x.features.login.node.LoginFlowNode
|
||||
import io.element.android.x.features.onboarding.OnBoardingScreen
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
class NotLoggedInFlowNode(
|
||||
buildContext: BuildContext,
|
||||
private val backstack: BackStack<NavTarget> = BackStack(
|
||||
initialElement = NavTarget.OnBoarding,
|
||||
savedStateMap = buildContext.savedStateMap,
|
||||
),
|
||||
) : ParentNode<NotLoggedInFlowNode.NavTarget>(
|
||||
navModel = backstack,
|
||||
buildContext = buildContext
|
||||
) {
|
||||
|
||||
sealed interface NavTarget : Parcelable {
|
||||
@Parcelize
|
||||
object OnBoarding : NavTarget
|
||||
|
||||
@Parcelize
|
||||
object LoginFlow : NavTarget
|
||||
}
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
NavTarget.OnBoarding -> viewModelSupportNode(buildContext) {
|
||||
OnBoardingScreen(
|
||||
onSignIn = { backstack.replace(NavTarget.LoginFlow) }
|
||||
)
|
||||
}
|
||||
NavTarget.LoginFlow -> LoginFlowNode(buildContext)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
Children(navModel = backstack)
|
||||
}
|
||||
}
|
||||
136
app/src/main/java/io/element/android/x/node/RootFlowNode.kt
Normal file
136
app/src/main/java/io/element/android/x/node/RootFlowNode.kt
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
package io.element.android.x.node
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.android.showkase.models.Showkase
|
||||
import com.bumble.appyx.core.children.whenChildAttached
|
||||
import com.bumble.appyx.core.clienthelper.interactor.Interactor
|
||||
import com.bumble.appyx.core.composable.Children
|
||||
import com.bumble.appyx.core.lifecycle.subscribe
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.node.ParentNode
|
||||
import com.bumble.appyx.core.node.node
|
||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||
import com.bumble.appyx.navmodel.backstack.operation.replace
|
||||
import io.element.android.x.BuildConfig
|
||||
import io.element.android.x.component.ShowkaseButton
|
||||
import io.element.android.x.core.di.DaggerComponentOwner
|
||||
import io.element.android.x.di.SessionComponentsOwner
|
||||
import io.element.android.x.getBrowserIntent
|
||||
import io.element.android.x.matrix.Matrix
|
||||
import io.element.android.x.matrix.core.SessionId
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import timber.log.Timber
|
||||
|
||||
class SessionComponentsOwnerInteractor(private val sessionComponentsOwner: SessionComponentsOwner) : Interactor<RootFlowNode>() {
|
||||
override fun onCreate(lifecycle: Lifecycle) {
|
||||
lifecycle.subscribe(onCreate = {
|
||||
whenChildAttached { commonLifecycle: Lifecycle, child: LoggedInFlowNode ->
|
||||
Timber.v("LoggedInFlowNode attached: ${child.sessionId} ")
|
||||
commonLifecycle.subscribe(
|
||||
onDestroy = {
|
||||
Timber.v("LoggedInFlowNode destroyed: ${child.sessionId}")
|
||||
sessionComponentsOwner.release(child.sessionId)
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class RootFlowNode(
|
||||
buildContext: BuildContext,
|
||||
private val backstack: BackStack<NavTarget> = BackStack(
|
||||
initialElement = NavTarget.SplashScreen,
|
||||
savedStateMap = buildContext.savedStateMap,
|
||||
),
|
||||
private val daggerComponentOwner: DaggerComponentOwner,
|
||||
private val matrix: Matrix,
|
||||
private val sessionComponentsOwner: SessionComponentsOwner,
|
||||
) :
|
||||
ParentNode<RootFlowNode.NavTarget>(
|
||||
navModel = backstack,
|
||||
buildContext = buildContext,
|
||||
plugins = listOf(SessionComponentsOwnerInteractor(sessionComponentsOwner)),
|
||||
),
|
||||
|
||||
DaggerComponentOwner by daggerComponentOwner {
|
||||
|
||||
init {
|
||||
matrix.isLoggedIn()
|
||||
.distinctUntilChanged()
|
||||
.onEach { isLoggedIn ->
|
||||
if (isLoggedIn) {
|
||||
val matrixClient = matrix.restoreSession()
|
||||
if (matrixClient == null) {
|
||||
backstack.replace(NavTarget.NotLoggedInFlow)
|
||||
} else {
|
||||
matrixClient.startSync()
|
||||
sessionComponentsOwner.create(matrixClient)
|
||||
backstack.replace(NavTarget.LoggedInFlow(matrixClient.sessionId))
|
||||
}
|
||||
} else {
|
||||
backstack.replace(NavTarget.NotLoggedInFlow)
|
||||
}
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
var isShowkaseButtonVisible by remember { mutableStateOf(BuildConfig.DEBUG) }
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize(),
|
||||
contentAlignment = Alignment.TopCenter,
|
||||
) {
|
||||
Children(navModel = backstack)
|
||||
val context = LocalContext.current
|
||||
ShowkaseButton(
|
||||
isVisible = isShowkaseButtonVisible,
|
||||
onCloseClicked = { isShowkaseButtonVisible = false },
|
||||
onClick = { startActivity(context, Showkase.getBrowserIntent(context), null) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface NavTarget : Parcelable {
|
||||
@Parcelize
|
||||
object SplashScreen : NavTarget
|
||||
|
||||
@Parcelize
|
||||
object NotLoggedInFlow : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class LoggedInFlow(val sessionId: SessionId) : NavTarget
|
||||
}
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
is NavTarget.LoggedInFlow -> LoggedInFlowNode(buildContext, navTarget.sessionId)
|
||||
NavTarget.NotLoggedInFlow -> NotLoggedInFlowNode(buildContext)
|
||||
NavTarget.SplashScreen -> node(buildContext) {
|
||||
Box(modifier = it.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue