Browse Source

Merge pull request #47 from vector-im/feature/fga/presenter_tests

Feature/fga/presenter tests
feature/jme/update_rust_sdk
ganfra 2 years ago committed by GitHub
parent
commit
ef23d08707
  1. 2
      app/build.gradle.kts
  2. 2
      app/src/main/kotlin/io/element/android/x/ElementXApplication.kt
  3. 4
      app/src/main/kotlin/io/element/android/x/MainActivity.kt
  4. 4
      app/src/main/kotlin/io/element/android/x/di/AppBindings.kt
  5. 7
      app/src/main/kotlin/io/element/android/x/di/AppModule.kt
  6. 2
      app/src/main/kotlin/io/element/android/x/node/LoggedInFlowNode.kt
  7. 2
      app/src/main/kotlin/io/element/android/x/node/RoomFlowNode.kt
  8. 10
      app/src/main/kotlin/io/element/android/x/node/RootFlowNode.kt
  9. 1
      build.gradle.kts
  10. 8
      features/login/src/main/kotlin/io/element/android/x/features/login/changeserver/ChangeServerPresenter.kt
  11. 12
      features/login/src/main/kotlin/io/element/android/x/features/login/root/LoginRootPresenter.kt
  12. 11
      features/roomlist/build.gradle.kts
  13. 2
      features/roomlist/src/main/kotlin/io/element/android/x/features/roomlist/model/RoomListState.kt
  14. 46
      features/roomlist/src/test/kotlin/io/element/android/x/features/roomlist/RoomListPresenterTests.kt
  15. 44
      gradle/libs.versions.toml
  16. 6
      libraries/architecture/build.gradle.kts
  17. 2
      libraries/architecture/src/main/kotlin/io/element/android/x/architecture/Bindings.kt
  18. 2
      libraries/di/src/main/kotlin/io/element/android/x/di/DaggerComponentOwner.kt
  19. 1
      libraries/matrix/src/main/kotlin/io/element/android/x/matrix/RustMatrixClient.kt
  20. 31
      libraries/matrix/src/main/kotlin/io/element/android/x/matrix/auth/MatrixAuthenticationService.kt
  21. 38
      libraries/matrix/src/main/kotlin/io/element/android/x/matrix/auth/RustMatrixAuthenticationService.kt
  22. 36
      libraries/matrix/src/main/kotlin/io/element/android/x/matrix/di/MatrixModule.kt
  23. 22
      libraries/matrix/src/main/kotlin/io/element/android/x/matrix/media/MediaResolver.kt
  24. 40
      libraries/matrix/src/main/kotlin/io/element/android/x/matrix/media/RustMediaResolver.kt
  25. 100
      libraries/matrix/src/main/kotlin/io/element/android/x/matrix/session/PreferencesSessionStore.kt
  26. 78
      libraries/matrix/src/main/kotlin/io/element/android/x/matrix/session/SessionStore.kt
  27. 30
      libraries/matrixtest/build.gradle.kts
  28. 18
      libraries/matrixtest/src/main/AndroidManifest.xml
  29. 70
      libraries/matrixtest/src/main/kotlin/io/element/android/x/libraries/matrixtest/FakeMatrixClient.kt
  30. 23
      libraries/matrixtest/src/main/kotlin/io/element/android/x/libraries/matrixtest/media/FakeMediaResolver.kt
  31. 67
      libraries/matrixtest/src/main/kotlin/io/element/android/x/libraries/matrixtest/room/FakeMatrixRoom.kt
  32. 31
      libraries/matrixtest/src/main/kotlin/io/element/android/x/libraries/matrixtest/room/InMemoryRoomSummaryDataSource.kt
  33. 60
      libraries/matrixtest/src/main/kotlin/io/element/android/x/libraries/matrixtest/timeline/FakeMatrixTimeline.kt
  34. 1
      plugins/src/main/kotlin/extension/DependencyHandleScope.kt
  35. 1
      settings.gradle.kts

2
app/build.gradle.kts

@ -36,6 +36,8 @@ plugins {
android { android {
namespace = "io.element.android.x" namespace = "io.element.android.x"
testOptions { unitTests.isIncludeAndroidResources = true }
defaultConfig { defaultConfig {
applicationId = "io.element.android.x" applicationId = "io.element.android.x"
targetSdk = 33 // TODO Use Versions.targetSdk targetSdk = 33 // TODO Use Versions.targetSdk

2
app/src/main/kotlin/io/element/android/x/ElementXApplication.kt

@ -18,7 +18,7 @@ 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.x.core.di.DaggerComponentOwner import io.element.android.x.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.x.initializer.CrashInitializer import io.element.android.x.initializer.CrashInitializer

4
app/src/main/kotlin/io/element/android/x/MainActivity.kt

@ -26,7 +26,7 @@ import androidx.core.view.WindowCompat
import com.bumble.appyx.core.integration.NodeHost 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.x.architecture.bindings import io.element.android.x.architecture.bindings
import io.element.android.x.core.di.DaggerComponentOwner import io.element.android.x.di.DaggerComponentOwner
import io.element.android.x.designsystem.ElementXTheme import io.element.android.x.designsystem.ElementXTheme
import io.element.android.x.di.AppBindings import io.element.android.x.di.AppBindings
import io.element.android.x.node.RootFlowNode import io.element.android.x.node.RootFlowNode
@ -47,7 +47,7 @@ class MainActivity : NodeComponentActivity() {
RootFlowNode( RootFlowNode(
buildContext = it, buildContext = it,
appComponentOwner = applicationContext as DaggerComponentOwner, appComponentOwner = applicationContext as DaggerComponentOwner,
matrix = appBindings.matrix(), authenticationService = appBindings.authenticationService(),
rootPresenter = appBindings.rootPresenter() rootPresenter = appBindings.rootPresenter()
) )
} }

4
app/src/main/kotlin/io/element/android/x/di/AppBindings.kt

@ -17,7 +17,7 @@
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.x.matrix.Matrix import io.element.android.x.matrix.auth.MatrixAuthenticationService
import io.element.android.x.root.RootPresenter import io.element.android.x.root.RootPresenter
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -25,5 +25,5 @@ import kotlinx.coroutines.CoroutineScope
interface AppBindings { interface AppBindings {
fun coroutineScope(): CoroutineScope fun coroutineScope(): CoroutineScope
fun rootPresenter(): RootPresenter fun rootPresenter(): RootPresenter
fun matrix(): Matrix fun authenticationService(): MatrixAuthenticationService
} }

7
app/src/main/kotlin/io/element/android/x/di/AppModule.kt

@ -16,6 +16,7 @@
package io.element.android.x.di package io.element.android.x.di
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
@ -26,12 +27,18 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import java.io.File
import java.util.concurrent.Executors import java.util.concurrent.Executors
@Module @Module
@ContributesTo(AppScope::class) @ContributesTo(AppScope::class)
object AppModule { object AppModule {
@Provides
fun providesBaseDirectory(@ApplicationContext context: Context): File {
return File(context.filesDir, "sessions")
}
@Provides @Provides
@SingleIn(AppScope::class) @SingleIn(AppScope::class)
fun providesAppCoroutineScope(): CoroutineScope { fun providesAppCoroutineScope(): CoroutineScope {

2
app/src/main/kotlin/io/element/android/x/node/LoggedInFlowNode.kt

@ -34,7 +34,7 @@ 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.x.architecture.bindings import io.element.android.x.architecture.bindings
import io.element.android.x.architecture.createNode import io.element.android.x.architecture.createNode
import io.element.android.x.core.di.DaggerComponentOwner import io.element.android.x.di.DaggerComponentOwner
import io.element.android.x.di.SessionComponent import io.element.android.x.di.SessionComponent
import io.element.android.x.features.preferences.PreferencesFlowNode import io.element.android.x.features.preferences.PreferencesFlowNode
import io.element.android.x.features.roomlist.RoomListNode import io.element.android.x.features.roomlist.RoomListNode

2
app/src/main/kotlin/io/element/android/x/node/RoomFlowNode.kt

@ -27,7 +27,7 @@ import com.bumble.appyx.core.node.ParentNode
import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.BackStack
import io.element.android.x.architecture.bindings import io.element.android.x.architecture.bindings
import io.element.android.x.architecture.createNode import io.element.android.x.architecture.createNode
import io.element.android.x.core.di.DaggerComponentOwner import io.element.android.x.di.DaggerComponentOwner
import io.element.android.x.di.RoomComponent import io.element.android.x.di.RoomComponent
import io.element.android.x.features.messages.MessagesNode import io.element.android.x.features.messages.MessagesNode
import io.element.android.x.matrix.room.MatrixRoom import io.element.android.x.matrix.room.MatrixRoom

10
app/src/main/kotlin/io/element/android/x/node/RootFlowNode.kt

@ -38,10 +38,10 @@ 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.x.architecture.createNode import io.element.android.x.architecture.createNode
import io.element.android.x.architecture.presenterConnector import io.element.android.x.architecture.presenterConnector
import io.element.android.x.core.di.DaggerComponentOwner import io.element.android.x.di.DaggerComponentOwner
import io.element.android.x.features.rageshake.bugreport.BugReportNode import io.element.android.x.features.rageshake.bugreport.BugReportNode
import io.element.android.x.matrix.Matrix
import io.element.android.x.matrix.MatrixClient import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.auth.MatrixAuthenticationService
import io.element.android.x.matrix.core.SessionId import io.element.android.x.matrix.core.SessionId
import io.element.android.x.root.RootPresenter import io.element.android.x.root.RootPresenter
import io.element.android.x.root.RootView import io.element.android.x.root.RootView
@ -59,7 +59,7 @@ class RootFlowNode(
savedStateMap = buildContext.savedStateMap, savedStateMap = buildContext.savedStateMap,
), ),
private val appComponentOwner: DaggerComponentOwner, private val appComponentOwner: DaggerComponentOwner,
private val matrix: Matrix, private val authenticationService: MatrixAuthenticationService,
rootPresenter: RootPresenter rootPresenter: RootPresenter
) : ) :
ParentNode<RootFlowNode.NavTarget>( ParentNode<RootFlowNode.NavTarget>(
@ -79,12 +79,12 @@ class RootFlowNode(
onDestroy = { matrixClientsHolder.remove(child.sessionId) } onDestroy = { matrixClientsHolder.remove(child.sessionId) }
) )
} }
matrix.isLoggedIn() authenticationService.isLoggedIn()
.distinctUntilChanged() .distinctUntilChanged()
.onEach { isLoggedIn -> .onEach { isLoggedIn ->
Timber.v("isLoggedIn=$isLoggedIn") Timber.v("isLoggedIn=$isLoggedIn")
if (isLoggedIn) { if (isLoggedIn) {
val matrixClient = matrix.restoreSession() val matrixClient = authenticationService.restoreSession()
if (matrixClient == null) { if (matrixClient == null) {
backstack.newRoot(NavTarget.NotLoggedInFlow) backstack.newRoot(NavTarget.NotLoggedInFlow)
} else { } else {

1
build.gradle.kts

@ -23,7 +23,6 @@ plugins {
alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.ksp) apply false alias(libs.plugins.ksp) apply false
alias(libs.plugins.anvil) apply false alias(libs.plugins.anvil) apply false
alias(libs.plugins.molecule) apply false
alias(libs.plugins.kotlin.jvm) apply false alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kapt) apply false alias(libs.plugins.kapt) apply false
alias(libs.plugins.dependencycheck) apply false alias(libs.plugins.dependencycheck) apply false

8
features/login/src/main/kotlin/io/element/android/x/features/login/changeserver/ChangeServerPresenter.kt

@ -25,18 +25,18 @@ import androidx.compose.runtime.saveable.rememberSaveable
import io.element.android.x.architecture.Async import io.element.android.x.architecture.Async
import io.element.android.x.architecture.Presenter import io.element.android.x.architecture.Presenter
import io.element.android.x.architecture.execute import io.element.android.x.architecture.execute
import io.element.android.x.matrix.Matrix import io.element.android.x.matrix.auth.MatrixAuthenticationService
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 ChangeServerPresenter @Inject constructor(private val matrix: Matrix) : Presenter<ChangeServerState> { class ChangeServerPresenter @Inject constructor(private val authenticationService: MatrixAuthenticationService) : Presenter<ChangeServerState> {
@Composable @Composable
override fun present(): ChangeServerState { override fun present(): ChangeServerState {
val localCoroutineScope = rememberCoroutineScope() val localCoroutineScope = rememberCoroutineScope()
val homeserver = rememberSaveable { val homeserver = rememberSaveable {
mutableStateOf(matrix.getHomeserverOrDefault()) mutableStateOf(authenticationService.getHomeserverOrDefault())
} }
val changeServerAction: MutableState<Async<Unit>> = remember { val changeServerAction: MutableState<Async<Unit>> = remember {
mutableStateOf(Async.Uninitialized) mutableStateOf(Async.Uninitialized)
@ -58,7 +58,7 @@ class ChangeServerPresenter @Inject constructor(private val matrix: Matrix) : Pr
private fun CoroutineScope.submit(homeserver: String, changeServerAction: MutableState<Async<Unit>>) = launch { private fun CoroutineScope.submit(homeserver: String, changeServerAction: MutableState<Async<Unit>>) = launch {
suspend { suspend {
matrix.setHomeserver(homeserver) authenticationService.setHomeserver(homeserver)
}.execute(changeServerAction) }.execute(changeServerAction)
} }
} }

12
features/login/src/main/kotlin/io/element/android/x/features/login/root/LoginRootPresenter.kt

@ -23,18 +23,18 @@ 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.x.architecture.Presenter import io.element.android.x.architecture.Presenter
import io.element.android.x.matrix.Matrix import io.element.android.x.matrix.auth.MatrixAuthenticationService
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 LoginRootPresenter @Inject constructor(private val matrix: Matrix) : Presenter<LoginRootState> { class LoginRootPresenter @Inject constructor(private val authenticationService: MatrixAuthenticationService) : Presenter<LoginRootState> {
@Composable @Composable
override fun present(): LoginRootState { override fun present(): LoginRootState {
val localCoroutineScope = rememberCoroutineScope() val localCoroutineScope = rememberCoroutineScope()
val homeserver = rememberSaveable { val homeserver = rememberSaveable {
mutableStateOf(matrix.getHomeserverOrDefault()) mutableStateOf(authenticationService.getHomeserverOrDefault())
} }
val loggedInState: MutableState<LoggedInState> = remember { val loggedInState: MutableState<LoggedInState> = remember {
mutableStateOf(LoggedInState.NotLoggedIn) mutableStateOf(LoggedInState.NotLoggedIn)
@ -67,8 +67,8 @@ class LoginRootPresenter @Inject constructor(private val matrix: Matrix) : Prese
private fun CoroutineScope.submit(homeserver: String, formState: LoginFormState, loggedInState: MutableState<LoggedInState>) = launch { private fun CoroutineScope.submit(homeserver: String, formState: LoginFormState, loggedInState: MutableState<LoggedInState>) = launch {
loggedInState.value = LoggedInState.LoggingIn loggedInState.value = LoggedInState.LoggingIn
try { try {
matrix.setHomeserver(homeserver) authenticationService.setHomeserver(homeserver)
val sessionId = matrix.login(formState.login.trim(), formState.password.trim()) val sessionId = authenticationService.login(formState.login.trim(), formState.password.trim())
loggedInState.value = LoggedInState.LoggedIn(sessionId) loggedInState.value = LoggedInState.LoggedIn(sessionId)
} catch (failure: Throwable) { } catch (failure: Throwable) {
loggedInState.value = LoggedInState.ErrorLoggingIn(failure) loggedInState.value = LoggedInState.ErrorLoggingIn(failure)
@ -80,6 +80,6 @@ class LoginRootPresenter @Inject constructor(private val matrix: Matrix) : Prese
} }
private fun refreshHomeServer(homeserver: MutableState<String>) { private fun refreshHomeServer(homeserver: MutableState<String>) {
homeserver.value = matrix.getHomeserverOrDefault() homeserver.value = authenticationService.getHomeserverOrDefault()
} }
} }

11
features/roomlist/build.gradle.kts

@ -31,8 +31,9 @@ anvil {
} }
dependencies { dependencies {
implementation(project(":anvilannotations"))
anvil(project(":anvilcodegen")) anvil(project(":anvilcodegen"))
implementation(project(":anvilannotations"))
implementation(project(":libraries:di")) implementation(project(":libraries:di"))
implementation(project(":libraries:core")) implementation(project(":libraries:core"))
implementation(project(":libraries:architecture")) implementation(project(":libraries:architecture"))
@ -44,7 +45,15 @@ dependencies {
implementation(project(":libraries:ui-strings")) implementation(project(":libraries:ui-strings"))
implementation(libs.datetime) implementation(libs.datetime)
implementation(libs.accompanist.placeholder) implementation(libs.accompanist.placeholder)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test)
testImplementation(libs.molecule.runtime)
testImplementation(libs.test.truth)
testImplementation(libs.test.turbine)
testImplementation(project(":libraries:matrixtest"))
androidTestImplementation(libs.test.junitext) androidTestImplementation(libs.test.junitext)
ksp(libs.showkase.processor) ksp(libs.showkase.processor)
} }

2
features/roomlist/src/main/kotlin/io/element/android/x/features/roomlist/model/RoomListState.kt

@ -26,5 +26,5 @@ data class RoomListState(
val roomList: ImmutableList<RoomListRoomSummary>, val roomList: ImmutableList<RoomListRoomSummary>,
val filter: String, val filter: String,
val isLoginOut: Boolean, val isLoginOut: Boolean,
val eventSink: (RoomListEvents) -> Unit = {} val eventSink: (RoomListEvents) -> Unit
) )

46
features/roomlist/src/test/kotlin/io/element/android/x/features/roomlist/RoomListPresenterTests.kt

@ -0,0 +1,46 @@
/*
* 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.features.roomlist
import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.x.libraries.matrixtest.FakeMatrixClient
import io.element.android.x.matrix.core.SessionId
import kotlinx.coroutines.test.runTest
import org.junit.Test
class RoomListPresenterTests {
@Test
fun `present - should start with no user and then load user with success`() = runTest {
val presenter = RoomListPresenter(
FakeMatrixClient(
SessionId("sessionId")
), LastMessageFormatter())
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.matrixUser).isNull()
val withUserState = awaitItem()
assertThat(withUserState).isNotNull()
}
}
}

44
gradle/libs.versions.toml

@ -29,27 +29,15 @@ coroutines = "1.6.4"
accompanist = "0.27.0" accompanist = "0.27.0"
# Test # Test
test_junit = "4.13.2"
test_runner = "1.4.0"
test_core = "1.4.0" test_core = "1.4.0"
test_mockk = "1.13.2"
test_uiautomator = "2.2.0"
test_junitext = "1.1.3"
test_barista = "4.2.0"
test_hamcrest = "2.2"
test_orchestrator = "1.4.1"
test_paparazzi = "1.2.0"
test_parameter_injector = "1.8"
#other #other
coil = "2.2.2" coil = "2.2.2"
datetime = "0.4.0" datetime = "0.4.0"
wysiwyg = "0.7.0.1"
serialization_json = "1.4.1" serialization_json = "1.4.1"
showkase = "1.0.0-beta14" showkase = "1.0.0-beta14"
jsoup = "1.15.3" jsoup = "1.15.3"
appyx = "1.0.1" appyx = "1.0.1"
seismic = "1.0.3"
dependencycheck = "7.4.4" dependencycheck = "7.4.4"
stem = "2.2.3" stem = "2.2.3"
@ -99,20 +87,22 @@ accompanist_pagerindicator = { module = "com.google.accompanist:accompanist-page
accompanist_flowlayout = { module = "com.google.accompanist:accompanist-flowlayout", version.ref = "accompanist" } accompanist_flowlayout = { module = "com.google.accompanist:accompanist-flowlayout", version.ref = "accompanist" }
# Libraries # Libraries
squareup_seismic = { module = "com.squareup:seismic", version.ref = "seismic" } squareup_seismic = "com.squareup:seismic:1.0.3"
# Test # Test
test_junit = { module = "junit:junit", version.ref = "test_junit" }
test_runner = { module = "androidx.test:runner", version.ref = "test_runner" }
test_core = { module = "androidx.test:core", version.ref = "test_core" } test_core = { module = "androidx.test:core", version.ref = "test_core" }
test_corektx = { module = "androidx.test:core-ktx", version.ref = "test_core" } test_corektx = { module = "androidx.test:core-ktx", version.ref = "test_core" }
test_uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "test_uiautomator" } test_junit = "junit:junit:4.13.2"
test_junitext = { module = "androidx.test.ext:junit", version.ref = "test_junitext" } test_runner = "androidx.test:runner:1.4.0"
test_mockk = { module = "io.mockk:mockk", version.ref = "test_mockk" } test_uiautomator = "androidx.test.uiautomator:uiautomator:2.2.0"
test_barista = { module = "com.adevinta.android:barista", version.ref = "test_barista" } test_junitext = "androidx.test.ext:junit:1.1.3"
test_hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "test_hamcrest" } test_mockk = "io.mockk:mockk:1.13.2"
test_orchestrator = { module = "androidx.test:orchestrator", version.ref = "test_orchestrator" } test_barista = "com.adevinta.android:barista:4.2.0"
test_parameter_injector = { module = "com.google.testparameterinjector:test-parameter-injector", version.ref = "test_parameter_injector" } test_hamcrest = "org.hamcrest:hamcrest:2.2"
test_orchestrator = "androidx.test:orchestrator:1.4.1"
test_turbine = "app.cash.turbine:turbine:0.12.1"
test_truth = "com.google.truth:truth:1.1.3"
test_parameter_injector = "com.google.testparameterinjector:test-parameter-injector:1.8"
# Others # Others
coil = { module = "io.coil-kt:coil", version.ref = "coil" } coil = { module = "io.coil-kt:coil", version.ref = "coil" }
@ -122,17 +112,18 @@ serialization_json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-jso
showkase = { module = "com.airbnb.android:showkase", version.ref = "showkase" } showkase = { module = "com.airbnb.android:showkase", version.ref = "showkase" }
showkase_processor = { module = "com.airbnb.android:showkase-processor", version.ref = "showkase" } showkase_processor = { module = "com.airbnb.android:showkase-processor", version.ref = "showkase" }
jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
appyx_core = {module = "com.bumble.appyx:core", version.ref = "appyx"} appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule" }
# Di # Di
inject = { module = "javax.inject:javax.inject", version = "1" } inject = "javax.inject:javax.inject:1"
dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" } dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" }
dagger_compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" } dagger_compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" }
anvil_compiler_api = { module = "com.squareup.anvil:compiler-api", version.ref = "anvil" } anvil_compiler_api = { module = "com.squareup.anvil:compiler-api", version.ref = "anvil" }
anvil_compiler_utils = { module = "com.squareup.anvil:compiler-utils", version.ref = "anvil" } anvil_compiler_utils = { module = "com.squareup.anvil:compiler-utils", version.ref = "anvil" }
# Composer # Composer
wysiwyg = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } wysiwyg = "io.element.android:wysiwyg:0.7.0.1"
[bundles] [bundles]
@ -146,9 +137,8 @@ ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
anvil = { id = "com.squareup.anvil", version.ref = "anvil" } anvil = { id = "com.squareup.anvil", version.ref = "anvil" }
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
molecule = {id = "app.cash.molecule", version.ref = "molecule"}
dependencygraph = { id = "com.savvasdalkitsis.module-dependency-graph", version.ref = "dependencygraph" } dependencygraph = { id = "com.savvasdalkitsis.module-dependency-graph", version.ref = "dependencygraph" }
dependencycheck = { id = "org.owasp.dependencycheck", version.ref = "dependencycheck" } dependencycheck = { id = "org.owasp.dependencycheck", version.ref = "dependencycheck" }
stem = { id = "com.likethesalad.stem", version.ref = "stem" } stem = { id = "com.likethesalad.stem", version.ref = "stem" }
stemlibrary = { id = "com.likethesalad.stem-library", version.ref = "stem" } stemlibrary = { id = "com.likethesalad.stem-library", version.ref = "stem" }
paparazzi = { id = "app.cash.paparazzi", version.ref = "test_paparazzi" } paparazzi = "app.cash.paparazzi:1.2.0"

6
libraries/architecture/build.gradle.kts

@ -18,16 +18,16 @@
@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.molecule)
} }
android { android {
namespace = "io.element.android.x.architecture" namespace = "io.element.android.x.libraries.architecture"
} }
dependencies { dependencies {
api(project(":libraries:core")) api(project(":libraries:di"))
api(libs.dagger) api(libs.dagger)
api(libs.appyx.core) api(libs.appyx.core)
api(libs.molecule.runtime)
api(libs.androidx.lifecycle.runtime) api(libs.androidx.lifecycle.runtime)
} }

2
libraries/architecture/src/main/kotlin/io/element/android/x/architecture/Bindings.kt

@ -19,7 +19,7 @@ package io.element.android.x.architecture
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import io.element.android.x.core.di.DaggerComponentOwner import io.element.android.x.di.DaggerComponentOwner
inline fun <reified T : Any> Node.bindings() = bindings(T::class.java) inline fun <reified T : Any> Node.bindings() = bindings(T::class.java)
inline fun <reified T : Any> Context.bindings() = bindings(T::class.java) inline fun <reified T : Any> Context.bindings() = bindings(T::class.java)

2
libraries/core/src/main/kotlin/io/element/android/x/core/di/DaggerComponentOwner.kt → libraries/di/src/main/kotlin/io/element/android/x/di/DaggerComponentOwner.kt

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.x.core.di package io.element.android.x.di
/** /**
* A [DaggerComponentOwner] is anything that "owns" a Dagger Component. * A [DaggerComponentOwner] is anything that "owns" a Dagger Component.

1
libraries/matrix/src/main/kotlin/io/element/android/x/matrix/RustMatrixClient.kt

@ -26,6 +26,7 @@ import io.element.android.x.matrix.room.MatrixRoom
import io.element.android.x.matrix.room.RoomSummaryDataSource import io.element.android.x.matrix.room.RoomSummaryDataSource
import io.element.android.x.matrix.room.RustMatrixRoom import io.element.android.x.matrix.room.RustMatrixRoom
import io.element.android.x.matrix.room.RustRoomSummaryDataSource import io.element.android.x.matrix.room.RustRoomSummaryDataSource
import io.element.android.x.matrix.session.PreferencesSessionStore
import io.element.android.x.matrix.session.SessionStore import io.element.android.x.matrix.session.SessionStore
import io.element.android.x.matrix.session.sessionId import io.element.android.x.matrix.session.sessionId
import io.element.android.x.matrix.sync.SlidingSyncObserverProxy import io.element.android.x.matrix.sync.SlidingSyncObserverProxy

31
libraries/matrix/src/main/kotlin/io/element/android/x/matrix/auth/MatrixAuthenticationService.kt

@ -0,0 +1,31 @@
/*
* 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.matrix.auth
import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.core.SessionId
import kotlinx.coroutines.flow.Flow
interface MatrixAuthenticationService {
fun isLoggedIn(): Flow<Boolean>
suspend fun getLatestSessionId(): SessionId?
suspend fun restoreSession(): MatrixClient?
fun getHomeserver(): String?
fun getHomeserverOrDefault(): String
suspend fun setHomeserver(homeserver: String)
suspend fun login(username: String, password: String): SessionId
}

38
libraries/matrix/src/main/kotlin/io/element/android/x/matrix/Matrix.kt → libraries/matrix/src/main/kotlin/io/element/android/x/matrix/auth/RustMatrixAuthenticationService.kt

@ -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,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.x.matrix package io.element.android.x.matrix.auth
import android.content.Context import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.x.core.coroutine.CoroutineDispatchers import io.element.android.x.core.coroutine.CoroutineDispatchers
import io.element.android.x.di.AppScope import io.element.android.x.di.AppScope
import io.element.android.x.di.ApplicationContext import io.element.android.x.matrix.MatrixClient
import io.element.android.x.di.SingleIn import io.element.android.x.matrix.RustMatrixClient
import io.element.android.x.matrix.core.SessionId import io.element.android.x.matrix.core.SessionId
import io.element.android.x.matrix.session.SessionStore import io.element.android.x.matrix.session.SessionStore
import io.element.android.x.matrix.session.sessionId import io.element.android.x.matrix.session.sessionId
@ -35,26 +35,24 @@ import timber.log.Timber
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
@SingleIn(AppScope::class) @ContributesBinding(AppScope::class)
class Matrix @Inject constructor( class RustMatrixAuthenticationService @Inject constructor(
private val baseDirectory: File,
private val coroutineScope: CoroutineScope, private val coroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers, private val coroutineDispatchers: CoroutineDispatchers,
@ApplicationContext context: Context, private val sessionStore: SessionStore,
) { private val authService: AuthenticationService,
) : MatrixAuthenticationService {
private val baseDirectory = File(context.filesDir, "sessions") override fun isLoggedIn(): Flow<Boolean> {
private val sessionStore = SessionStore(context)
private val authService = AuthenticationService(baseDirectory.absolutePath)
fun isLoggedIn(): Flow<Boolean> {
return sessionStore.isLoggedIn() return sessionStore.isLoggedIn()
} }
suspend fun getLatestSessionId(): SessionId? = withContext(coroutineDispatchers.io) { override suspend fun getLatestSessionId(): SessionId? = withContext(coroutineDispatchers.io) {
sessionStore.getLatestSession()?.sessionId() sessionStore.getLatestSession()?.sessionId()
} }
suspend fun restoreSession() = withContext(coroutineDispatchers.io) { override suspend fun restoreSession() = withContext(coroutineDispatchers.io) {
sessionStore.getLatestSession() sessionStore.getLatestSession()
?.let { session -> ?.let { session ->
try { try {
@ -73,17 +71,17 @@ class Matrix @Inject constructor(
} }
} }
fun getHomeserver(): String? = authService.homeserverDetails()?.url() override fun getHomeserver(): String? = authService.homeserverDetails()?.url()
fun getHomeserverOrDefault(): String = getHomeserver() ?: "matrix.org" override fun getHomeserverOrDefault(): String = getHomeserver() ?: "matrix.org"
suspend fun setHomeserver(homeserver: String) { override suspend fun setHomeserver(homeserver: String) {
withContext(coroutineDispatchers.io) { withContext(coroutineDispatchers.io) {
authService.configureHomeserver(homeserver) authService.configureHomeserver(homeserver)
} }
} }
suspend fun login(username: String, password: String): SessionId = override suspend fun login(username: String, password: String): SessionId =
withContext(coroutineDispatchers.io) { withContext(coroutineDispatchers.io) {
val client = try { val client = try {
authService.login(username, password, "ElementX Android", null) authService.login(username, password, "ElementX Android", null)

36
libraries/matrix/src/main/kotlin/io/element/android/x/matrix/di/MatrixModule.kt

@ -0,0 +1,36 @@
/*
* 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.matrix.di
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.Provides
import io.element.android.x.di.AppScope
import io.element.android.x.di.SingleIn
import org.matrix.rustcomponents.sdk.AuthenticationService
import java.io.File
@Module
@ContributesTo(AppScope::class)
object MatrixModule {
@Provides
@SingleIn(AppScope::class)
fun providesRustAuthenticationService(baseDirectory: File): AuthenticationService {
return AuthenticationService(baseDirectory.absolutePath)
}
}

22
libraries/matrix/src/main/kotlin/io/element/android/x/matrix/media/MediaResolver.kt

@ -16,9 +16,7 @@
package io.element.android.x.matrix.media package io.element.android.x.matrix.media
import io.element.android.x.matrix.MatrixClient
import org.matrix.rustcomponents.sdk.MediaSource import org.matrix.rustcomponents.sdk.MediaSource
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl
interface MediaResolver { interface MediaResolver {
@ -39,23 +37,3 @@ interface MediaResolver {
suspend fun resolve(meta: Meta): ByteArray? suspend fun resolve(meta: Meta): ByteArray?
} }
internal class RustMediaResolver(private val client: MatrixClient) : MediaResolver {
override suspend fun resolve(url: String?, kind: MediaResolver.Kind): ByteArray? {
if (url.isNullOrEmpty()) return null
val mediaSource = mediaSourceFromUrl(url)
return resolve(MediaResolver.Meta(mediaSource, kind))
}
override suspend fun resolve(meta: MediaResolver.Meta): ByteArray? {
return when (meta.kind) {
is MediaResolver.Kind.Content -> client.loadMediaContentForSource(meta.source)
is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnailForSource(
meta.source,
meta.kind.width.toLong(),
meta.kind.height.toLong()
)
}.getOrNull()
}
}

40
libraries/matrix/src/main/kotlin/io/element/android/x/matrix/media/RustMediaResolver.kt

@ -0,0 +1,40 @@
/*
* 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.matrix.media
import io.element.android.x.matrix.MatrixClient
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl
internal class RustMediaResolver(private val client: MatrixClient) : MediaResolver {
override suspend fun resolve(url: String?, kind: MediaResolver.Kind): ByteArray? {
if (url.isNullOrEmpty()) return null
val mediaSource = mediaSourceFromUrl(url)
return resolve(MediaResolver.Meta(mediaSource, kind))
}
override suspend fun resolve(meta: MediaResolver.Meta): ByteArray? {
return when (meta.kind) {
is MediaResolver.Kind.Content -> client.loadMediaContentForSource(meta.source)
is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnailForSource(
meta.source,
meta.kind.width.toLong(),
meta.kind.height.toLong()
)
}.getOrNull()
}
}

100
libraries/matrix/src/main/kotlin/io/element/android/x/matrix/session/PreferencesSessionStore.kt

@ -0,0 +1,100 @@
/*
* 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.x.matrix.session
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.x.di.AppScope
import io.element.android.x.di.ApplicationContext
import io.element.android.x.di.SingleIn
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.matrix.rustcomponents.sdk.Session
import javax.inject.Inject
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "elementx_sessions")
// TODO It contains the access token, so it has to be stored in a more secured storage.
private val sessionKey = stringPreferencesKey("session")
@SingleIn(AppScope::class)
@ContributesBinding(AppScope::class)
class PreferencesSessionStore @Inject constructor(
@ApplicationContext context: Context
) : SessionStore {
@Serializable
data class SessionData(
val accessToken: String,
val deviceId: String,
val homeserverUrl: String,
val isSoftLogout: Boolean,
val refreshToken: String?,
val userId: String
)
private val store = context.dataStore
override fun isLoggedIn(): Flow<Boolean> {
return store.data.map { prefs ->
prefs[sessionKey] != null
}
}
override suspend fun storeData(session: Session) {
store.edit { prefs ->
val sessionData = SessionData(
accessToken = session.accessToken,
deviceId = session.deviceId,
homeserverUrl = session.homeserverUrl,
isSoftLogout = session.isSoftLogout,
refreshToken = session.refreshToken,
userId = session.userId
)
val encodedSession = Json.encodeToString(sessionData)
prefs[sessionKey] = encodedSession
}
}
override suspend fun getLatestSession(): Session? {
return store.data.firstOrNull()?.let { prefs ->
val encodedSession = prefs[sessionKey] ?: return@let null
val sessionData = Json.decodeFromString<SessionData>(encodedSession)
Session(
accessToken = sessionData.accessToken,
deviceId = sessionData.deviceId,
homeserverUrl = sessionData.homeserverUrl,
isSoftLogout = sessionData.isSoftLogout,
refreshToken = sessionData.refreshToken,
userId = sessionData.userId
)
}
}
override suspend fun reset() {
store.edit { it.clear() }
}
}

78
libraries/matrix/src/main/kotlin/io/element/android/x/matrix/session/SessionStore.kt

@ -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.
@ -16,78 +16,12 @@
package io.element.android.x.matrix.session package io.element.android.x.matrix.session
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.matrix.rustcomponents.sdk.Session import org.matrix.rustcomponents.sdk.Session
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "elementx_sessions") interface SessionStore {
fun isLoggedIn(): Flow<Boolean>
// TODO It contains the access token, so it has to be stored in a more secured storage. suspend fun storeData(session: Session)
private val sessionKey = stringPreferencesKey("session") suspend fun getLatestSession(): Session?
suspend fun reset()
internal class SessionStore(
context: Context
) {
@Serializable
data class SessionData(
val accessToken: String,
val deviceId: String,
val homeserverUrl: String,
val isSoftLogout: Boolean,
val refreshToken: String?,
val userId: String
)
private val store = context.dataStore
fun isLoggedIn(): Flow<Boolean> {
return store.data.map { prefs ->
prefs[sessionKey] != null
}
}
suspend fun storeData(session: Session) {
store.edit { prefs ->
val sessionData = SessionData(
accessToken = session.accessToken,
deviceId = session.deviceId,
homeserverUrl = session.homeserverUrl,
isSoftLogout = session.isSoftLogout,
refreshToken = session.refreshToken,
userId = session.userId
)
val encodedSession = Json.encodeToString(sessionData)
prefs[sessionKey] = encodedSession
}
}
suspend fun getLatestSession(): Session? {
return store.data.firstOrNull()?.let { prefs ->
val encodedSession = prefs[sessionKey] ?: return@let null
val sessionData = Json.decodeFromString<SessionData>(encodedSession)
Session(
accessToken = sessionData.accessToken,
deviceId = sessionData.deviceId,
homeserverUrl = sessionData.homeserverUrl,
isSoftLogout = sessionData.isSoftLogout,
refreshToken = sessionData.refreshToken,
userId = sessionData.userId
)
}
}
suspend fun reset() {
store.edit { it.clear() }
}
} }

30
libraries/matrixtest/build.gradle.kts

@ -0,0 +1,30 @@
/*
* 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.
*/
// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id("io.element.android-library")
}
android {
namespace = "io.element.android.x.libraries.matrix.test"
}
dependencies {
api(project(":libraries:matrix"))
api(libs.coroutines.core)
}

18
libraries/matrixtest/src/main/AndroidManifest.xml

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<manifest/>

70
libraries/matrixtest/src/main/kotlin/io/element/android/x/libraries/matrixtest/FakeMatrixClient.kt

@ -0,0 +1,70 @@
/*
* 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.libraries.matrixtest
import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.core.RoomId
import io.element.android.x.matrix.core.SessionId
import io.element.android.x.matrix.core.UserId
import io.element.android.x.libraries.matrixtest.media.FakeMediaResolver
import io.element.android.x.matrix.media.MediaResolver
import io.element.android.x.libraries.matrixtest.room.FakeMatrixRoom
import io.element.android.x.libraries.matrixtest.room.InMemoryRoomSummaryDataSource
import io.element.android.x.matrix.room.MatrixRoom
import io.element.android.x.matrix.room.RoomSummaryDataSource
import org.matrix.rustcomponents.sdk.MediaSource
class FakeMatrixClient(override val sessionId: SessionId) : MatrixClient {
override fun getRoom(roomId: RoomId): MatrixRoom? {
return FakeMatrixRoom(roomId)
}
override fun startSync() = Unit
override fun stopSync() = Unit
override fun roomSummaryDataSource(): RoomSummaryDataSource {
return InMemoryRoomSummaryDataSource()
}
override fun mediaResolver(): MediaResolver {
return FakeMediaResolver()
}
override suspend fun logout() = Unit
override fun userId(): UserId = UserId("")
override suspend fun loadUserDisplayName(): Result<String> {
return Result.success("")
}
override suspend fun loadUserAvatarURLString(): Result<String> {
return Result.success("")
}
override suspend fun loadMediaContentForSource(source: MediaSource): Result<ByteArray> {
return Result.success(ByteArray(0))
}
override suspend fun loadMediaThumbnailForSource(source: MediaSource, width: Long, height: Long): Result<ByteArray> {
return Result.success(ByteArray(0))
}
override fun close() = Unit
}

23
features/roomlist/src/test/kotlin/io/element/android/x/features/roomlist/ExampleUnitTest.kt → libraries/matrixtest/src/main/kotlin/io/element/android/x/libraries/matrixtest/media/FakeMediaResolver.kt

@ -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,16 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.x.features.roomlist package io.element.android.x.libraries.matrixtest.media
import org.junit.Assert.assertEquals import io.element.android.x.matrix.media.MediaResolver
import org.junit.Test
/** class FakeMediaResolver : MediaResolver {
* Example local unit test, which will execute on the development machine (host). override suspend fun resolve(url: String?, kind: MediaResolver.Kind): ByteArray? {
* return null
* See [testing documentation](http://d.android.com/tools/testing). }
*/
class ExampleUnitTest { override suspend fun resolve(meta: MediaResolver.Meta): ByteArray? {
@Test return null
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
} }
} }

67
libraries/matrixtest/src/main/kotlin/io/element/android/x/libraries/matrixtest/room/FakeMatrixRoom.kt

@ -0,0 +1,67 @@
/*
* 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.libraries.matrixtest.room
import io.element.android.x.matrix.core.EventId
import io.element.android.x.matrix.core.RoomId
import io.element.android.x.matrix.room.MatrixRoom
import io.element.android.x.libraries.matrixtest.timeline.FakeMatrixTimeline
import io.element.android.x.matrix.timeline.MatrixTimeline
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
class FakeMatrixRoom(
override val roomId: RoomId,
override val name: String? = null,
override val bestName: String = "",
override val displayName: String = "",
override val topic: String? = null,
override val avatarUrl: String? = null
) : MatrixRoom {
override fun syncUpdateFlow(): Flow<Long> {
return emptyFlow()
}
override fun timeline(): MatrixTimeline {
return FakeMatrixTimeline()
}
override suspend fun userDisplayName(userId: String): Result<String?> {
return Result.success("")
}
override suspend fun userAvatarUrl(userId: String): Result<String?> {
TODO("Not yet implemented")
}
override suspend fun sendMessage(message: String): Result<Unit> {
TODO("Not yet implemented")
}
override suspend fun editMessage(originalEventId: EventId, message: String): Result<Unit> {
TODO("Not yet implemented")
}
override suspend fun replyMessage(eventId: EventId, message: String): Result<Unit> {
TODO("Not yet implemented")
}
override suspend fun redactEvent(eventId: EventId, reason: String?): Result<Unit> {
TODO("Not yet implemented")
}
}

31
libraries/matrixtest/src/main/kotlin/io/element/android/x/libraries/matrixtest/room/InMemoryRoomSummaryDataSource.kt

@ -0,0 +1,31 @@
/*
* 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.libraries.matrixtest.room
import io.element.android.x.matrix.room.RoomSummary
import io.element.android.x.matrix.room.RoomSummaryDataSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
class InMemoryRoomSummaryDataSource : RoomSummaryDataSource {
override fun roomSummaries(): Flow<List<RoomSummary>> {
return emptyFlow()
}
override fun setSlidingSyncRange(range: IntRange) = Unit
}

60
libraries/matrixtest/src/main/kotlin/io/element/android/x/libraries/matrixtest/timeline/FakeMatrixTimeline.kt

@ -0,0 +1,60 @@
/*
* 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.libraries.matrixtest.timeline
import io.element.android.x.matrix.core.EventId
import io.element.android.x.matrix.timeline.MatrixTimeline
import io.element.android.x.matrix.timeline.MatrixTimelineItem
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import org.matrix.rustcomponents.sdk.TimelineListener
class FakeMatrixTimeline : MatrixTimeline {
override var callback: MatrixTimeline.Callback?
get() = null
set(value) {}
override val hasMoreToLoad: Boolean
get() = true
override fun timelineItems(): Flow<List<MatrixTimelineItem>> {
return emptyFlow()
}
override suspend fun paginateBackwards(count: Int): Result<Unit> {
return Result.success(Unit)
}
override fun addListener(timelineListener: TimelineListener) = Unit
override fun initialize() = Unit
override fun dispose() = Unit
override suspend fun sendMessage(message: String): Result<Unit> {
return Result.success(Unit)
}
override suspend fun editMessage(originalEventId: EventId, message: String): Result<Unit> {
return Result.success(Unit)
}
override suspend fun replyMessage(inReplyToEventId: EventId, message: String): Result<Unit> {
return Result.success(Unit)
}
}

1
plugins/src/main/kotlin/extension/DependencyHandleScope.kt

@ -41,7 +41,6 @@ fun DependencyHandlerScope.composeDependencies() {
implementation("androidx.compose.material3:material3") implementation("androidx.compose.material3:material3")
implementation("androidx.compose.material:material-icons-extended") implementation("androidx.compose.material:material-icons-extended")
implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1")
implementation("androidx.activity:activity-compose:1.6.1") implementation("androidx.activity:activity-compose:1.6.1")
debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest") debugImplementation("androidx.compose.ui:ui-test-manifest")

1
settings.gradle.kts

@ -54,3 +54,4 @@ include(":tests:uitests")
include(":anvilannotations") include(":anvilannotations")
include(":anvilcodegen") include(":anvilcodegen")
include(":libraries:architecture") include(":libraries:architecture")
include(":libraries:matrixtest")

Loading…
Cancel
Save