Merge pull request #173 from vector-im/feature/fga/improve_node_architecture

Feature/fga/improve node architecture
This commit is contained in:
ganfra 2023-03-09 17:55:17 +01:00 committed by GitHub
commit 08f5f6edaa
557 changed files with 2189 additions and 1743 deletions

View file

@ -17,7 +17,7 @@
@file:Suppress("UnstableApiUsage") @file:Suppress("UnstableApiUsage")
import com.android.build.api.variant.FilterConfiguration.FilterType.ABI import com.android.build.api.variant.FilterConfiguration.FilterType.ABI
import extension.allFeatures import extension.allFeaturesImpl
import extension.allLibraries import extension.allLibraries
// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed // TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
@ -26,8 +26,8 @@ plugins {
id("io.element.android-compose-application") id("io.element.android-compose-application")
alias(libs.plugins.stem) alias(libs.plugins.stem)
alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.android)
alias(libs.plugins.ksp)
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
alias(libs.plugins.kapt) alias(libs.plugins.kapt)
id("com.google.firebase.appdistribution") version "4.0.0" id("com.google.firebase.appdistribution") version "4.0.0"
id("org.jetbrains.kotlinx.knit") version "0.4.0" id("org.jetbrains.kotlinx.knit") version "0.4.0"
@ -201,12 +201,13 @@ knit {
dependencies { dependencies {
allLibraries() allLibraries()
allFeatures() allFeaturesImpl()
implementation(projects.libraries.matrix.impl) implementation(projects.libraries.matrix.impl)
implementation(projects.libraries.dateformatter.impl) implementation(projects.libraries.dateformatter.impl)
implementation(projects.libraries.sessionStorage.impl) implementation(projects.libraries.sessionStorage.impl)
implementation(projects.tests.uitests) implementation(projects.tests.uitests)
implementation(projects.anvilannotations) implementation(projects.anvilannotations)
implementation(projects.appnav)
anvil(projects.anvilcodegen) anvil(projects.anvilcodegen)
// https://developer.android.com/studio/write/java8-support#library-desugaring-versions // https://developer.android.com/studio/write/java8-support#library-desugaring-versions

View file

@ -18,9 +18,9 @@ package io.element.android.x
import android.app.Application import android.app.Application
import androidx.startup.AppInitializer import androidx.startup.AppInitializer
import io.element.android.libraries.di.DaggerComponentOwner
import io.element.android.x.di.AppComponent import io.element.android.x.di.AppComponent
import io.element.android.x.di.DaggerAppComponent import io.element.android.x.di.DaggerAppComponent
import io.element.android.libraries.di.DaggerComponentOwner
import io.element.android.x.info.logApplicationInfo import io.element.android.x.info.logApplicationInfo
import io.element.android.x.initializer.CrashInitializer import io.element.android.x.initializer.CrashInitializer
import io.element.android.x.initializer.MatrixInitializer import io.element.android.x.initializer.MatrixInitializer

View file

@ -29,9 +29,7 @@ import com.bumble.appyx.core.integration.NodeHost
import com.bumble.appyx.core.integrationpoint.NodeComponentActivity import com.bumble.appyx.core.integrationpoint.NodeComponentActivity
import io.element.android.libraries.architecture.bindings import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.designsystem.theme.ElementTheme import io.element.android.libraries.designsystem.theme.ElementTheme
import io.element.android.libraries.di.DaggerComponentOwner
import io.element.android.x.di.AppBindings import io.element.android.x.di.AppBindings
import io.element.android.x.node.RootFlowNode
class MainActivity : NodeComponentActivity() { class MainActivity : NodeComponentActivity() {
@ -47,13 +45,7 @@ class MainActivity : NodeComponentActivity() {
modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background), modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background),
) { ) {
NodeHost(integrationPoint = appyxIntegrationPoint) { NodeHost(integrationPoint = appyxIntegrationPoint) {
RootFlowNode( MainNode(it, appBindings.mainDaggerComponentOwner())
buildContext = it,
appComponentOwner = applicationContext as DaggerComponentOwner,
authenticationService = appBindings.authenticationService(),
presenter = appBindings.rootPresenter(),
matrixClientsHolder = appBindings.matrixClientsHolder()
)
} }
} }
} }

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.x
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.navigation.model.permanent.PermanentNavModel
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.ParentNode
import io.element.android.appnav.LoggedInFlowNode
import io.element.android.appnav.RoomFlowNode
import io.element.android.appnav.RootFlowNode
import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.DaggerComponentOwner
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.x.di.MainDaggerComponentsOwner
import io.element.android.x.di.RoomComponent
import io.element.android.x.di.SessionComponent
import kotlinx.parcelize.Parcelize
class MainNode(
buildContext: BuildContext,
private val mainDaggerComponentOwner: MainDaggerComponentsOwner,
) :
ParentNode<MainNode.RootNavTarget>(
navModel = PermanentNavModel(
navTargets = setOf(RootNavTarget),
savedStateMap = buildContext.savedStateMap,
),
buildContext = buildContext,
),
DaggerComponentOwner by mainDaggerComponentOwner {
private val loggedInFlowNodeCallback = object : LoggedInFlowNode.LifecycleCallback {
override fun onFlowCreated(client: MatrixClient) {
val component = bindings<SessionComponent.ParentBindings>().sessionComponentBuilder().client(client).build()
mainDaggerComponentOwner.addComponent(client.sessionId.value, component)
}
override fun onFlowReleased(client: MatrixClient) {
mainDaggerComponentOwner.removeComponent(client.sessionId.value)
}
}
private val roomFlowNodeCallback = object : RoomFlowNode.LifecycleCallback {
override fun onFlowCreated(room: MatrixRoom) {
val component = bindings<RoomComponent.ParentBindings>().roomComponentBuilder().room(room).build()
mainDaggerComponentOwner.addComponent(room.roomId.value, component)
}
override fun onFlowReleased(room: MatrixRoom) {
mainDaggerComponentOwner.removeComponent(room.roomId.value)
}
}
override fun resolve(navTarget: RootNavTarget, buildContext: BuildContext): Node {
return createNode<RootFlowNode>(buildContext, plugins = listOf(loggedInFlowNodeCallback, roomFlowNodeCallback))
}
@Composable
override fun View(modifier: Modifier) {
Children(navModel = navModel)
}
@Parcelize
object RootNavTarget : Parcelable
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,13 +17,11 @@
package io.element.android.x.di package io.element.android.x.di
import com.squareup.anvil.annotations.ContributesTo import com.squareup.anvil.annotations.ContributesTo
import io.element.android.appnav.di.MatrixClientsHolder
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.x.root.RootPresenter
@ContributesTo(AppScope::class) @ContributesTo(AppScope::class)
interface AppBindings { interface AppBindings {
fun rootPresenter(): RootPresenter
fun authenticationService(): MatrixAuthenticationService
fun matrixClientsHolder(): MatrixClientsHolder fun matrixClientsHolder(): MatrixClientsHolder
fun mainDaggerComponentOwner(): MainDaggerComponentsOwner
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,8 +20,6 @@ import android.content.Context
import com.squareup.anvil.annotations.ContributesTo import com.squareup.anvil.annotations.ContributesTo
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import io.element.android.features.rageshake.reporter.BugReporter
import io.element.android.features.rageshake.reporter.DefaultBugReporter
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.ApplicationContext
@ -60,7 +58,4 @@ object AppModule {
diffUpdateDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() diffUpdateDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
) )
} }
@Provides
fun providesBugReporter(bugReporter: DefaultBugReporter): BugReporter = bugReporter
} }

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.x.di
import android.content.Context
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.di.DaggerComponentOwner
import io.element.android.libraries.di.SingleIn
import javax.inject.Inject
@SingleIn(AppScope::class)
class MainDaggerComponentsOwner @Inject constructor(@ApplicationContext context: Context) : DaggerComponentOwner {
private val daggerComponents = LinkedHashMap<String, Any>().apply {
put("app", (context as DaggerComponentOwner).daggerComponent)
}
fun addComponent(identifier: String, component: Any) {
daggerComponents[identifier] = component
}
fun removeComponent(identifier: String) {
daggerComponents.remove(identifier)
}
override val daggerComponent: Any
get() = daggerComponents.values.reversed()
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -28,7 +28,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
@SingleIn(SessionScope::class) @SingleIn(SessionScope::class)
@MergeSubcomponent(SessionScope::class) @MergeSubcomponent(SessionScope::class)
interface SessionComponent : NodeFactoriesBindings, RoomComponent.ParentBindings { interface SessionComponent : NodeFactoriesBindings {
@Subcomponent.Builder @Subcomponent.Builder
interface Builder { interface Builder {

View file

@ -18,7 +18,7 @@ package io.element.android.x.initializer
import android.content.Context import android.content.Context
import androidx.startup.Initializer import androidx.startup.Initializer
import io.element.android.features.rageshake.crash.VectorUncaughtExceptionHandler import io.element.android.features.rageshake.impl.crash.VectorUncaughtExceptionHandler
class CrashInitializer : Initializer<Unit> { class CrashInitializer : Initializer<Unit> {

View file

@ -18,7 +18,7 @@ package io.element.android.x.initializer
import android.content.Context import android.content.Context
import androidx.startup.Initializer import androidx.startup.Initializer
import io.element.android.features.rageshake.logs.VectorFileLogger import io.element.android.features.rageshake.impl.logs.VectorFileLogger
import io.element.android.x.BuildConfig import io.element.android.x.BuildConfig
import timber.log.Timber import timber.log.Timber

View file

@ -1,70 +0,0 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.x.root
import io.element.android.features.rageshake.reporter.BugReporter
import io.element.android.features.rageshake.reporter.BugReporterListener
import io.element.android.features.rageshake.reporter.ReportType
import io.element.android.libraries.matrix.test.A_FAILURE_REASON
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
// TODO Remove this duplicated class when we will rework modules.
class FakeBugReporter(val mode: FakeBugReporterMode = FakeBugReporterMode.Success) : BugReporter {
override fun sendBugReport(
coroutineScope: CoroutineScope,
reportType: ReportType,
withDevicesLogs: Boolean,
withCrashLogs: Boolean,
withKeyRequestHistory: Boolean,
withScreenshot: Boolean,
theBugDescription: String,
serverVersion: String,
canContact: Boolean,
customFields: Map<String, String>?,
listener: BugReporterListener?,
) {
coroutineScope.launch {
delay(100)
listener?.onProgress(0)
delay(100)
listener?.onProgress(50)
delay(100)
when (mode) {
FakeBugReporterMode.Success -> Unit
FakeBugReporterMode.Failure -> {
listener?.onUploadFailed(A_FAILURE_REASON)
return@launch
}
FakeBugReporterMode.Cancel -> {
listener?.onUploadCancelled()
return@launch
}
}
listener?.onProgress(100)
delay(100)
listener?.onUploadSucceed(null)
}
}
}
enum class FakeBugReporterMode {
Success,
Failure,
Cancel
}

View file

@ -1,50 +0,0 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.x.root
import io.element.android.features.rageshake.crash.CrashDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
const val A_CRASH_DATA = "Some crash data"
// TODO Remove this duplicated class when we will rework modules.
class FakeCrashDataStore(
crashData: String = "",
appHasCrashed: Boolean = false,
) : CrashDataStore {
private val appHasCrashedFlow = MutableStateFlow(appHasCrashed)
private val crashDataFlow = MutableStateFlow(crashData)
override fun setCrashData(crashData: String) {
crashDataFlow.value = crashData
}
override suspend fun resetAppHasCrashed() {
appHasCrashedFlow.value = false
}
override fun appHasCrashed(): Flow<Boolean> = appHasCrashedFlow
override fun crashInfo(): Flow<String> = crashDataFlow
override suspend fun reset() {
appHasCrashedFlow.value = false
crashDataFlow.value = ""
}
}

View file

@ -1,44 +0,0 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.x.root
import io.element.android.features.rageshake.rageshake.RageShake
// TODO Remove this duplicated class when we will rework modules.
class FakeRageShake(
private var isAvailableValue: Boolean = true
) : RageShake {
private var interceptor: (() -> Unit)? = null
override fun isAvailable() = isAvailableValue
override fun start(sensitivity: Float) {
}
override fun stop() {
}
override fun setSensitivity(sensitivity: Float) {
}
override fun setInterceptor(interceptor: (() -> Unit)?) {
this.interceptor = interceptor
}
fun triggerPhoneRageshake() = interceptor?.invoke()
}

View file

@ -1,46 +0,0 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.x.root
import io.element.android.features.rageshake.rageshake.RageshakeDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
const val A_SENSITIVITY = 1f
// TODO Remove this duplicated class when we will rework modules.
class FakeRageshakeDataStore(
isEnabled: Boolean = true,
sensitivity: Float = A_SENSITIVITY,
) : RageshakeDataStore {
private val isEnabledFlow = MutableStateFlow(isEnabled)
override fun isEnabled(): Flow<Boolean> = isEnabledFlow
override suspend fun setIsEnabled(isEnabled: Boolean) {
isEnabledFlow.value = isEnabled
}
private val sensitivityFlow = MutableStateFlow(sensitivity)
override fun sensitivity(): Flow<Float> = sensitivityFlow
override suspend fun setSensitivity(sensitivity: Float) {
sensitivityFlow.value = sensitivity
}
override suspend fun reset() = Unit
}

59
appnav/build.gradle.kts Normal file
View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("UnstableApiUsage")
import extension.allFeaturesApi
// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id("io.element.android-compose-library")
alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
alias(libs.plugins.kapt)
id("kotlin-parcelize")
}
android {
namespace = "io.element.android.appnav"
}
dependencies {
implementation(projects.anvilannotations)
anvil(projects.anvilcodegen)
implementation(libs.dagger)
kapt(libs.dagger.compiler)
allFeaturesApi()
implementation(projects.libraries.core)
implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.matrixui)
implementation(projects.tests.uitests)
implementation(libs.coil)
testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test)
testImplementation(libs.molecule.runtime)
testImplementation(libs.test.truth)
testImplementation(libs.test.turbine)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.features.rageshake.test)
testImplementation(projects.features.rageshake.impl)
}

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.x.node package io.element.android.appnav
import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.NewRoot import com.bumble.appyx.navmodel.backstack.operation.NewRoot

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.x.node package io.element.android.appnav
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -27,72 +27,79 @@ import com.bumble.appyx.core.composable.Children
import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.lifecycle.subscribe
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node 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.core.node.node
import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.core.plugin.plugins
import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.push import com.bumble.appyx.navmodel.backstack.operation.push
import io.element.android.features.createroom.CreateRoomFlowNode import dagger.assisted.Assisted
import io.element.android.features.preferences.PreferencesFlowNode import dagger.assisted.AssistedInject
import io.element.android.features.roomlist.RoomListNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.createroom.api.CreateRoomEntryPoint
import io.element.android.features.preferences.api.PreferencesEntryPoint
import io.element.android.features.roomlist.api.RoomListEntryPoint
import io.element.android.libraries.architecture.BackstackNode
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.architecture.bindings import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.di.DaggerComponentOwner import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.ui.di.MatrixUIBindings import io.element.android.libraries.matrix.ui.di.MatrixUIBindings
import io.element.android.x.di.SessionComponent
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
class LoggedInFlowNode( @ContributesNode(AppScope::class)
buildContext: BuildContext, class LoggedInFlowNode @AssistedInject constructor(
val sessionId: SessionId, @Assisted buildContext: BuildContext,
private val matrixClient: MatrixClient, @Assisted plugins: List<Plugin>,
private val onOpenBugReport: () -> Unit, private val roomListEntryPoint: RoomListEntryPoint,
private val backstack: BackStack<NavTarget> = BackStack( private val preferencesEntryPoint: PreferencesEntryPoint,
private val createRoomEntryPoint: CreateRoomEntryPoint,
) : BackstackNode<LoggedInFlowNode.NavTarget>(
backstack = BackStack(
initialElement = NavTarget.RoomList, initialElement = NavTarget.RoomList,
savedStateMap = buildContext.savedStateMap, savedStateMap = buildContext.savedStateMap,
), ),
) : ParentNode<LoggedInFlowNode.NavTarget>( buildContext = buildContext,
navModel = backstack, plugins = plugins
buildContext = buildContext ) {
), DaggerComponentOwner {
override val daggerComponent: Any by lazy { interface Callback : Plugin {
parent!!.bindings<SessionComponent.ParentBindings>().sessionComponentBuilder().client(matrixClient).build() fun onOpenBugReport() = Unit
} }
interface LifecycleCallback : NodeLifecycleCallback {
fun onFlowCreated(client: MatrixClient) = Unit
fun onFlowReleased(client: MatrixClient) = Unit
}
data class Inputs(
val matrixClient: MatrixClient
) : NodeInputs
private val inputs: Inputs = inputs()
override fun onBuilt() { override fun onBuilt() {
super.onBuilt() super.onBuilt()
lifecycle.subscribe( lifecycle.subscribe(
onCreate = { onCreate = {
plugins<LifecycleCallback>().forEach { it.onFlowCreated(inputs.matrixClient) }
val imageLoaderFactory = bindings<MatrixUIBindings>().loggedInImageLoaderFactory() val imageLoaderFactory = bindings<MatrixUIBindings>().loggedInImageLoaderFactory()
Coil.setImageLoader(imageLoaderFactory) Coil.setImageLoader(imageLoaderFactory)
matrixClient.startSync() inputs.matrixClient.startSync()
}, },
onDestroy = { onDestroy = {
val imageLoaderFactory = bindings<MatrixUIBindings>().notLoggedInImageLoaderFactory() val imageLoaderFactory = bindings<MatrixUIBindings>().notLoggedInImageLoaderFactory()
Coil.setImageLoader(imageLoaderFactory) Coil.setImageLoader(imageLoaderFactory)
plugins<LifecycleCallback>().forEach { it.onFlowReleased(inputs.matrixClient) }
} }
) )
} }
private val roomListCallback = object : RoomListNode.Callback {
override fun onRoomClicked(roomId: RoomId) {
backstack.push(NavTarget.Room(roomId))
}
override fun onSettingsClicked() {
backstack.push(NavTarget.Settings)
}
override fun onCreateRoomClicked() {
backstack.push(NavTarget.CreateRoom)
}
}
sealed interface NavTarget : Parcelable { sealed interface NavTarget : Parcelable {
@Parcelize @Parcelize
object RoomList : NavTarget object RoomList : NavTarget
@ -110,10 +117,26 @@ class LoggedInFlowNode(
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) { return when (navTarget) {
NavTarget.RoomList -> { NavTarget.RoomList -> {
createNode<RoomListNode>(buildContext, plugins = listOf(roomListCallback)) val callback = object : RoomListEntryPoint.Callback {
override fun onRoomClicked(roomId: RoomId) {
backstack.push(NavTarget.Room(roomId))
}
override fun onSettingsClicked() {
backstack.push(NavTarget.Settings)
}
override fun onCreateRoomClicked() {
backstack.push(NavTarget.CreateRoom)
}
}
roomListEntryPoint
.nodeBuilder(this, buildContext)
.callback(callback)
.build()
} }
is NavTarget.Room -> { is NavTarget.Room -> {
val room = matrixClient.getRoom(roomId = navTarget.roomId) val room = inputs.matrixClient.getRoom(roomId = navTarget.roomId)
if (room == null) { if (room == null) {
// TODO CREATE UNKNOWN ROOM NODE // TODO CREATE UNKNOWN ROOM NODE
node(buildContext) { node(buildContext) {
@ -122,14 +145,23 @@ class LoggedInFlowNode(
} }
} }
} else { } else {
RoomFlowNode(buildContext, room) val nodeLifecycleCallbacks = plugins<NodeLifecycleCallback>()
val inputs = RoomFlowNode.Inputs(room)
createNode<RoomFlowNode>(buildContext, plugins = listOf(inputs) + nodeLifecycleCallbacks)
} }
} }
NavTarget.Settings -> { NavTarget.Settings -> {
PreferencesFlowNode(buildContext, onOpenBugReport) val callback = object : PreferencesEntryPoint.Callback {
override fun onOpenBugReport() {
plugins<Callback>().forEach { it.onOpenBugReport() }
}
}
preferencesEntryPoint.nodeBuilder(this, buildContext)
.callback(callback)
.build()
} }
NavTarget.CreateRoom -> { NavTarget.CreateRoom -> {
CreateRoomFlowNode(buildContext) createRoomEntryPoint.createNode(this, buildContext)
} }
} }
} }

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.appnav
import com.bumble.appyx.core.plugin.Plugin
interface NodeLifecycleCallback : Plugin

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.x.node package io.element.android.appnav
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -23,27 +23,34 @@ import com.bumble.appyx.core.composable.Children
import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.lifecycle.subscribe
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.ParentNode import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.core.node.node
import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.push import com.bumble.appyx.navmodel.backstack.operation.push
import io.element.android.features.login.LoginFlowNode import dagger.assisted.Assisted
import io.element.android.features.onboarding.OnBoardingScreen import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.login.api.LoginEntryPoint
import io.element.android.features.onboarding.api.OnBoardingEntryPoint
import io.element.android.libraries.architecture.BackstackNode
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.di.AppScope
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import timber.log.Timber import timber.log.Timber
class NotLoggedInFlowNode( @ContributesNode(AppScope::class)
buildContext: BuildContext, class NotLoggedInFlowNode @AssistedInject constructor(
private val backstack: BackStack<NavTarget> = BackStack( @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val onBoardingEntryPoint: OnBoardingEntryPoint,
private val loginEntryPoint: LoginEntryPoint,
) : BackstackNode<NotLoggedInFlowNode.NavTarget>(
backstack = BackStack(
initialElement = NavTarget.OnBoarding, initialElement = NavTarget.OnBoarding,
savedStateMap = buildContext.savedStateMap, savedStateMap = buildContext.savedStateMap
), ),
) : ParentNode<NotLoggedInFlowNode.NavTarget>( buildContext = buildContext,
navModel = backstack, plugins = plugins,
buildContext = buildContext
) { ) {
init { init {
lifecycle.subscribe( lifecycle.subscribe(
onCreate = { Timber.v("OnCreate") }, onCreate = { Timber.v("OnCreate") },
@ -61,12 +68,24 @@ class NotLoggedInFlowNode(
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) { return when (navTarget) {
NavTarget.OnBoarding -> node(buildContext) { NavTarget.OnBoarding -> {
OnBoardingScreen( val callback = object : OnBoardingEntryPoint.Callback {
onSignIn = { backstack.push(NavTarget.LoginFlow) } override fun onSignUp() {
) //NOOP
}
override fun onSignIn() {
backstack.push(NavTarget.LoginFlow)
}
}
onBoardingEntryPoint
.nodeBuilder(this, buildContext)
.callback(callback)
.build()
}
NavTarget.LoginFlow -> {
loginEntryPoint.createNode(this, buildContext)
} }
NavTarget.LoginFlow -> LoginFlowNode(buildContext)
} }
} }

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.x.node package io.element.android.appnav
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -23,43 +23,64 @@ import com.bumble.appyx.core.composable.Children
import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.lifecycle.subscribe
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.ParentNode import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.core.plugin.plugins
import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.BackStack
import io.element.android.features.messages.MessagesNode import dagger.assisted.Assisted
import io.element.android.libraries.architecture.bindings import dagger.assisted.AssistedInject
import io.element.android.libraries.architecture.createNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.di.DaggerComponentOwner import io.element.android.features.messages.api.MessagesEntryPoint
import io.element.android.libraries.architecture.BackstackNode
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.x.di.RoomComponent
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import timber.log.Timber import timber.log.Timber
class RoomFlowNode( @ContributesNode(SessionScope::class)
buildContext: BuildContext, class RoomFlowNode @AssistedInject constructor(
private val room: MatrixRoom, @Assisted buildContext: BuildContext,
private val backstack: BackStack<NavTarget> = BackStack( @Assisted plugins: List<Plugin>,
private val messagesEntryPoint: MessagesEntryPoint,
) : BackstackNode<RoomFlowNode.NavTarget>(
backstack = BackStack(
initialElement = NavTarget.Messages, initialElement = NavTarget.Messages,
savedStateMap = buildContext.savedStateMap, savedStateMap = buildContext.savedStateMap,
), ),
) : ParentNode<RoomFlowNode.NavTarget>( buildContext = buildContext,
navModel = backstack, plugins = plugins,
buildContext = buildContext ) {
), DaggerComponentOwner {
override val daggerComponent: Any by lazy { interface LifecycleCallback : NodeLifecycleCallback {
parent!!.bindings<RoomComponent.ParentBindings>().roomComponentBuilder().room(room).build() fun onFlowCreated(room: MatrixRoom) = Unit
fun onFlowReleased(room: MatrixRoom) = Unit
} }
data class Inputs(
val room: MatrixRoom,
) : NodeInputs
private val inputs: Inputs = inputs()
init { init {
lifecycle.subscribe( lifecycle.subscribe(
onCreate = { Timber.v("OnCreate") }, onCreate = {
onDestroy = { Timber.v("OnDestroy") } Timber.v("OnCreate")
plugins<LifecycleCallback>().forEach { it.onFlowCreated(inputs.room) }
},
onDestroy = {
Timber.v("OnDestroy")
plugins<LifecycleCallback>().forEach { it.onFlowReleased(inputs.room) }
}
) )
} }
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) { return when (navTarget) {
NavTarget.Messages -> createNode<MessagesNode>(buildContext) NavTarget.Messages -> {
messagesEntryPoint.createNode(this, buildContext)
}
} }
} }

View file

@ -14,58 +14,66 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.x.node package io.element.android.appnav
import android.app.Activity
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.composable.Children
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node 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.core.node.node
import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.core.plugin.plugins
import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.newRoot import com.bumble.appyx.navmodel.backstack.operation.newRoot
import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.pop
import com.bumble.appyx.navmodel.backstack.operation.push import com.bumble.appyx.navmodel.backstack.operation.push
import io.element.android.features.rageshake.bugreport.BugReportNode import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.appnav.di.MatrixClientsHolder
import io.element.android.appnav.root.RootPresenter
import io.element.android.appnav.root.RootView
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
import io.element.android.libraries.architecture.BackstackNode
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.di.DaggerComponentOwner import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.x.di.MatrixClientsHolder import io.element.android.tests.uitests.openShowkase
import io.element.android.x.root.RootPresenter
import io.element.android.x.root.RootView
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import timber.log.Timber import timber.log.Timber
class RootFlowNode( @ContributesNode(AppScope::class)
private val buildContext: BuildContext, class RootFlowNode @AssistedInject constructor(
private val backstack: BackStack<NavTarget> = BackStack( @Assisted val buildContext: BuildContext,
initialElement = NavTarget.SplashScreen, @Assisted plugins: List<Plugin>,
savedStateMap = buildContext.savedStateMap,
),
private val appComponentOwner: DaggerComponentOwner,
private val authenticationService: MatrixAuthenticationService, private val authenticationService: MatrixAuthenticationService,
private val matrixClientsHolder: MatrixClientsHolder, private val matrixClientsHolder: MatrixClientsHolder,
private val presenter: RootPresenter private val presenter: RootPresenter,
private val bugReportEntryPoint: BugReportEntryPoint,
) : ) :
ParentNode<RootFlowNode.NavTarget>( BackstackNode<RootFlowNode.NavTarget>(
navModel = backstack, backstack = BackStack(
buildContext = buildContext initialElement = NavTarget.SplashScreen,
), savedStateMap = buildContext.savedStateMap,
),
DaggerComponentOwner by appComponentOwner { buildContext = buildContext,
plugins = plugins
) {
override fun onBuilt() { override fun onBuilt() {
super.onBuilt() super.onBuilt()
@ -127,11 +135,17 @@ class RootFlowNode(
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val activity = LocalContext.current as Activity
fun openShowkase() {
openShowkase(activity)
}
val state = presenter.present() val state = presenter.present()
RootView( RootView(
state = state, state = state,
modifier = modifier, modifier = modifier,
onOpenBugReport = this::onOpenBugReport, onOpenBugReport = this::onOpenBugReport,
onOpenShowkase = ::openShowkase
) { ) {
Children( Children(
navModel = backstack, navModel = backstack,
@ -141,12 +155,6 @@ class RootFlowNode(
} }
} }
private val bugReportNodeCallback = object : BugReportNode.Callback {
override fun onBugReportSent() {
backstack.pop()
}
}
sealed interface NavTarget : Parcelable { sealed interface NavTarget : Parcelable {
@Parcelize @Parcelize
object SplashScreen : NavTarget object SplashScreen : NavTarget
@ -168,16 +176,28 @@ class RootFlowNode(
Timber.w("Couldn't find any session, go through SplashScreen") Timber.w("Couldn't find any session, go through SplashScreen")
backstack.newRoot(NavTarget.SplashScreen) backstack.newRoot(NavTarget.SplashScreen)
} }
LoggedInFlowNode( val inputs = LoggedInFlowNode.Inputs(matrixClient)
buildContext = buildContext, val callback = object : LoggedInFlowNode.Callback {
sessionId = navTarget.sessionId, override fun onOpenBugReport() {
matrixClient = matrixClient, backstack.push(NavTarget.BugReport)
onOpenBugReport = this::onOpenBugReport }
) }
val nodeLifecycleCallbacks = plugins<NodeLifecycleCallback>()
createNode<LoggedInFlowNode>(buildContext, plugins = listOf(inputs, callback) + nodeLifecycleCallbacks)
} }
NavTarget.NotLoggedInFlow -> NotLoggedInFlowNode(buildContext) NavTarget.NotLoggedInFlow -> createNode<NotLoggedInFlowNode>(buildContext)
NavTarget.SplashScreen -> splashNode(buildContext) NavTarget.SplashScreen -> splashNode(buildContext)
NavTarget.BugReport -> createNode<BugReportNode>(buildContext, plugins = listOf(bugReportNodeCallback)) NavTarget.BugReport -> {
val callback = object : BugReportEntryPoint.Callback {
override fun onBugReportSent() {
backstack.pop()
}
}
bugReportEntryPoint
.nodeBuilder(this, buildContext)
.callback(callback)
.build()
}
} }
} }

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.x.di package io.element.android.appnav.di
import android.os.Bundle import android.os.Bundle
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.x.root package io.element.android.appnav.root
sealed interface RootEvents { sealed interface RootEvents {
object HideShowkaseButton : RootEvents object HideShowkaseButton : RootEvents

View file

@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.x.root package io.element.android.appnav.root
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import io.element.android.features.rageshake.crash.ui.CrashDetectionPresenter import io.element.android.features.rageshake.api.crash.CrashDetectionPresenter
import io.element.android.features.rageshake.detection.RageshakeDetectionPresenter import io.element.android.features.rageshake.api.detection.RageshakeDetectionPresenter
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import javax.inject.Inject import javax.inject.Inject

View file

@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.x.root package io.element.android.appnav.root
import androidx.compose.runtime.Stable import androidx.compose.runtime.Immutable
import io.element.android.features.rageshake.crash.ui.CrashDetectionState import io.element.android.features.rageshake.api.crash.CrashDetectionState
import io.element.android.features.rageshake.detection.RageshakeDetectionState import io.element.android.features.rageshake.api.detection.RageshakeDetectionState
@Stable @Immutable
data class RootState( data class RootState(
val isShowkaseButtonVisible: Boolean, val isShowkaseButtonVisible: Boolean,
val rageshakeDetectionState: RageshakeDetectionState, val rageshakeDetectionState: RageshakeDetectionState,

View file

@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.x.root package io.element.android.appnav.root
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.rageshake.crash.ui.aCrashDetectionState import io.element.android.features.rageshake.api.crash.aCrashDetectionState
import io.element.android.features.rageshake.detection.aRageshakeDetectionState import io.element.android.features.rageshake.api.detection.aRageshakeDetectionState
open class RootStateProvider : PreviewParameterProvider<RootState> { open class RootStateProvider : PreviewParameterProvider<RootState> {
override val values: Sequence<RootState> override val values: Sequence<RootState>

View file

@ -14,33 +14,30 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.x.root package io.element.android.appnav.root
import android.app.Activity
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.features.rageshake.crash.ui.CrashDetectionEvents import io.element.android.features.rageshake.api.crash.CrashDetectionEvents
import io.element.android.features.rageshake.crash.ui.CrashDetectionView import io.element.android.features.rageshake.api.crash.CrashDetectionView
import io.element.android.features.rageshake.detection.RageshakeDetectionEvents import io.element.android.features.rageshake.api.detection.RageshakeDetectionEvents
import io.element.android.features.rageshake.detection.RageshakeDetectionView import io.element.android.features.rageshake.api.detection.RageshakeDetectionView
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.tests.uitests.openShowkase
import io.element.android.x.component.ShowkaseButton
@Composable @Composable
fun RootView( fun RootView(
state: RootState, state: RootState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onOpenBugReport: () -> Unit = {}, onOpenBugReport: () -> Unit = {},
onOpenShowkase: () -> Unit = {},
children: @Composable BoxScope.() -> Unit, children: @Composable BoxScope.() -> Unit,
) { ) {
Box( Box(
@ -50,7 +47,6 @@ fun RootView(
) { ) {
children() children()
val eventSink = state.eventSink val eventSink = state.eventSink
val context = LocalContext.current
fun onOpenBugReport() { fun onOpenBugReport() {
state.crashDetectionState.eventSink(CrashDetectionEvents.ResetAppHasCrashed) state.crashDetectionState.eventSink(CrashDetectionEvents.ResetAppHasCrashed)
@ -61,7 +57,7 @@ fun RootView(
ShowkaseButton( ShowkaseButton(
isVisible = state.isShowkaseButtonVisible, isVisible = state.isShowkaseButtonVisible,
onCloseClicked = { eventSink(RootEvents.HideShowkaseButton) }, onCloseClicked = { eventSink(RootEvents.HideShowkaseButton) },
onClick = { openShowkase(context as Activity) } onClick = onOpenShowkase
) )
RageshakeDetectionView( RageshakeDetectionView(
state = state.rageshakeDetectionState, state = state.rageshakeDetectionState,

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.x.component package io.element.android.appnav.root
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size

View file

@ -16,15 +16,21 @@
@file:OptIn(ExperimentalCoroutinesApi::class) @file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.x.root package io.element.android.appnav
import app.cash.molecule.RecompositionClock import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.rageshake.crash.ui.CrashDetectionPresenter import io.element.android.appnav.root.RootEvents
import io.element.android.features.rageshake.detection.RageshakeDetectionPresenter import io.element.android.appnav.root.RootPresenter
import io.element.android.features.rageshake.preferences.RageshakePreferencesPresenter import io.element.android.features.rageshake.impl.crash.DefaultCrashDetectionPresenter
import io.element.android.features.rageshake.impl.detection.DefaultRageshakeDetectionPresenter
import io.element.android.features.rageshake.impl.preferences.DefaultRageshakePreferencesPresenter
import io.element.android.features.rageshake.test.crash.FakeCrashDataStore
import io.element.android.features.rageshake.test.rageshake.FakeRageShake
import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore
import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolder
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Test import org.junit.Test
@ -61,13 +67,13 @@ class RootPresenterTest {
val rageshakeDataStore = FakeRageshakeDataStore() val rageshakeDataStore = FakeRageshakeDataStore()
val rageshake = FakeRageShake() val rageshake = FakeRageShake()
val screenshotHolder = FakeScreenshotHolder() val screenshotHolder = FakeScreenshotHolder()
val crashDetectionPresenter = CrashDetectionPresenter( val crashDetectionPresenter = DefaultCrashDetectionPresenter(
crashDataStore = crashDataStore crashDataStore = crashDataStore
) )
val rageshakeDetectionPresenter = RageshakeDetectionPresenter( val rageshakeDetectionPresenter = DefaultRageshakeDetectionPresenter(
screenshotHolder = screenshotHolder, screenshotHolder = screenshotHolder,
rageShake = rageshake, rageShake = rageshake,
preferencesPresenter = RageshakePreferencesPresenter( preferencesPresenter = DefaultRageshakePreferencesPresenter(
rageshake = rageshake, rageshake = rageshake,
rageshakeDataStore = rageshakeDataStore, rageshakeDataStore = rageshakeDataStore,
) )

View file

@ -1,6 +1,12 @@
import kotlinx.kover.api.KoverTaskExtension import kotlinx.kover.api.KoverTaskExtension
import org.jetbrains.kotlin.cli.common.toBooleanLenient import org.jetbrains.kotlin.cli.common.toBooleanLenient
buildscript {
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0")
}
}
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2022 New Vector Ltd
* *

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,19 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login plugins {
id("io.element.android-library")
import org.junit.Assert.assertEquals }
import org.junit.Test
android {
/** namespace = "io.element.android.features.createroom.api"
* Example local unit test, which will execute on the development machine (host). }
*
* See [testing documentation](http://d.android.com/tools/testing). dependencies {
*/ implementation(projects.libraries.architecture)
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
} }

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.createroom.api
import io.element.android.libraries.architecture.SimpleFeatureEntryPoint
interface CreateRoomEntryPoint : SimpleFeatureEntryPoint

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2023 New Vector Ltd * Copyright (c) 2022 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,13 +18,19 @@
@Suppress("DSL_SCOPE_VIOLATION") @Suppress("DSL_SCOPE_VIOLATION")
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
android { android {
namespace = "io.element.android.features.createroom" namespace = "io.element.android.features.createroom.impl"
testOptions {
unitTests {
isIncludeAndroidResources = true
}
}
} }
anvil { anvil {
@ -42,6 +48,7 @@ dependencies {
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.elementresources) implementation(projects.libraries.elementresources)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
api(projects.features.createroom.api)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test) testImplementation(libs.coroutines.test)

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.createroom package io.element.android.features.createroom.impl
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -22,22 +22,29 @@ import androidx.compose.ui.Modifier
import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.composable.Children
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.ParentNode import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.BackStack
import io.element.android.features.createroom.root.CreateRoomRootNode import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.createroom.impl.root.CreateRoomRootNode
import io.element.android.libraries.architecture.BackstackNode
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.SessionScope
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
class CreateRoomFlowNode( @ContributesNode(SessionScope::class)
buildContext: BuildContext, class CreateRoomFlowNode @AssistedInject constructor(
private val backstack: BackStack<NavTarget> = BackStack( @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
) : BackstackNode<CreateRoomFlowNode.NavTarget>(
backstack = BackStack(
initialElement = NavTarget.Root, initialElement = NavTarget.Root,
savedStateMap = buildContext.savedStateMap, savedStateMap = buildContext.savedStateMap,
), ),
) : ParentNode<CreateRoomFlowNode.NavTarget>( buildContext = buildContext,
navModel = backstack, plugins = plugins
buildContext = buildContext
) { ) {
sealed interface NavTarget : Parcelable { sealed interface NavTarget : Parcelable {

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.createroom.impl
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.createroom.api.CreateRoomEntryPoint
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.AppScope
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultCreateRoomEntryPoint @Inject constructor() : CreateRoomEntryPoint {
override fun createNode(parentNode: Node, buildContext: BuildContext): Node {
return parentNode.createNode<CreateRoomFlowNode>(buildContext)
}
}

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.createroom.root package io.element.android.features.createroom.impl.root
sealed interface CreateRoomRootEvents { sealed interface CreateRoomRootEvents {
object CreateRoom : CreateRoomRootEvents object CreateRoom : CreateRoomRootEvents

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.createroom.root package io.element.android.features.createroom.impl.root
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -43,7 +43,7 @@ class CreateRoomRootNode @AssistedInject constructor(
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() val state = presenter.present()
CreateRoomRootScreen( CreateRoomRootView(
state = state, state = state,
modifier = modifier, modifier = modifier,
onClosePressed = this::navigateUp, onClosePressed = this::navigateUp,

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.createroom.root package io.element.android.features.createroom.impl.root
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.createroom.root package io.element.android.features.createroom.impl.root
// TODO add your ui models. Remove the eventSink if you don't have events. // TODO add your ui models. Remove the eventSink if you don't have events.
// Do not use default value, so no member get forgotten in the presenters. // Do not use default value, so no member get forgotten in the presenters.

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.createroom.root package io.element.android.features.createroom.impl.root
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.createroom.root package io.element.android.features.createroom.impl.root
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -59,7 +59,7 @@ import io.element.android.libraries.ui.strings.R as StringR
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun CreateRoomRootScreen( fun CreateRoomRootView(
state: CreateRoomRootState, state: CreateRoomRootState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClosePressed: () -> Unit = {}, onClosePressed: () -> Unit = {},
@ -230,7 +230,7 @@ fun CreateRoomRootViewDarkPreview(@PreviewParameter(CreateRoomRootStateProvider:
@Composable @Composable
private fun ContentToPreview(state: CreateRoomRootState) { private fun ContentToPreview(state: CreateRoomRootState) {
CreateRoomRootScreen( CreateRoomRootView(
state = state, state = state,
) )
} }

View file

@ -16,12 +16,14 @@
@file:OptIn(ExperimentalCoroutinesApi::class) @file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.features.createroom.root package io.element.android.features.createroom.impl.root
import app.cash.molecule.RecompositionClock import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.createroom.impl.root.CreateRoomRootEvents
import io.element.android.features.createroom.impl.root.CreateRoomRootPresenter
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Test import org.junit.Test

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,19 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.logout plugins {
id("io.element.android-library")
import org.junit.Assert.assertEquals }
import org.junit.Test
android {
/** namespace = "io.element.android.features.login.api"
* Example local unit test, which will execute on the development machine (host). }
*
* See [testing documentation](http://d.android.com/tools/testing). dependencies {
*/ implementation(projects.libraries.architecture)
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
} }

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.login.api
import io.element.android.libraries.architecture.SimpleFeatureEntryPoint
interface LoginEntryPoint : SimpleFeatureEntryPoint

View file

@ -18,13 +18,13 @@
@Suppress("DSL_SCOPE_VIOLATION") @Suppress("DSL_SCOPE_VIOLATION")
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
android { android {
namespace = "io.element.android.features.login" namespace = "io.element.android.features.login.impl"
testOptions { testOptions {
unitTests { unitTests {
@ -47,6 +47,7 @@ dependencies {
implementation(projects.libraries.elementresources) implementation(projects.libraries.elementresources)
implementation(projects.libraries.testtags) implementation(projects.libraries.testtags)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
api(projects.features.login.api)
ksp(libs.showkase.processor) ksp(libs.showkase.processor)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.login.impl
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.login.api.LoginEntryPoint
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.AppScope
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultLoginEntryPoint @Inject constructor() : LoginEntryPoint {
override fun createNode(parentNode: Node, buildContext: BuildContext): Node {
return parentNode.createNode<LoginFlowNode>(buildContext)
}
}

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login package io.element.android.features.login.impl
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -22,32 +22,33 @@ import androidx.compose.ui.Modifier
import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.composable.Children
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.ParentNode import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.push import com.bumble.appyx.navmodel.backstack.operation.push
import io.element.android.features.login.changeserver.ChangeServerNode import dagger.assisted.Assisted
import io.element.android.features.login.root.LoginRootNode import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.login.impl.changeserver.ChangeServerNode
import io.element.android.features.login.impl.root.LoginRootNode
import io.element.android.libraries.architecture.BackstackNode
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.AppScope
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
class LoginFlowNode( @ContributesNode(AppScope::class)
buildContext: BuildContext, class LoginFlowNode @AssistedInject constructor(
private val backstack: BackStack<NavTarget> = BackStack( @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
) : BackstackNode<LoginFlowNode.NavTarget>(
backstack = BackStack(
initialElement = NavTarget.Root, initialElement = NavTarget.Root,
savedStateMap = buildContext.savedStateMap, savedStateMap = buildContext.savedStateMap,
), ),
) : ParentNode<LoginFlowNode.NavTarget>( buildContext = buildContext,
navModel = backstack, plugins = plugins,
buildContext = buildContext
) { ) {
private val loginRootCallback = object : LoginRootNode.Callback {
override fun onChangeHomeServer() {
backstack.push(NavTarget.ChangeServer)
}
}
sealed interface NavTarget : Parcelable { sealed interface NavTarget : Parcelable {
@Parcelize @Parcelize
object Root : NavTarget object Root : NavTarget
@ -58,7 +59,14 @@ class LoginFlowNode(
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) { return when (navTarget) {
NavTarget.Root -> createNode<LoginRootNode>(buildContext, plugins = listOf(loginRootCallback)) NavTarget.Root -> {
val callback = object : LoginRootNode.Callback {
override fun onChangeHomeServer() {
backstack.push(NavTarget.ChangeServer)
}
}
createNode<LoginRootNode>(buildContext, plugins = listOf(callback))
}
NavTarget.ChangeServer -> createNode<ChangeServerNode>(buildContext) NavTarget.ChangeServer -> createNode<ChangeServerNode>(buildContext)
} }
} }

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.changeserver package io.element.android.features.login.impl.changeserver
sealed interface ChangeServerEvents { sealed interface ChangeServerEvents {
data class SetServer(val server: String) : ChangeServerEvents data class SetServer(val server: String) : ChangeServerEvents

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.changeserver package io.element.android.features.login.impl.changeserver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -28,7 +28,7 @@ import com.bumble.appyx.core.plugin.Plugin
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.login.util.LoginConstants import io.element.android.features.login.impl.util.LoginConstants
import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.changeserver package io.element.android.features.login.impl.changeserver
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
@ -22,7 +22,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import io.element.android.features.login.util.LoginConstants import io.element.android.features.login.impl.util.LoginConstants
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.execute import io.element.android.libraries.architecture.execute

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.changeserver package io.element.android.features.login.impl.changeserver
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.changeserver package io.element.android.features.login.impl.changeserver
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.changeserver package io.element.android.features.login.impl.changeserver
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
@ -45,7 +45,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.ParagraphStyle import androidx.compose.ui.text.ParagraphStyle
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
@ -58,20 +57,20 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.features.login.R import io.element.android.features.login.impl.R
import io.element.android.features.login.error.changeServerError import io.element.android.features.login.impl.error.changeServerError
import io.element.android.features.login.util.LoginConstants import io.element.android.features.login.impl.util.LoginConstants
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.designsystem.ElementTextStyles import io.element.android.libraries.designsystem.ElementTextStyles
import io.element.android.libraries.designsystem.LinkColor import io.element.android.libraries.designsystem.LinkColor
import io.element.android.libraries.designsystem.components.ClickableLinkText import io.element.android.libraries.designsystem.components.ClickableLinkText
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.components.form.textFieldState import io.element.android.libraries.designsystem.components.form.textFieldState
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.LocalColors import io.element.android.libraries.designsystem.theme.LocalColors
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Icon
@ -81,7 +80,6 @@ import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextField import io.element.android.libraries.designsystem.theme.components.TextField
import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext
import io.element.android.libraries.designsystem.theme.roomListRoomName
import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag import io.element.android.libraries.testtags.testTag
import org.matrix.rustcomponents.sdk.AuthenticationException import org.matrix.rustcomponents.sdk.AuthenticationException

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.error package io.element.android.features.login.impl.error
import io.element.android.libraries.matrix.api.auth.AuthErrorCode import io.element.android.libraries.matrix.api.auth.AuthErrorCode
import io.element.android.libraries.matrix.api.auth.errorCode import io.element.android.libraries.matrix.api.auth.errorCode

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.root package io.element.android.features.login.impl.root
sealed interface LoginRootEvents { sealed interface LoginRootEvents {
data class SetLogin(val login: String) : LoginRootEvents data class SetLogin(val login: String) : LoginRootEvents

View file

@ -14,11 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.root package io.element.android.features.login.impl.root
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.lifecycle.Lifecycle
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.Plugin
@ -26,7 +25,6 @@ import com.bumble.appyx.core.plugin.plugins
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope
@ContributesNode(AppScope::class) @ContributesNode(AppScope::class)
@ -47,7 +45,7 @@ class LoginRootNode @AssistedInject constructor(
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() val state = presenter.present()
LoginRootScreen( LoginRootView(
state = state, state = state,
modifier = modifier, modifier = modifier,
onChangeServer = this::onChangeHomeServer, onChangeServer = this::onChangeHomeServer,

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.root package io.element.android.features.login.impl.root
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
@ -23,14 +23,12 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import io.element.android.features.login.util.LoginConstants import io.element.android.features.login.impl.util.LoginConstants
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ -87,5 +85,4 @@ class LoginRootPresenter @Inject constructor(private val authenticationService:
private fun updateFormState(formState: MutableState<LoginFormState>, updateLambda: LoginFormState.() -> LoginFormState) { private fun updateFormState(formState: MutableState<LoginFormState>, updateLambda: LoginFormState.() -> LoginFormState) {
formState.value = updateLambda(formState.value) formState.value = updateLambda(formState.value)
} }
} }

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.root package io.element.android.features.login.impl.root
import android.os.Parcelable import android.os.Parcelable
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.root package io.element.android.features.login.impl.root
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.root package io.element.android.features.login.impl.root
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -62,7 +62,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.features.login.error.loginError import io.element.android.features.login.impl.error.loginError
import io.element.android.libraries.designsystem.ElementTextStyles import io.element.android.libraries.designsystem.ElementTextStyles
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.components.form.textFieldState import io.element.android.libraries.designsystem.components.form.textFieldState
@ -85,7 +85,7 @@ import io.element.android.libraries.ui.strings.R as StringR
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun LoginRootScreen( fun LoginRootView(
state: LoginRootState, state: LoginRootState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onChangeServer: () -> Unit = {}, onChangeServer: () -> Unit = {},
@ -349,7 +349,7 @@ internal fun LoginRootScreenDarkPreview(@PreviewParameter(LoginRootStateProvider
@Composable @Composable
private fun ContentToPreview(state: LoginRootState) { private fun ContentToPreview(state: LoginRootState) {
LoginRootScreen( LoginRootView(
state = state, state = state,
onBackPressed = {} onBackPressed = {}
) )

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.util package io.element.android.features.login.impl.util
object LoginConstants { object LoginConstants {

View file

@ -16,12 +16,14 @@
@file:OptIn(ExperimentalCoroutinesApi::class) @file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.features.login.changeserver package io.element.android.features.login.impl.changeserver
import app.cash.molecule.RecompositionClock import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.login.impl.changeserver.ChangeServerEvents
import io.element.android.features.login.impl.changeserver.ChangeServerPresenter
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.test.A_HOMESERVER import io.element.android.libraries.matrix.test.A_HOMESERVER
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.A_HOMESERVER_URL

View file

@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.error package io.element.android.features.login.impl.error
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.Test
import io.element.android.libraries.ui.strings.R import io.element.android.libraries.ui.strings.R
import org.junit.Test
import org.matrix.rustcomponents.sdk.AuthenticationException import org.matrix.rustcomponents.sdk.AuthenticationException
class ErrorFormatterTests { class ErrorFormatterTests {

View file

@ -16,12 +16,16 @@
@file:OptIn(ExperimentalCoroutinesApi::class) @file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.features.login.root package io.element.android.features.login.impl.root
import app.cash.molecule.RecompositionClock import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.login.impl.root.LoggedInState
import io.element.android.features.login.impl.root.LoginFormState
import io.element.android.features.login.impl.root.LoginRootEvents
import io.element.android.features.login.impl.root.LoginRootPresenter
import io.element.android.libraries.matrix.test.A_HOMESERVER import io.element.android.libraries.matrix.test.A_HOMESERVER
import io.element.android.libraries.matrix.test.A_PASSWORD import io.element.android.libraries.matrix.test.A_PASSWORD
import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_SESSION_ID

View file

@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -1,38 +0,0 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.login
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
/**
* 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("io.element.android.features.login.test", appContext.packageName)
}
}

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2022 New Vector Ltd
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<manifest>
</manifest>

View file

@ -1 +0,0 @@
/build

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
plugins {
id("io.element.android-compose-library")
}
android {
namespace = "io.element.android.features.logout.api"
}
dependencies {
implementation(projects.libraries.architecture)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings)
}

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.logout package io.element.android.features.logout.api
sealed interface LogoutPreferenceEvents { sealed interface LogoutPreferenceEvents {
object Logout : LogoutPreferenceEvents object Logout : LogoutPreferenceEvents

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.logout.api
import io.element.android.libraries.architecture.Presenter
interface LogoutPreferencePresenter : Presenter<LogoutPreferenceState>

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.logout package io.element.android.features.logout.api
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Logout import androidx.compose.material.icons.filled.Logout

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.logout package io.element.android.features.logout.api
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.logout package io.element.android.features.logout.api
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async

View file

@ -18,12 +18,12 @@
@Suppress("DSL_SCOPE_VIOLATION") @Suppress("DSL_SCOPE_VIOLATION")
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
} }
android { android {
namespace = "io.element.android.features.logout" namespace = "io.element.android.features.logout.impl"
} }
anvil { anvil {
@ -33,12 +33,14 @@ anvil {
dependencies { dependencies {
implementation(projects.anvilannotations) implementation(projects.anvilannotations)
anvil(projects.anvilcodegen) anvil(projects.anvilcodegen)
implementation(projects.libraries.architecture)
implementation(projects.libraries.core) implementation(projects.libraries.core)
implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrix.api)
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.elementresources) implementation(projects.libraries.elementresources)
implementation(projects.libraries.testtags)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
api(projects.features.logout.api)
ksp(libs.showkase.processor) ksp(libs.showkase.processor)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)

View file

@ -15,6 +15,6 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<manifest> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest> </manifest>

View file

@ -14,23 +14,28 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.logout package io.element.android.features.logout.impl
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.logout.api.LogoutPreferenceEvents
import io.element.android.features.logout.api.LogoutPreferencePresenter
import io.element.android.features.logout.api.LogoutPreferenceState
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.execute import io.element.android.libraries.architecture.execute
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
class LogoutPreferencePresenter @Inject constructor(private val matrixClient: MatrixClient) : @ContributesBinding(SessionScope::class)
Presenter<LogoutPreferenceState> { class DefaultLogoutPreferencePresenter @Inject constructor(private val matrixClient: MatrixClient) :
LogoutPreferencePresenter {
@Composable @Composable
override fun present(): LogoutPreferenceState { override fun present(): LogoutPreferenceState {

View file

@ -16,12 +16,14 @@
@file:OptIn(ExperimentalCoroutinesApi::class) @file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.features.logout package io.element.android.features.logout.impl
import app.cash.molecule.RecompositionClock import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.logout.api.LogoutPreferenceEvents
import io.element.android.features.logout.api.LogoutPreferenceState
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.A_THROWABLE
@ -33,7 +35,7 @@ import org.junit.Test
class LogoutPreferencePresenterTest { class LogoutPreferencePresenterTest {
@Test @Test
fun `present - initial state`() = runTest { fun `present - initial state`() = runTest {
val presenter = LogoutPreferencePresenter( val presenter = DefaultLogoutPreferencePresenter(
FakeMatrixClient(A_SESSION_ID), FakeMatrixClient(A_SESSION_ID),
) )
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
@ -46,7 +48,7 @@ class LogoutPreferencePresenterTest {
@Test @Test
fun `present - logout`() = runTest { fun `present - logout`() = runTest {
val presenter = LogoutPreferencePresenter( val presenter = DefaultLogoutPreferencePresenter(
FakeMatrixClient(A_SESSION_ID), FakeMatrixClient(A_SESSION_ID),
) )
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
@ -64,7 +66,7 @@ class LogoutPreferencePresenterTest {
@Test @Test
fun `present - logout with error`() = runTest { fun `present - logout with error`() = runTest {
val matrixClient = FakeMatrixClient(A_SESSION_ID) val matrixClient = FakeMatrixClient(A_SESSION_ID)
val presenter = LogoutPreferencePresenter( val presenter = DefaultLogoutPreferencePresenter(
matrixClient, matrixClient,
) )
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {

View file

@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -1,38 +0,0 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.logout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
/**
* 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("io.element.android.features.login.test", appContext.packageName)
}
}

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2022 New Vector Ltd
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<manifest>
</manifest>

View file

@ -1 +0,0 @@
/build

View file

@ -14,19 +14,16 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login // TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
@Suppress("DSL_SCOPE_VIOLATION")
import org.junit.Assert.assertEquals plugins {
import org.junit.Test id("io.element.android-library")
}
/**
* Example local unit test, which will execute on the development machine (host). android {
* namespace = "io.element.android.features.messages.api"
* See [testing documentation](http://d.android.com/tools/testing). }
*/
class ExampleUnitTest { dependencies {
@Test implementation(projects.libraries.architecture)
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
} }

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.messages.api
import io.element.android.libraries.architecture.SimpleFeatureEntryPoint
interface MessagesEntryPoint : SimpleFeatureEntryPoint

View file

@ -18,12 +18,12 @@
@Suppress("DSL_SCOPE_VIOLATION") @Suppress("DSL_SCOPE_VIOLATION")
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
} }
android { android {
namespace = "io.element.android.features.messages" namespace = "io.element.android.features.messages.impl"
} }
anvil { anvil {
@ -33,6 +33,7 @@ anvil {
dependencies { dependencies {
implementation(projects.anvilannotations) implementation(projects.anvilannotations)
anvil(projects.anvilcodegen) anvil(projects.anvilcodegen)
api(projects.features.messages.api)
implementation(projects.libraries.core) implementation(projects.libraries.core)
implementation(projects.libraries.architecture) implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrix.api)

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.messages.impl
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.messages.api.MessagesEntryPoint
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.AppScope
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultMessagesEntryPoint @Inject constructor() : MessagesEntryPoint {
override fun createNode(parentNode: Node, buildContext: BuildContext): Node {
return parentNode.createNode<MessagesNode>(buildContext)
}
}

View file

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.messages package io.element.android.features.messages.impl
import io.element.android.features.messages.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
sealed interface MessagesEvents { sealed interface MessagesEvents {
data class HandleAction(val action: TimelineItemAction, val event: TimelineItem.Event) : MessagesEvents data class HandleAction(val action: TimelineItemAction, val event: TimelineItem.Event) : MessagesEvents

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.messages package io.element.android.features.messages.impl
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.messages package io.element.android.features.messages.impl
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -24,15 +24,15 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import io.element.android.features.messages.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.ActionListPresenter
import io.element.android.features.messages.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.textcomposer.MessageComposerEvents import io.element.android.features.messages.impl.textcomposer.MessageComposerEvents
import io.element.android.features.messages.textcomposer.MessageComposerPresenter import io.element.android.features.messages.impl.textcomposer.MessageComposerPresenter
import io.element.android.features.messages.textcomposer.MessageComposerState import io.element.android.features.messages.impl.textcomposer.MessageComposerState
import io.element.android.features.messages.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineEvents
import io.element.android.features.messages.timeline.TimelinePresenter import io.element.android.features.messages.impl.timeline.TimelinePresenter
import io.element.android.features.messages.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize

Some files were not shown because too many files have changed in this diff Show more