Merge branch 'develop' into feature/bma/documentation
This commit is contained in:
commit
9f252ad621
366 changed files with 3066 additions and 1682 deletions
2
.github/workflows/danger.yml
vendored
2
.github/workflows/danger.yml
vendored
|
|
@ -11,7 +11,7 @@ jobs:
|
||||||
- run: |
|
- run: |
|
||||||
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
||||||
- name: Danger
|
- name: Danger
|
||||||
uses: danger/danger-js@11.2.2
|
uses: danger/danger-js@11.2.3
|
||||||
with:
|
with:
|
||||||
args: "--dangerfile ./tools/danger/dangerfile.js"
|
args: "--dangerfile ./tools/danger/dangerfile.js"
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
2
.github/workflows/maestro.yml
vendored
2
.github/workflows/maestro.yml
vendored
|
|
@ -25,7 +25,7 @@ jobs:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Assemble debug APK
|
- name: Assemble debug APK
|
||||||
run: ./gradlew assembleDebug $CI_GRADLE_ARG_PROPERTIES
|
run: ./gradlew assembleDebug $CI_GRADLE_ARG_PROPERTIES
|
||||||
- uses: mobile-dev-inc/action-maestro-cloud@v1.1.1
|
- uses: mobile-dev-inc/action-maestro-cloud@v1.2.3
|
||||||
with:
|
with:
|
||||||
api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }}
|
api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }}
|
||||||
app-file: app/build/outputs/apk/debug/app-debug.apk
|
app-file: app/build/outputs/apk/debug/app-debug.apk
|
||||||
|
|
|
||||||
2
.github/workflows/quality.yml
vendored
2
.github/workflows/quality.yml
vendored
|
|
@ -37,7 +37,7 @@ jobs:
|
||||||
yarn add danger-plugin-lint-report --dev
|
yarn add danger-plugin-lint-report --dev
|
||||||
- name: Danger lint
|
- name: Danger lint
|
||||||
if: always()
|
if: always()
|
||||||
uses: danger/danger-js@11.2.2
|
uses: danger/danger-js@11.2.3
|
||||||
with:
|
with:
|
||||||
args: "--dangerfile ./tools/danger/dangerfile-lint.js"
|
args: "--dangerfile ./tools/danger/dangerfile-lint.js"
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
25
.github/workflows/tests.yml
vendored
25
.github/workflows/tests.yml
vendored
|
|
@ -23,6 +23,17 @@ jobs:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: ./gradlew test $CI_GRADLE_ARG_PROPERTIES
|
run: ./gradlew test $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
- name: Generate kover report
|
||||||
|
if: always()
|
||||||
|
run: ./gradlew koverMergedReport $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
|
||||||
|
- name: Archive kover report
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: kover-results
|
||||||
|
path: |
|
||||||
|
**/build/reports/kover/merged
|
||||||
|
|
||||||
- name: Archive test results on error
|
- name: Archive test results on error
|
||||||
if: failure()
|
if: failure()
|
||||||
|
|
@ -32,3 +43,17 @@ jobs:
|
||||||
path: |
|
path: |
|
||||||
**/out/failures/
|
**/out/failures/
|
||||||
**/build/reports/tests/*UnitTest/
|
**/build/reports/tests/*UnitTest/
|
||||||
|
|
||||||
|
- name: Publish results to Sonar
|
||||||
|
env:
|
||||||
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
if: ${{ always() && env.SONAR_TOKEN != '' && env.ORG_GRADLE_PROJECT_SONAR_LOGIN != '' }}
|
||||||
|
run: ./gradlew sonar $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
|
||||||
|
# https://github.com/codecov/codecov-action
|
||||||
|
- name: Upload coverage reports to codecov
|
||||||
|
if: always()
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
# with:
|
||||||
|
# files: build/reports/kover/merged/xml/report.xml
|
||||||
|
|
|
||||||
4
.idea/compiler.xml
generated
4
.idea/compiler.xml
generated
|
|
@ -1,6 +1,8 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="CompilerConfiguration">
|
<component name="CompilerConfiguration">
|
||||||
<bytecodeTargetLevel target="11" />
|
<bytecodeTargetLevel target="1.8">
|
||||||
|
<module name="ElementX.libraries.composeutils" target="11" />
|
||||||
|
</bytecodeTargetLevel>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,11 @@
|
||||||
|
[](https://github.com/vector-im/element-x-android/actions/workflows/build.yml?query=branch%3Adevelop)
|
||||||
|
[](https://sonarcloud.io/summary/new_code?id=vector-im_element-x-android)
|
||||||
|
[](https://sonarcloud.io/summary/new_code?id=vector-im_element-x-android)
|
||||||
|
[](https://sonarcloud.io/summary/new_code?id=vector-im_element-x-android)
|
||||||
|
[](https://codecov.io/github/vector-im/element-x-android)
|
||||||
|
[](https://matrix.to/#/#element-android:matrix.org)
|
||||||
|
[](https://translate.element.io/engage/element-android/?utm_source=widget)
|
||||||
|
|
||||||
# element-x-android
|
# element-x-android
|
||||||
|
|
||||||
ElementX Android is a [Matrix](https://matrix.org/) Android Client provided by [Element](https://element.io/).
|
ElementX Android is a [Matrix](https://matrix.org/) Android Client provided by [Element](https://element.io/).
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.anvilannotations
|
package io.element.android.anvilannotations
|
||||||
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":anvilannotations"))
|
implementation(projects.anvilannotations)
|
||||||
api(libs.anvil.compiler.api)
|
api(libs.anvil.compiler.api)
|
||||||
implementation(libs.anvil.compiler.utils)
|
implementation(libs.anvil.compiler.utils)
|
||||||
implementation("com.squareup:kotlinpoet:1.12.0")
|
implementation("com.squareup:kotlinpoet:1.12.0")
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
@file:OptIn(ExperimentalAnvilApi::class)
|
@file:OptIn(ExperimentalAnvilApi::class)
|
||||||
|
|
||||||
package io.element.android.x.anvilcodegen
|
package io.element.android.anvilcodegen
|
||||||
|
|
||||||
import com.google.auto.service.AutoService
|
import com.google.auto.service.AutoService
|
||||||
import com.squareup.anvil.annotations.ContributesTo
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
|
|
@ -46,7 +46,7 @@ import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import dagger.multibindings.IntoMap
|
import dagger.multibindings.IntoMap
|
||||||
import io.element.android.x.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
|
@ -148,7 +148,7 @@ class ContributesNodeCodeGenerator : CodeGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val assistedNodeFactoryFqName = FqName("io.element.android.x.architecture.AssistedNodeFactory")
|
private val assistedNodeFactoryFqName = FqName("io.element.android.libraries.architecture.AssistedNodeFactory")
|
||||||
private val nodeKeyFqName = FqName("io.element.android.x.architecture.NodeKey")
|
private val nodeKeyFqName = FqName("io.element.android.libraries.architecture.NodeKey")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2022 New Vector Ltd
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
*
|
*
|
||||||
|
|
@ -164,14 +162,14 @@ knit {
|
||||||
dependencies {
|
dependencies {
|
||||||
allLibraries()
|
allLibraries()
|
||||||
allFeatures()
|
allFeatures()
|
||||||
implementation(project(":tests:uitests"))
|
implementation(projects.tests.uitests)
|
||||||
implementation(project(":anvilannotations"))
|
implementation(projects.anvilannotations)
|
||||||
anvil(project(":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
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.2.2")
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.2.2")
|
||||||
implementation(libs.appyx.core)
|
implementation(libs.appyx.core)
|
||||||
|
implementation(libs.androidx.splash)
|
||||||
implementation(libs.androidx.corektx)
|
implementation(libs.androidx.corektx)
|
||||||
implementation(libs.androidx.lifecycle.runtime)
|
implementation(libs.androidx.lifecycle.runtime)
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,8 @@
|
||||||
tools:targetApi="33">
|
tools:targetApi="33">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
android:theme="@style/Theme.ElementX.Splash"
|
||||||
|
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|uiMode"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="adjustResize"
|
||||||
tools:ignore="LockedOrientationActivity">
|
tools:ignore="LockedOrientationActivity">
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,10 @@ 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.di.DaggerComponentOwner
|
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.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
|
||||||
import io.element.android.x.initializer.TimberInitializer
|
import io.element.android.x.initializer.TimberInitializer
|
||||||
|
|
@ -40,5 +41,6 @@ class ElementXApplication : Application(), DaggerComponentOwner {
|
||||||
initializeComponent(TimberInitializer::class.java)
|
initializeComponent(TimberInitializer::class.java)
|
||||||
initializeComponent(MatrixInitializer::class.java)
|
initializeComponent(MatrixInitializer::class.java)
|
||||||
}
|
}
|
||||||
|
logApplicationInfo()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,20 +22,23 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.core.view.WindowCompat
|
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.libraries.architecture.bindings
|
||||||
import io.element.android.x.di.DaggerComponentOwner
|
import io.element.android.libraries.designsystem.ElementXTheme
|
||||||
import io.element.android.x.designsystem.ElementXTheme
|
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
|
import io.element.android.x.node.RootFlowNode
|
||||||
|
|
||||||
class MainActivity : NodeComponentActivity() {
|
class MainActivity : NodeComponentActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
installSplashScreen()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val appBindings = bindings<AppBindings>()
|
val appBindings = bindings<AppBindings>()
|
||||||
|
appBindings.matrixClientsHolder().restore(savedInstanceState)
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
setContent {
|
setContent {
|
||||||
ElementXTheme {
|
ElementXTheme {
|
||||||
|
|
@ -48,11 +51,17 @@ class MainActivity : NodeComponentActivity() {
|
||||||
buildContext = it,
|
buildContext = it,
|
||||||
appComponentOwner = applicationContext as DaggerComponentOwner,
|
appComponentOwner = applicationContext as DaggerComponentOwner,
|
||||||
authenticationService = appBindings.authenticationService(),
|
authenticationService = appBindings.authenticationService(),
|
||||||
rootPresenter = appBindings.rootPresenter()
|
presenter = appBindings.rootPresenter(),
|
||||||
|
matrixClientsHolder = appBindings.matrixClientsHolder()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
bindings<AppBindings>().matrixClientsHolder().onSaveInstanceState(outState)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,14 @@
|
||||||
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.auth.MatrixAuthenticationService
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.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
|
||||||
|
|
||||||
@ContributesTo(AppScope::class)
|
@ContributesTo(AppScope::class)
|
||||||
interface AppBindings {
|
interface AppBindings {
|
||||||
fun coroutineScope(): CoroutineScope
|
|
||||||
fun rootPresenter(): RootPresenter
|
fun rootPresenter(): RootPresenter
|
||||||
fun authenticationService(): MatrixAuthenticationService
|
fun authenticationService(): MatrixAuthenticationService
|
||||||
|
fun matrixClientsHolder(): MatrixClientsHolder
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,10 @@ import android.content.Context
|
||||||
import com.squareup.anvil.annotations.MergeComponent
|
import com.squareup.anvil.annotations.MergeComponent
|
||||||
import dagger.BindsInstance
|
import dagger.BindsInstance
|
||||||
import dagger.Component
|
import dagger.Component
|
||||||
import io.element.android.x.architecture.NodeFactoriesBindings
|
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
|
import io.element.android.libraries.di.SingleIn
|
||||||
|
|
||||||
@SingleIn(AppScope::class)
|
@SingleIn(AppScope::class)
|
||||||
@MergeComponent(AppScope::class)
|
@MergeComponent(AppScope::class)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,10 @@ 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.x.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
|
import io.element.android.libraries.di.SingleIn
|
||||||
import kotlinx.coroutines.CoroutineName
|
import kotlinx.coroutines.CoroutineName
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* 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.os.Bundle
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.di.SingleIn
|
||||||
|
import io.element.android.libraries.matrix.MatrixClient
|
||||||
|
import io.element.android.libraries.matrix.auth.MatrixAuthenticationService
|
||||||
|
import io.element.android.libraries.matrix.core.SessionId
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val SAVE_INSTANCE_KEY = "io.element.android.x.di.MatrixClientsHolder.SaveInstanceKey"
|
||||||
|
|
||||||
|
@SingleIn(AppScope::class)
|
||||||
|
class MatrixClientsHolder @Inject constructor(private val authenticationService: MatrixAuthenticationService) {
|
||||||
|
|
||||||
|
private val sessionIdsToMatrixClient = ConcurrentHashMap<SessionId, MatrixClient>()
|
||||||
|
|
||||||
|
fun add(matrixClient: MatrixClient) {
|
||||||
|
sessionIdsToMatrixClient[matrixClient.sessionId] = matrixClient
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeAll() {
|
||||||
|
sessionIdsToMatrixClient.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remove(sessionId: SessionId) {
|
||||||
|
sessionIdsToMatrixClient.remove(sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isEmpty(): Boolean = sessionIdsToMatrixClient.isEmpty()
|
||||||
|
|
||||||
|
fun knowSession(sessionId: SessionId): Boolean = sessionIdsToMatrixClient.containsKey(sessionId)
|
||||||
|
|
||||||
|
fun getOrNull(sessionId: SessionId): MatrixClient? {
|
||||||
|
return sessionIdsToMatrixClient[sessionId]
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
fun restore(savedInstanceState: Bundle?) {
|
||||||
|
if (savedInstanceState == null || sessionIdsToMatrixClient.isNotEmpty()) return
|
||||||
|
val sessionIds = savedInstanceState.getSerializable(SAVE_INSTANCE_KEY) as? Array<SessionId>
|
||||||
|
if (sessionIds.isNullOrEmpty()) return
|
||||||
|
// Not ideal but should only happens in case of process recreation. This ensure we restore all the active sessions before restoring the node graphs.
|
||||||
|
runBlocking {
|
||||||
|
sessionIds.forEach { sessionId ->
|
||||||
|
Timber.v("Restore matrix session: $sessionId")
|
||||||
|
val matrixClient = authenticationService.restoreSession(sessionId)
|
||||||
|
if (matrixClient != null) {
|
||||||
|
add(matrixClient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
val sessionKeys = sessionIdsToMatrixClient.keys.toTypedArray()
|
||||||
|
Timber.v("Save matrix session keys = $sessionKeys")
|
||||||
|
outState.putSerializable(SAVE_INSTANCE_KEY, sessionKeys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,8 +20,11 @@ import com.squareup.anvil.annotations.ContributesTo
|
||||||
import com.squareup.anvil.annotations.MergeSubcomponent
|
import com.squareup.anvil.annotations.MergeSubcomponent
|
||||||
import dagger.BindsInstance
|
import dagger.BindsInstance
|
||||||
import dagger.Subcomponent
|
import dagger.Subcomponent
|
||||||
import io.element.android.x.architecture.NodeFactoriesBindings
|
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||||
import io.element.android.x.matrix.room.MatrixRoom
|
import io.element.android.libraries.di.RoomScope
|
||||||
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.di.SingleIn
|
||||||
|
import io.element.android.libraries.matrix.room.MatrixRoom
|
||||||
|
|
||||||
@SingleIn(RoomScope::class)
|
@SingleIn(RoomScope::class)
|
||||||
@MergeSubcomponent(RoomScope::class)
|
@MergeSubcomponent(RoomScope::class)
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,11 @@ import com.squareup.anvil.annotations.ContributesTo
|
||||||
import com.squareup.anvil.annotations.MergeSubcomponent
|
import com.squareup.anvil.annotations.MergeSubcomponent
|
||||||
import dagger.BindsInstance
|
import dagger.BindsInstance
|
||||||
import dagger.Subcomponent
|
import dagger.Subcomponent
|
||||||
import io.element.android.x.architecture.NodeFactoriesBindings
|
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||||
import io.element.android.x.matrix.MatrixClient
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.di.SingleIn
|
||||||
|
import io.element.android.libraries.matrix.MatrixClient
|
||||||
|
|
||||||
@SingleIn(SessionScope::class)
|
@SingleIn(SessionScope::class)
|
||||||
@MergeSubcomponent(SessionScope::class)
|
@MergeSubcomponent(SessionScope::class)
|
||||||
|
|
|
||||||
44
app/src/main/kotlin/io/element/android/x/info/Logs.kt
Normal file
44
app/src/main/kotlin/io/element/android/x/info/Logs.kt
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* 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.info
|
||||||
|
|
||||||
|
import io.element.android.x.BuildConfig
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
fun logApplicationInfo() {
|
||||||
|
val appVersion = buildString {
|
||||||
|
append(BuildConfig.VERSION_NAME)
|
||||||
|
append(" (")
|
||||||
|
append(BuildConfig.VERSION_CODE)
|
||||||
|
append(") - ")
|
||||||
|
append(BuildConfig.BUILD_TYPE)
|
||||||
|
}
|
||||||
|
// TODO Get SDK version somehow
|
||||||
|
val sdkVersion = "SDK VERSION (TODO)"
|
||||||
|
val date = SimpleDateFormat("MM-dd HH:mm:ss.SSSZ", Locale.US).format(Date())
|
||||||
|
|
||||||
|
Timber.d("----------------------------------------------------------------")
|
||||||
|
Timber.d("----------------------------------------------------------------")
|
||||||
|
Timber.d(" Application version: $appVersion")
|
||||||
|
Timber.d(" SDK version: $sdkVersion")
|
||||||
|
Timber.d(" Local time: $date")
|
||||||
|
Timber.d("----------------------------------------------------------------")
|
||||||
|
Timber.d("----------------------------------------------------------------\n\n\n\n")
|
||||||
|
}
|
||||||
|
|
@ -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.x.features.rageshake.crash.VectorUncaughtExceptionHandler
|
import io.element.android.features.rageshake.crash.VectorUncaughtExceptionHandler
|
||||||
|
|
||||||
class CrashInitializer : Initializer<Unit> {
|
class CrashInitializer : Initializer<Unit> {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ 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.libraries.matrix.tracing.TracingConfigurations
|
||||||
|
import io.element.android.libraries.matrix.tracing.setupTracing
|
||||||
import io.element.android.x.BuildConfig
|
import io.element.android.x.BuildConfig
|
||||||
import io.element.android.x.matrix.tracing.TracingConfigurations
|
|
||||||
import io.element.android.x.matrix.tracing.setupTracing
|
|
||||||
|
|
||||||
class MatrixInitializer : Initializer<Unit> {
|
class MatrixInitializer : Initializer<Unit> {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ 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.x.BuildConfig
|
import io.element.android.x.BuildConfig
|
||||||
import io.element.android.x.features.rageshake.logs.VectorFileLogger
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class TimberInitializer : Initializer<Unit> {
|
class TimberInitializer : Initializer<Unit> {
|
||||||
|
|
|
||||||
|
|
@ -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.node
|
||||||
|
|
||||||
|
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||||
|
import com.bumble.appyx.navmodel.backstack.operation.NewRoot
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't process NewRoot if the nav target already exists in the stack.
|
||||||
|
*/
|
||||||
|
fun <T : Any> BackStack<T>.safeRoot(element: T) {
|
||||||
|
val containsRoot = elements.value.any {
|
||||||
|
it.key.navTarget == element
|
||||||
|
}
|
||||||
|
if (containsRoot) return
|
||||||
|
accept(NewRoot(element))
|
||||||
|
}
|
||||||
|
|
@ -32,16 +32,17 @@ 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.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.x.architecture.bindings
|
import io.element.android.features.preferences.PreferencesFlowNode
|
||||||
import io.element.android.x.architecture.createNode
|
import io.element.android.features.roomlist.RoomListNode
|
||||||
import io.element.android.x.di.DaggerComponentOwner
|
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
|
||||||
|
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.MatrixClient
|
||||||
|
import io.element.android.libraries.matrix.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.ui.di.MatrixUIBindings
|
||||||
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.roomlist.RoomListNode
|
|
||||||
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.ui.di.MatrixUIBindings
|
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
class LoggedInFlowNode(
|
class LoggedInFlowNode(
|
||||||
|
|
@ -124,6 +125,11 @@ class LoggedInFlowNode(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
Children(navModel = backstack)
|
Children(
|
||||||
|
navModel = backstack,
|
||||||
|
modifier = modifier,
|
||||||
|
// Animate navigation to settings and to a room
|
||||||
|
transitionHandler = rememberDefaultTransitionHandler(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,10 @@ import com.bumble.appyx.core.node.Node
|
||||||
import com.bumble.appyx.core.node.ParentNode
|
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.navmodel.backstack.BackStack
|
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.replace
|
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||||
import io.element.android.x.features.login.LoginFlowNode
|
import io.element.android.features.login.LoginFlowNode
|
||||||
import io.element.android.x.features.onboarding.OnBoardingScreen
|
import io.element.android.features.onboarding.OnBoardingScreen
|
||||||
|
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
|
@ -62,7 +63,7 @@ class NotLoggedInFlowNode(
|
||||||
return when (navTarget) {
|
return when (navTarget) {
|
||||||
NavTarget.OnBoarding -> node(buildContext) {
|
NavTarget.OnBoarding -> node(buildContext) {
|
||||||
OnBoardingScreen(
|
OnBoardingScreen(
|
||||||
onSignIn = { backstack.replace(NavTarget.LoginFlow) }
|
onSignIn = { backstack.push(NavTarget.LoginFlow) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
NavTarget.LoginFlow -> LoginFlowNode(buildContext)
|
NavTarget.LoginFlow -> LoginFlowNode(buildContext)
|
||||||
|
|
@ -71,6 +72,11 @@ class NotLoggedInFlowNode(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
Children(navModel = backstack)
|
Children(
|
||||||
|
navModel = backstack,
|
||||||
|
modifier = modifier,
|
||||||
|
// Animate navigation to login screen
|
||||||
|
transitionHandler = rememberDefaultTransitionHandler(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,12 @@ 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.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.features.messages.MessagesNode
|
||||||
import io.element.android.x.architecture.createNode
|
import io.element.android.libraries.architecture.bindings
|
||||||
import io.element.android.x.di.DaggerComponentOwner
|
import io.element.android.libraries.architecture.createNode
|
||||||
|
import io.element.android.libraries.di.DaggerComponentOwner
|
||||||
|
import io.element.android.libraries.matrix.room.MatrixRoom
|
||||||
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.matrix.room.MatrixRoom
|
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
|
@ -70,6 +70,9 @@ class RoomFlowNode(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
Children(navModel = backstack)
|
Children(
|
||||||
|
navModel = backstack,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,10 @@ import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
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.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.ParentNode
|
||||||
|
|
@ -36,13 +33,13 @@ 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.x.architecture.createNode
|
import io.element.android.features.rageshake.bugreport.BugReportNode
|
||||||
import io.element.android.x.architecture.presenterConnector
|
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
|
||||||
import io.element.android.x.di.DaggerComponentOwner
|
import io.element.android.libraries.architecture.createNode
|
||||||
import io.element.android.x.features.rageshake.bugreport.BugReportNode
|
import io.element.android.libraries.di.DaggerComponentOwner
|
||||||
import io.element.android.x.matrix.MatrixClient
|
import io.element.android.libraries.matrix.auth.MatrixAuthenticationService
|
||||||
import io.element.android.x.matrix.auth.MatrixAuthenticationService
|
import io.element.android.libraries.matrix.core.SessionId
|
||||||
import io.element.android.x.matrix.core.SessionId
|
import io.element.android.x.di.MatrixClientsHolder
|
||||||
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
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
|
@ -50,66 +47,96 @@ 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
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
|
|
||||||
class RootFlowNode(
|
class RootFlowNode(
|
||||||
buildContext: BuildContext,
|
private val buildContext: BuildContext,
|
||||||
private val backstack: BackStack<NavTarget> = BackStack(
|
private val backstack: BackStack<NavTarget> = BackStack(
|
||||||
initialElement = NavTarget.SplashScreen,
|
initialElement = NavTarget.SplashScreen,
|
||||||
savedStateMap = buildContext.savedStateMap,
|
savedStateMap = buildContext.savedStateMap,
|
||||||
),
|
),
|
||||||
private val appComponentOwner: DaggerComponentOwner,
|
private val appComponentOwner: DaggerComponentOwner,
|
||||||
private val authenticationService: MatrixAuthenticationService,
|
private val authenticationService: MatrixAuthenticationService,
|
||||||
rootPresenter: RootPresenter
|
private val matrixClientsHolder: MatrixClientsHolder,
|
||||||
|
private val presenter: RootPresenter
|
||||||
) :
|
) :
|
||||||
ParentNode<RootFlowNode.NavTarget>(
|
ParentNode<RootFlowNode.NavTarget>(
|
||||||
navModel = backstack,
|
navModel = backstack,
|
||||||
buildContext = buildContext,
|
buildContext = buildContext
|
||||||
),
|
),
|
||||||
|
|
||||||
DaggerComponentOwner by appComponentOwner {
|
DaggerComponentOwner by appComponentOwner {
|
||||||
|
|
||||||
private val matrixClientsHolder = ConcurrentHashMap<SessionId, MatrixClient>()
|
|
||||||
private val presenterConnector = presenterConnector(rootPresenter)
|
|
||||||
|
|
||||||
override fun onBuilt() {
|
override fun onBuilt() {
|
||||||
super.onBuilt()
|
super.onBuilt()
|
||||||
whenChildAttached(LoggedInFlowNode::class) { _, child ->
|
observeLoggedInState()
|
||||||
child.lifecycle.subscribe(
|
}
|
||||||
onDestroy = { matrixClientsHolder.remove(child.sessionId) }
|
|
||||||
)
|
private fun observeLoggedInState() {
|
||||||
}
|
|
||||||
authenticationService.isLoggedIn()
|
authenticationService.isLoggedIn()
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.onEach { isLoggedIn ->
|
.onEach { isLoggedIn ->
|
||||||
Timber.v("isLoggedIn=$isLoggedIn")
|
Timber.v("isLoggedIn=$isLoggedIn")
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
val matrixClient = authenticationService.restoreSession()
|
tryToRestoreLatestSession(
|
||||||
if (matrixClient == null) {
|
onSuccess = { switchToLoggedInFlow(it) },
|
||||||
backstack.newRoot(NavTarget.NotLoggedInFlow)
|
onFailure = { switchToLogoutFlow() }
|
||||||
} else {
|
)
|
||||||
matrixClientsHolder[matrixClient.sessionId] = matrixClient
|
|
||||||
backstack.newRoot(NavTarget.LoggedInFlow(matrixClient.sessionId))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
backstack.newRoot(NavTarget.NotLoggedInFlow)
|
switchToLogoutFlow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.launchIn(lifecycleScope)
|
.launchIn(lifecycleScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun switchToLoggedInFlow(sessionId: SessionId) {
|
||||||
|
backstack.safeRoot(NavTarget.LoggedInFlow(sessionId = sessionId))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun switchToLogoutFlow() {
|
||||||
|
matrixClientsHolder.removeAll()
|
||||||
|
backstack.safeRoot(NavTarget.NotLoggedInFlow)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun tryToRestoreLatestSession(
|
||||||
|
onSuccess: (SessionId) -> Unit = {},
|
||||||
|
onFailure: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
val latestKnownSessionId = authenticationService.getLatestSessionId()
|
||||||
|
if (latestKnownSessionId == null) {
|
||||||
|
onFailure()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (matrixClientsHolder.knowSession(latestKnownSessionId)) {
|
||||||
|
onSuccess(latestKnownSessionId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val matrixClient = authenticationService.restoreSession(latestKnownSessionId)
|
||||||
|
if (matrixClient == null) {
|
||||||
|
Timber.v("Failed to restore session...")
|
||||||
|
onFailure()
|
||||||
|
} else {
|
||||||
|
matrixClientsHolder.add(matrixClient)
|
||||||
|
onSuccess(matrixClient.sessionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun onOpenBugReport() {
|
private fun onOpenBugReport() {
|
||||||
backstack.push(NavTarget.BugReport)
|
backstack.push(NavTarget.BugReport)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state by presenterConnector.stateFlow.collectAsState()
|
val state = presenter.present()
|
||||||
RootView(
|
RootView(
|
||||||
state = state,
|
state = state,
|
||||||
|
modifier = modifier,
|
||||||
onOpenBugReport = this::onOpenBugReport,
|
onOpenBugReport = this::onOpenBugReport,
|
||||||
) {
|
) {
|
||||||
Children(navModel = backstack)
|
Children(
|
||||||
|
navModel = backstack,
|
||||||
|
// Animate opening the bug report screen
|
||||||
|
transitionHandler = rememberDefaultTransitionHandler(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,8 +163,10 @@ class RootFlowNode(
|
||||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||||
return when (navTarget) {
|
return when (navTarget) {
|
||||||
is NavTarget.LoggedInFlow -> {
|
is NavTarget.LoggedInFlow -> {
|
||||||
val matrixClient =
|
val matrixClient = matrixClientsHolder.getOrNull(navTarget.sessionId) ?: return splashNode(buildContext).also {
|
||||||
matrixClientsHolder[navTarget.sessionId] ?: throw IllegalStateException("Makes sure to give a matrixClient with the given sessionId")
|
Timber.w("Couldn't find any session, go through SplashScreen")
|
||||||
|
backstack.newRoot(NavTarget.SplashScreen)
|
||||||
|
}
|
||||||
LoggedInFlowNode(
|
LoggedInFlowNode(
|
||||||
buildContext = buildContext,
|
buildContext = buildContext,
|
||||||
sessionId = navTarget.sessionId,
|
sessionId = navTarget.sessionId,
|
||||||
|
|
@ -146,12 +175,14 @@ class RootFlowNode(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
NavTarget.NotLoggedInFlow -> NotLoggedInFlowNode(buildContext)
|
NavTarget.NotLoggedInFlow -> NotLoggedInFlowNode(buildContext)
|
||||||
NavTarget.SplashScreen -> node(buildContext) {
|
NavTarget.SplashScreen -> splashNode(buildContext)
|
||||||
Box(modifier = it.fillMaxSize(), contentAlignment = Alignment.Center) {
|
|
||||||
CircularProgressIndicator()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NavTarget.BugReport -> createNode<BugReportNode>(buildContext, plugins = listOf(bugReportNodeCallback))
|
NavTarget.BugReport -> createNode<BugReportNode>(buildContext, plugins = listOf(bugReportNodeCallback))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun splashNode(buildContext: BuildContext) = node(buildContext) {
|
||||||
|
Box(modifier = it.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,10 @@ package io.element.android.x.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.x.architecture.Presenter
|
import io.element.android.features.rageshake.bugreport.BugReportPresenter
|
||||||
import io.element.android.x.features.rageshake.bugreport.BugReportPresenter
|
import io.element.android.features.rageshake.crash.ui.CrashDetectionPresenter
|
||||||
import io.element.android.x.features.rageshake.crash.ui.CrashDetectionPresenter
|
import io.element.android.features.rageshake.detection.RageshakeDetectionPresenter
|
||||||
import io.element.android.x.features.rageshake.detection.RageshakeDetectionPresenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RootPresenter @Inject constructor(
|
class RootPresenter @Inject constructor(
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@
|
||||||
package io.element.android.x.root
|
package io.element.android.x.root
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import io.element.android.x.features.rageshake.bugreport.BugReportState
|
import io.element.android.features.rageshake.bugreport.BugReportState
|
||||||
import io.element.android.x.features.rageshake.crash.ui.CrashDetectionState
|
import io.element.android.features.rageshake.crash.ui.CrashDetectionState
|
||||||
import io.element.android.x.features.rageshake.detection.RageshakeDetectionState
|
import io.element.android.features.rageshake.detection.RageshakeDetectionState
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class RootState(
|
data class RootState(
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,12 @@ 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.platform.LocalContext
|
||||||
|
import io.element.android.features.rageshake.crash.ui.CrashDetectionEvents
|
||||||
|
import io.element.android.features.rageshake.crash.ui.CrashDetectionView
|
||||||
|
import io.element.android.features.rageshake.detection.RageshakeDetectionEvents
|
||||||
|
import io.element.android.features.rageshake.detection.RageshakeDetectionView
|
||||||
|
import io.element.android.tests.uitests.openShowkase
|
||||||
import io.element.android.x.component.ShowkaseButton
|
import io.element.android.x.component.ShowkaseButton
|
||||||
import io.element.android.x.features.rageshake.crash.ui.CrashDetectionEvents
|
|
||||||
import io.element.android.x.features.rageshake.crash.ui.CrashDetectionView
|
|
||||||
import io.element.android.x.features.rageshake.detection.RageshakeDetectionEvents
|
|
||||||
import io.element.android.x.features.rageshake.detection.RageshakeDetectionView
|
|
||||||
import io.element.android.x.tests.uitests.openShowkase
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RootView(
|
fun RootView(
|
||||||
|
|
|
||||||
8
app/src/main/res/drawable/splash_icon.xml
Normal file
8
app/src/main/res/drawable/splash_icon.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<inset
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:drawable="@mipmap/ic_launcher_round"
|
||||||
|
android:insetTop="80dp"
|
||||||
|
android:insetRight="80dp"
|
||||||
|
android:insetBottom="80dp"
|
||||||
|
android:insetLeft="80dp" />
|
||||||
26
app/src/main/res/values-night/themes.xml
Normal file
26
app/src/main/res/values-night/themes.xml
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<style name="Theme.ElementX.Splash" parent="Theme.SplashScreen">
|
||||||
|
<item name="windowSplashScreenBackground">@color/black</item>
|
||||||
|
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
|
||||||
|
<item name="postSplashScreenTheme">@style/Theme.ElementX</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.ElementX" parent="Theme.ElementAndroid" />
|
||||||
|
</resources>
|
||||||
|
|
@ -16,6 +16,10 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<resources>
|
<resources>
|
||||||
|
<style name="Theme.ElementX.Splash" parent="Theme.SplashScreen.IconBackground">
|
||||||
|
<item name="windowSplashScreenBackground">@color/white</item>
|
||||||
|
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
|
||||||
|
<item name="postSplashScreenTheme">@style/Theme.ElementX</item>
|
||||||
|
</style>
|
||||||
<style name="Theme.ElementX" parent="Theme.ElementAndroid" />
|
<style name="Theme.ElementX" parent="Theme.ElementAndroid" />
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ plugins {
|
||||||
alias(libs.plugins.detekt)
|
alias(libs.plugins.detekt)
|
||||||
alias(libs.plugins.ktlint)
|
alias(libs.plugins.ktlint)
|
||||||
alias(libs.plugins.dependencygraph)
|
alias(libs.plugins.dependencygraph)
|
||||||
|
alias(libs.plugins.sonarqube)
|
||||||
|
alias(libs.plugins.kover)
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<Delete>("clean").configure {
|
tasks.register<Delete>("clean").configure {
|
||||||
|
|
@ -108,3 +110,68 @@ allprojects {
|
||||||
plugin("org.owasp.dependencycheck")
|
plugin("org.owasp.dependencycheck")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// To run a sonar analysis:
|
||||||
|
// Run './gradlew sonar -Dsonar.login=<SONAR_LOGIN>'
|
||||||
|
// The SONAR_LOGIN is stored in passbolt as Token Sonar Cloud Bma
|
||||||
|
// Sonar result can be found here: https://sonarcloud.io/project/overview?id=vector-im_element-x-android
|
||||||
|
sonar {
|
||||||
|
properties {
|
||||||
|
property("sonar.projectName", "element-x-android")
|
||||||
|
property("sonar.projectKey", "vector-im_element-x-android")
|
||||||
|
property("sonar.host.url", "https://sonarcloud.io")
|
||||||
|
property("sonar.projectVersion", "1.0") // TODO project(":app").android.defaultConfig.versionName)
|
||||||
|
property("sonar.sourceEncoding", "UTF-8")
|
||||||
|
property("sonar.links.homepage", "https://github.com/vector-im/element-x-android/")
|
||||||
|
property("sonar.links.ci", "https://github.com/vector-im/element-x-android/actions")
|
||||||
|
property("sonar.links.scm", "https://github.com/vector-im/element-x-android/")
|
||||||
|
property("sonar.links.issue", "https://github.com/vector-im/element-x-android/issues")
|
||||||
|
property("sonar.organization", "new_vector_ltd_organization")
|
||||||
|
property("sonar.login", if (project.hasProperty("SONAR_LOGIN")) project.property("SONAR_LOGIN")!! else "invalid")
|
||||||
|
|
||||||
|
// exclude source code from analyses separated by a colon (:)
|
||||||
|
// Exclude Java source
|
||||||
|
property("sonar.exclusions", "**/BugReporterMultipartBody.java")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
val projectDir = projectDir.toString()
|
||||||
|
sonar {
|
||||||
|
properties {
|
||||||
|
// Note: folders `kotlin` are not supported (yet), I asked on their side: https://community.sonarsource.com/t/82824
|
||||||
|
// As a workaround provide the path in `sonar.sources` property.
|
||||||
|
if (File("$projectDir/src/main/kotlin").exists()) {
|
||||||
|
property("sonar.sources", "src/main/kotlin")
|
||||||
|
}
|
||||||
|
if (File("$projectDir/src/test/kotlin").exists()) {
|
||||||
|
property("sonar.tests", "src/test/kotlin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
apply(plugin = "kover")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run `./gradlew koverMergedHtmlReport` to get report at ./build/reports/kover
|
||||||
|
// Run `./gradlew koverMergedReport` to also get XML report
|
||||||
|
koverMerged {
|
||||||
|
enable()
|
||||||
|
|
||||||
|
filters {
|
||||||
|
classes {
|
||||||
|
excludes.addAll(
|
||||||
|
listOf(
|
||||||
|
/*
|
||||||
|
"*Fragment",
|
||||||
|
"*Fragment\$*",
|
||||||
|
"*Activity",
|
||||||
|
"*Activity\$*",
|
||||||
|
*/
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.x.features.login"
|
namespace = "io.element.android.features.login"
|
||||||
}
|
}
|
||||||
|
|
||||||
anvil {
|
anvil {
|
||||||
|
|
@ -32,16 +32,15 @@ anvil {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":anvilannotations"))
|
implementation(projects.anvilannotations)
|
||||||
anvil(project(":anvilcodegen"))
|
anvil(projects.anvilcodegen)
|
||||||
implementation(project(":libraries:di"))
|
implementation(projects.libraries.core)
|
||||||
implementation(project(":libraries:core"))
|
implementation(projects.libraries.architecture)
|
||||||
implementation(project(":libraries:architecture"))
|
implementation(projects.libraries.matrix)
|
||||||
implementation(project(":libraries:matrix"))
|
implementation(projects.libraries.designsystem)
|
||||||
implementation(project(":libraries:designsystem"))
|
implementation(projects.libraries.elementresources)
|
||||||
implementation(project(":libraries:elementresources"))
|
implementation(projects.libraries.testtags)
|
||||||
implementation(libs.appyx.core)
|
implementation(projects.libraries.uiStrings)
|
||||||
implementation(project(":libraries:ui-strings"))
|
|
||||||
ksp(libs.showkase.processor)
|
ksp(libs.showkase.processor)
|
||||||
testImplementation(libs.test.junit)
|
testImplementation(libs.test.junit)
|
||||||
androidTestImplementation(libs.test.junitext)
|
androidTestImplementation(libs.test.junitext)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.login
|
package io.element.android.features.login
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
|
@ -33,6 +33,6 @@ class ExampleInstrumentedTest {
|
||||||
fun useAppContext() {
|
fun useAppContext() {
|
||||||
// Context of the app under test.
|
// Context of the app under test.
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
assertEquals("io.element.android.x.features.login.test", appContext.packageName)
|
assertEquals("io.element.android.features.login.test", appContext.packageName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.login
|
package io.element.android.features.login
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -25,9 +25,10 @@ import com.bumble.appyx.core.node.Node
|
||||||
import com.bumble.appyx.core.node.ParentNode
|
import com.bumble.appyx.core.node.ParentNode
|
||||||
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.x.architecture.createNode
|
import io.element.android.features.login.changeserver.ChangeServerNode
|
||||||
import io.element.android.x.features.login.changeserver.ChangeServerNode
|
import io.element.android.features.login.root.LoginRootNode
|
||||||
import io.element.android.x.features.login.root.LoginRootNode
|
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
|
||||||
|
import io.element.android.libraries.architecture.createNode
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
class LoginFlowNode(
|
class LoginFlowNode(
|
||||||
|
|
@ -64,6 +65,11 @@ class LoginFlowNode(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
Children(navModel = backstack)
|
Children(
|
||||||
|
navModel = backstack,
|
||||||
|
modifier = modifier,
|
||||||
|
// Animate transition to change server screen
|
||||||
|
transitionHandler = rememberDefaultTransitionHandler(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.login.changeserver
|
package io.element.android.features.login.changeserver
|
||||||
|
|
||||||
sealed interface ChangeServerEvents {
|
sealed interface ChangeServerEvents {
|
||||||
data class SetServer(val server: String) : ChangeServerEvents
|
data class SetServer(val server: String) : ChangeServerEvents
|
||||||
|
|
@ -14,20 +14,17 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.login.changeserver
|
package io.element.android.features.login.changeserver
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
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
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.x.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.x.architecture.presenterConnector
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.x.di.AppScope
|
|
||||||
|
|
||||||
@ContributesNode(AppScope::class)
|
@ContributesNode(AppScope::class)
|
||||||
class ChangeServerNode @AssistedInject constructor(
|
class ChangeServerNode @AssistedInject constructor(
|
||||||
|
|
@ -36,17 +33,16 @@ class ChangeServerNode @AssistedInject constructor(
|
||||||
private val presenter: ChangeServerPresenter,
|
private val presenter: ChangeServerPresenter,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
|
||||||
private val presenterConnector = presenterConnector(presenter)
|
|
||||||
|
|
||||||
private fun onSuccess() {
|
private fun onSuccess() {
|
||||||
navigateUp()
|
navigateUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state by presenterConnector.stateFlow.collectAsState()
|
val state = presenter.present()
|
||||||
ChangeServerView(
|
ChangeServerView(
|
||||||
state = state,
|
state = state,
|
||||||
|
modifier = modifier,
|
||||||
onChangeServerSuccess = this::onSuccess,
|
onChangeServerSuccess = this::onSuccess,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.login.changeserver
|
package io.element.android.features.login.changeserver
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
|
@ -22,10 +22,10 @@ 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.x.architecture.Async
|
import io.element.android.libraries.architecture.Async
|
||||||
import io.element.android.x.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.x.architecture.execute
|
import io.element.android.libraries.architecture.execute
|
||||||
import io.element.android.x.matrix.auth.MatrixAuthenticationService
|
import io.element.android.libraries.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
|
||||||
|
|
@ -14,9 +14,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.login.changeserver
|
package io.element.android.features.login.changeserver
|
||||||
|
|
||||||
import io.element.android.x.architecture.Async
|
import io.element.android.libraries.architecture.Async
|
||||||
|
|
||||||
data class ChangeServerState(
|
data class ChangeServerState(
|
||||||
val homeserver: String = "",
|
val homeserver: String = "",
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
||||||
package io.element.android.x.features.login.changeserver
|
package io.element.android.features.login.changeserver
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
|
@ -52,11 +52,13 @@ 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.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import io.element.android.x.architecture.Async
|
import io.element.android.features.login.R
|
||||||
import io.element.android.x.core.compose.textFieldState
|
import io.element.android.features.login.error.changeServerError
|
||||||
import io.element.android.x.designsystem.components.VectorIcon
|
import io.element.android.libraries.architecture.Async
|
||||||
import io.element.android.x.features.login.R
|
import io.element.android.libraries.designsystem.components.VectorIcon
|
||||||
import io.element.android.x.features.login.error.changeServerError
|
import io.element.android.libraries.designsystem.components.form.textFieldState
|
||||||
|
import io.element.android.libraries.testtags.TestTags
|
||||||
|
import io.element.android.libraries.testtags.testTag
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChangeServerView(
|
fun ChangeServerView(
|
||||||
|
|
@ -129,6 +131,7 @@ fun ChangeServerView(
|
||||||
value = homeserverFieldState,
|
value = homeserverFieldState,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.testTag(TestTags.changeServerServer)
|
||||||
.padding(top = 200.dp),
|
.padding(top = 200.dp),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
homeserverFieldState = it
|
homeserverFieldState = it
|
||||||
|
|
@ -162,6 +165,7 @@ fun ChangeServerView(
|
||||||
enabled = state.submitEnabled,
|
enabled = state.submitEnabled,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.testTag(TestTags.changeServerContinue)
|
||||||
.padding(top = 44.dp)
|
.padding(top = 44.dp)
|
||||||
) {
|
) {
|
||||||
Text(text = "Continue")
|
Text(text = "Continue")
|
||||||
|
|
@ -14,13 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.login.error
|
package io.element.android.features.login.error
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import io.element.android.x.core.uri.isValidUrl
|
import io.element.android.features.login.root.LoginFormState
|
||||||
import io.element.android.x.features.login.root.LoginFormState
|
import io.element.android.libraries.core.uri.isValidUrl
|
||||||
import io.element.android.x.ui.strings.R as StringR
|
import io.element.android.libraries.ui.strings.R as StringR
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun loginError(
|
fun loginError(
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.login.root
|
package io.element.android.features.login.root
|
||||||
|
|
||||||
sealed interface LoginRootEvents {
|
sealed interface LoginRootEvents {
|
||||||
object RefreshHomeServer : LoginRootEvents
|
object RefreshHomeServer : LoginRootEvents
|
||||||
|
|
@ -14,11 +14,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.login.root
|
package io.element.android.features.login.root
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
|
|
@ -27,10 +25,9 @@ import com.bumble.appyx.core.plugin.Plugin
|
||||||
import com.bumble.appyx.core.plugin.plugins
|
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.x.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.x.architecture.presenterConnector
|
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
||||||
import io.element.android.x.core.compose.OnLifecycleEvent
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.x.di.AppScope
|
|
||||||
|
|
||||||
@ContributesNode(AppScope::class)
|
@ContributesNode(AppScope::class)
|
||||||
class LoginRootNode @AssistedInject constructor(
|
class LoginRootNode @AssistedInject constructor(
|
||||||
|
|
@ -39,8 +36,6 @@ class LoginRootNode @AssistedInject constructor(
|
||||||
private val presenter: LoginRootPresenter,
|
private val presenter: LoginRootPresenter,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
|
||||||
private val presenterConnector = presenterConnector(presenter)
|
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun onChangeHomeServer()
|
fun onChangeHomeServer()
|
||||||
}
|
}
|
||||||
|
|
@ -51,7 +46,7 @@ class LoginRootNode @AssistedInject constructor(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state by presenterConnector.stateFlow.collectAsState()
|
val state = presenter.present()
|
||||||
OnLifecycleEvent { _, event ->
|
OnLifecycleEvent { _, event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
Lifecycle.Event.ON_RESUME -> state.eventSink(LoginRootEvents.RefreshHomeServer)
|
Lifecycle.Event.ON_RESUME -> state.eventSink(LoginRootEvents.RefreshHomeServer)
|
||||||
|
|
@ -60,6 +55,7 @@ class LoginRootNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
LoginRootScreen(
|
LoginRootScreen(
|
||||||
state = state,
|
state = state,
|
||||||
|
modifier = modifier,
|
||||||
onChangeServer = this::onChangeHomeServer,
|
onChangeServer = this::onChangeHomeServer,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.login.root
|
package io.element.android.features.login.root
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
|
@ -22,8 +22,8 @@ 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.x.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.x.matrix.auth.MatrixAuthenticationService
|
import io.element.android.libraries.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
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
||||||
package io.element.android.x.features.login.root
|
package io.element.android.features.login.root
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
|
@ -58,10 +58,12 @@ 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.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import io.element.android.x.core.compose.textFieldState
|
import io.element.android.features.login.error.loginError
|
||||||
import io.element.android.x.features.login.error.loginError
|
import io.element.android.libraries.designsystem.components.form.textFieldState
|
||||||
import io.element.android.x.matrix.core.SessionId
|
import io.element.android.libraries.matrix.core.SessionId
|
||||||
import io.element.android.x.ui.strings.R as StringR
|
import io.element.android.libraries.testtags.TestTags
|
||||||
|
import io.element.android.libraries.testtags.testTag
|
||||||
|
import io.element.android.libraries.ui.strings.R as StringR
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -127,6 +129,7 @@ fun LoginRootScreen(
|
||||||
onClick = onChangeServer,
|
onClick = onChangeServer,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.CenterEnd)
|
.align(Alignment.CenterEnd)
|
||||||
|
.testTag(TestTags.loginChangeServer)
|
||||||
.padding(top = 8.dp, end = 8.dp),
|
.padding(top = 8.dp, end = 8.dp),
|
||||||
content = {
|
content = {
|
||||||
Text(text = "Change")
|
Text(text = "Change")
|
||||||
|
|
@ -137,6 +140,7 @@ fun LoginRootScreen(
|
||||||
value = loginFieldState,
|
value = loginFieldState,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.testTag(TestTags.loginEmailUsername)
|
||||||
.padding(top = 60.dp),
|
.padding(top = 60.dp),
|
||||||
label = {
|
label = {
|
||||||
Text(text = stringResource(id = StringR.string.login_signin_username_hint))
|
Text(text = stringResource(id = StringR.string.login_signin_username_hint))
|
||||||
|
|
@ -159,6 +163,7 @@ fun LoginRootScreen(
|
||||||
value = passwordFieldState,
|
value = passwordFieldState,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.testTag(TestTags.loginPassword)
|
||||||
.padding(top = 24.dp),
|
.padding(top = 24.dp),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
passwordFieldState = it
|
passwordFieldState = it
|
||||||
|
|
@ -202,6 +207,7 @@ fun LoginRootScreen(
|
||||||
enabled = state.submitEnabled,
|
enabled = state.submitEnabled,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.testTag(TestTags.loginContinue)
|
||||||
.padding(vertical = 32.dp)
|
.padding(vertical = 32.dp)
|
||||||
) {
|
) {
|
||||||
Text(text = "Continue")
|
Text(text = "Continue")
|
||||||
|
|
@ -14,10 +14,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.login.root
|
package io.element.android.features.login.root
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import io.element.android.x.matrix.core.SessionId
|
import io.element.android.libraries.matrix.core.SessionId
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
data class LoginRootState(
|
data class LoginRootState(
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.login
|
package io.element.android.features.login
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -23,7 +23,7 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.x.features.logout"
|
namespace = "io.element.android.features.logout"
|
||||||
}
|
}
|
||||||
|
|
||||||
anvil {
|
anvil {
|
||||||
|
|
@ -31,15 +31,14 @@ anvil {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":anvilannotations"))
|
implementation(projects.anvilannotations)
|
||||||
anvil(project(":anvilcodegen"))
|
anvil(projects.anvilcodegen)
|
||||||
implementation(project(":libraries:di"))
|
implementation(projects.libraries.architecture)
|
||||||
implementation(project(":libraries:architecture"))
|
implementation(projects.libraries.core)
|
||||||
implementation(project(":libraries:core"))
|
implementation(projects.libraries.matrix)
|
||||||
implementation(project(":libraries:matrix"))
|
implementation(projects.libraries.designsystem)
|
||||||
implementation(project(":libraries:designsystem"))
|
implementation(projects.libraries.elementresources)
|
||||||
implementation(project(":libraries:elementresources"))
|
implementation(projects.libraries.uiStrings)
|
||||||
implementation(project(":libraries:ui-strings"))
|
|
||||||
ksp(libs.showkase.processor)
|
ksp(libs.showkase.processor)
|
||||||
testImplementation(libs.test.junit)
|
testImplementation(libs.test.junit)
|
||||||
androidTestImplementation(libs.test.junitext)
|
androidTestImplementation(libs.test.junitext)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.login
|
package io.element.android.features.logout
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
|
@ -33,6 +33,6 @@ class ExampleInstrumentedTest {
|
||||||
fun useAppContext() {
|
fun useAppContext() {
|
||||||
// Context of the app under test.
|
// Context of the app under test.
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
assertEquals("io.element.android.x.features.login.test", appContext.packageName)
|
assertEquals("io.element.android.features.login.test", appContext.packageName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.logout
|
package io.element.android.features.logout
|
||||||
|
|
||||||
sealed interface LogoutPreferenceEvents {
|
sealed interface LogoutPreferenceEvents {
|
||||||
object Logout : LogoutPreferenceEvents
|
object Logout : LogoutPreferenceEvents
|
||||||
|
|
@ -14,22 +14,23 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.logout
|
package io.element.android.features.logout
|
||||||
|
|
||||||
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 io.element.android.x.architecture.Async
|
import io.element.android.libraries.architecture.Async
|
||||||
import io.element.android.x.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.x.architecture.execute
|
import io.element.android.libraries.architecture.execute
|
||||||
import io.element.android.x.matrix.MatrixClient
|
import io.element.android.libraries.matrix.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) : Presenter<LogoutPreferenceState> {
|
class LogoutPreferencePresenter @Inject constructor(private val matrixClient: MatrixClient) :
|
||||||
|
Presenter<LogoutPreferenceState> {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): LogoutPreferenceState {
|
override fun present(): LogoutPreferenceState {
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.logout
|
package io.element.android.features.logout
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -24,12 +24,12 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import io.element.android.x.architecture.Async
|
import io.element.android.libraries.architecture.Async
|
||||||
import io.element.android.x.designsystem.components.ProgressDialog
|
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||||
import io.element.android.x.designsystem.components.dialogs.ConfirmationDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||||
import io.element.android.x.designsystem.components.preferences.PreferenceCategory
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
||||||
import io.element.android.x.designsystem.components.preferences.PreferenceText
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
|
||||||
import io.element.android.x.ui.strings.R as StringR
|
import io.element.android.libraries.ui.strings.R as StringR
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LogoutPreferenceView(
|
fun LogoutPreferenceView(
|
||||||
|
|
@ -14,9 +14,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.logout
|
package io.element.android.features.logout
|
||||||
|
|
||||||
import io.element.android.x.architecture.Async
|
import io.element.android.libraries.architecture.Async
|
||||||
|
|
||||||
data class LogoutPreferenceState(
|
data class LogoutPreferenceState(
|
||||||
val logoutAction: Async<Unit> = Async.Uninitialized,
|
val logoutAction: Async<Unit> = Async.Uninitialized,
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.login
|
package io.element.android.features.logout
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -23,7 +23,7 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.x.features.messages"
|
namespace = "io.element.android.features.messages"
|
||||||
}
|
}
|
||||||
|
|
||||||
anvil {
|
anvil {
|
||||||
|
|
@ -31,16 +31,14 @@ anvil {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":anvilannotations"))
|
implementation(projects.anvilannotations)
|
||||||
anvil(project(":anvilcodegen"))
|
anvil(projects.anvilcodegen)
|
||||||
implementation(project(":libraries:di"))
|
implementation(projects.libraries.core)
|
||||||
implementation(project(":libraries:core"))
|
implementation(projects.libraries.architecture)
|
||||||
implementation(project(":libraries:architecture"))
|
implementation(projects.libraries.matrix)
|
||||||
implementation(project(":libraries:matrix"))
|
implementation(projects.libraries.matrixui)
|
||||||
implementation(project(":libraries:matrixui"))
|
implementation(projects.libraries.designsystem)
|
||||||
implementation(project(":libraries:designsystem"))
|
implementation(projects.libraries.textcomposer)
|
||||||
implementation(project(":libraries:textcomposer"))
|
|
||||||
implementation(libs.appyx.core)
|
|
||||||
implementation(libs.coil.compose)
|
implementation(libs.coil.compose)
|
||||||
implementation(libs.datetime)
|
implementation(libs.datetime)
|
||||||
implementation(libs.accompanist.flowlayout)
|
implementation(libs.accompanist.flowlayout)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages
|
package io.element.android.features.messages
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
|
@ -33,6 +33,6 @@ class ExampleInstrumentedTest {
|
||||||
fun useAppContext() {
|
fun useAppContext() {
|
||||||
// Context of the app under test.
|
// Context of the app under test.
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
assertEquals("io.element.android.x.features.messages.test", appContext.packageName)
|
assertEquals("io.element.android.features.messages.test", appContext.packageName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,10 +14,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages
|
package io.element.android.features.messages
|
||||||
|
|
||||||
import io.element.android.x.features.messages.actionlist.model.TimelineItemAction
|
import io.element.android.features.messages.actionlist.model.TimelineItemAction
|
||||||
import io.element.android.x.features.messages.timeline.model.TimelineItem
|
import io.element.android.features.messages.timeline.model.TimelineItem
|
||||||
|
|
||||||
sealed interface MessagesEvents {
|
sealed interface MessagesEvents {
|
||||||
data class HandleAction(val action: TimelineItemAction, val messageEvent: TimelineItem.MessageEvent) : MessagesEvents
|
data class HandleAction(val action: TimelineItemAction, val messageEvent: TimelineItem.MessageEvent) : MessagesEvents
|
||||||
|
|
@ -14,33 +14,28 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages
|
package io.element.android.features.messages
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
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
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.x.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.x.architecture.presenterConnector
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.x.di.RoomScope
|
|
||||||
|
|
||||||
@ContributesNode(RoomScope::class)
|
@ContributesNode(RoomScope::class)
|
||||||
class MessagesNode @AssistedInject constructor(
|
class MessagesNode @AssistedInject constructor(
|
||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
presenter: MessagesPresenter,
|
private val presenter: MessagesPresenter,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
|
||||||
private val connector = presenterConnector(presenter)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state by connector.stateFlow.collectAsState()
|
val state = presenter.present()
|
||||||
MessagesView(
|
MessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
onBackPressed = this::navigateUp,
|
onBackPressed = this::navigateUp,
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages
|
package io.element.android.features.messages
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
|
@ -24,37 +24,32 @@ 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.x.architecture.Presenter
|
import io.element.android.features.messages.actionlist.ActionListPresenter
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
import io.element.android.features.messages.actionlist.model.TimelineItemAction
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarSize
|
import io.element.android.features.messages.textcomposer.MessageComposerEvents
|
||||||
import io.element.android.x.features.messages.actionlist.ActionListPresenter
|
import io.element.android.features.messages.textcomposer.MessageComposerPresenter
|
||||||
import io.element.android.x.features.messages.actionlist.model.TimelineItemAction
|
import io.element.android.features.messages.textcomposer.MessageComposerState
|
||||||
import io.element.android.x.features.messages.textcomposer.MessageComposerEvents
|
import io.element.android.features.messages.timeline.TimelineEvents
|
||||||
import io.element.android.x.features.messages.textcomposer.MessageComposerPresenter
|
import io.element.android.features.messages.timeline.TimelinePresenter
|
||||||
import io.element.android.x.features.messages.textcomposer.MessageComposerState
|
import io.element.android.features.messages.timeline.model.TimelineItem
|
||||||
import io.element.android.x.features.messages.timeline.TimelineEvents
|
import io.element.android.features.messages.timeline.model.content.TimelineItemTextBasedContent
|
||||||
import io.element.android.x.features.messages.timeline.TimelinePresenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.x.features.messages.timeline.model.TimelineItem
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemTextBasedContent
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.x.matrix.MatrixClient
|
import io.element.android.libraries.matrix.room.MatrixRoom
|
||||||
import io.element.android.x.matrix.room.MatrixRoom
|
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||||
import io.element.android.x.matrix.ui.MatrixItemHelper
|
|
||||||
import io.element.android.x.textcomposer.MessageComposerMode
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class MessagesPresenter @Inject constructor(
|
class MessagesPresenter @Inject constructor(
|
||||||
private val matrixClient: MatrixClient,
|
|
||||||
private val room: MatrixRoom,
|
private val room: MatrixRoom,
|
||||||
private val composerPresenter: MessageComposerPresenter,
|
private val composerPresenter: MessageComposerPresenter,
|
||||||
private val timelinePresenter: TimelinePresenter,
|
private val timelinePresenter: TimelinePresenter,
|
||||||
private val actionListPresenter: ActionListPresenter,
|
private val actionListPresenter: ActionListPresenter,
|
||||||
) : Presenter<MessagesState> {
|
) : Presenter<MessagesState> {
|
||||||
|
|
||||||
private val matrixItemHelper = MatrixItemHelper(matrixClient)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): MessagesState {
|
override fun present(): MessagesState {
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
|
|
@ -71,8 +66,9 @@ class MessagesPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
LaunchedEffect(syncUpdateFlow) {
|
LaunchedEffect(syncUpdateFlow) {
|
||||||
roomAvatar.value =
|
roomAvatar.value =
|
||||||
matrixItemHelper.loadAvatarData(
|
AvatarData(
|
||||||
room = room,
|
name = room.bestName,
|
||||||
|
url = room.avatarUrl,
|
||||||
size = AvatarSize.SMALL
|
size = AvatarSize.SMALL
|
||||||
)
|
)
|
||||||
roomName.value = room.name
|
roomName.value = room.name
|
||||||
|
|
@ -14,14 +14,14 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages
|
package io.element.android.features.messages
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
import io.element.android.features.messages.actionlist.ActionListState
|
||||||
import io.element.android.x.features.messages.actionlist.ActionListState
|
import io.element.android.features.messages.textcomposer.MessageComposerState
|
||||||
import io.element.android.x.features.messages.textcomposer.MessageComposerState
|
import io.element.android.features.messages.timeline.TimelineState
|
||||||
import io.element.android.x.features.messages.timeline.TimelineState
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.x.matrix.core.RoomId
|
import io.element.android.libraries.matrix.core.RoomId
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class MessagesState(
|
data class MessagesState(
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class,
|
ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class,
|
||||||
)
|
)
|
||||||
|
|
||||||
package io.element.android.x.features.messages
|
package io.element.android.features.messages
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
|
@ -56,15 +56,15 @@ import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import io.element.android.x.core.compose.LogCompositions
|
import io.element.android.features.messages.actionlist.ActionListEvents
|
||||||
import io.element.android.x.designsystem.components.avatar.Avatar
|
import io.element.android.features.messages.actionlist.ActionListView
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
import io.element.android.features.messages.actionlist.model.TimelineItemAction
|
||||||
import io.element.android.x.features.messages.actionlist.ActionListEvents
|
import io.element.android.features.messages.textcomposer.MessageComposerView
|
||||||
import io.element.android.x.features.messages.actionlist.ActionListView
|
import io.element.android.features.messages.timeline.TimelineView
|
||||||
import io.element.android.x.features.messages.actionlist.model.TimelineItemAction
|
import io.element.android.features.messages.timeline.model.TimelineItem
|
||||||
import io.element.android.x.features.messages.timeline.model.TimelineItem
|
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||||
import io.element.android.x.features.messages.textcomposer.MessageComposerView
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.x.features.messages.timeline.TimelineView
|
import io.element.android.libraries.designsystem.utils.LogCompositions
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
|
@ -14,9 +14,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.actionlist
|
package io.element.android.features.messages.actionlist
|
||||||
|
|
||||||
import io.element.android.x.features.messages.timeline.model.TimelineItem
|
import io.element.android.features.messages.timeline.model.TimelineItem
|
||||||
|
|
||||||
sealed interface ActionListEvents {
|
sealed interface ActionListEvents {
|
||||||
object Clear : ActionListEvents
|
object Clear : ActionListEvents
|
||||||
|
|
@ -14,17 +14,17 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.actionlist
|
package io.element.android.features.messages.actionlist
|
||||||
|
|
||||||
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 io.element.android.x.architecture.Presenter
|
import io.element.android.features.messages.actionlist.model.TimelineItemAction
|
||||||
import io.element.android.x.features.messages.actionlist.model.TimelineItemAction
|
import io.element.android.features.messages.timeline.model.TimelineItem
|
||||||
import io.element.android.x.features.messages.timeline.model.TimelineItem
|
import io.element.android.features.messages.timeline.model.content.TimelineItemRedactedContent
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemRedactedContent
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -14,11 +14,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.actionlist
|
package io.element.android.features.messages.actionlist
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import io.element.android.x.features.messages.actionlist.model.TimelineItemAction
|
import io.element.android.features.messages.actionlist.model.TimelineItemAction
|
||||||
import io.element.android.x.features.messages.timeline.model.TimelineItem
|
import io.element.android.features.messages.timeline.model.TimelineItem
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
@file:OptIn(ExperimentalMaterialApi::class)
|
@file:OptIn(ExperimentalMaterialApi::class)
|
||||||
|
|
||||||
package io.element.android.x.features.messages.actionlist
|
package io.element.android.features.messages.actionlist
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
|
@ -41,9 +41,9 @@ import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.x.designsystem.components.VectorIcon
|
import io.element.android.features.messages.actionlist.model.TimelineItemAction
|
||||||
import io.element.android.x.features.messages.actionlist.model.TimelineItemAction
|
import io.element.android.features.messages.timeline.model.TimelineItem
|
||||||
import io.element.android.x.features.messages.timeline.model.TimelineItem
|
import io.element.android.libraries.designsystem.components.VectorIcon
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
@ -14,11 +14,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.actionlist.model
|
package io.element.android.features.messages.actionlist.model
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import io.element.android.x.designsystem.VectorIcons
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
sealed class TimelineItemAction(
|
sealed class TimelineItemAction(
|
||||||
|
|
@ -26,9 +25,9 @@ sealed class TimelineItemAction(
|
||||||
@DrawableRes val icon: Int,
|
@DrawableRes val icon: Int,
|
||||||
val destructive: Boolean = false
|
val destructive: Boolean = false
|
||||||
) {
|
) {
|
||||||
object Forward : TimelineItemAction("Forward", VectorIcons.ArrowForward)
|
object Forward : TimelineItemAction("Forward", io.element.android.libraries.designsystem.VectorIcons.ArrowForward)
|
||||||
object Copy : TimelineItemAction("Copy", VectorIcons.Copy)
|
object Copy : TimelineItemAction("Copy", io.element.android.libraries.designsystem.VectorIcons.Copy)
|
||||||
object Redact : TimelineItemAction("Redact", VectorIcons.Delete, destructive = true)
|
object Redact : TimelineItemAction("Redact", io.element.android.libraries.designsystem.VectorIcons.Delete, destructive = true)
|
||||||
object Reply : TimelineItemAction("Reply", VectorIcons.Reply)
|
object Reply : TimelineItemAction("Reply", io.element.android.libraries.designsystem.VectorIcons.Reply)
|
||||||
object Edit : TimelineItemAction("Edit", VectorIcons.Edit)
|
object Edit : TimelineItemAction("Edit", io.element.android.libraries.designsystem.VectorIcons.Edit)
|
||||||
}
|
}
|
||||||
|
|
@ -14,9 +14,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.textcomposer
|
package io.element.android.features.messages.textcomposer
|
||||||
|
|
||||||
import io.element.android.x.textcomposer.MessageComposerMode
|
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||||
|
|
||||||
sealed interface MessageComposerEvents {
|
sealed interface MessageComposerEvents {
|
||||||
object ToggleFullScreenState : MessageComposerEvents
|
object ToggleFullScreenState : MessageComposerEvents
|
||||||
|
|
@ -14,18 +14,19 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.textcomposer
|
package io.element.android.features.messages.textcomposer
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
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.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import io.element.android.x.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.x.core.data.StableCharSequence
|
import io.element.android.libraries.core.data.StableCharSequence
|
||||||
import io.element.android.x.core.data.toStableCharSequence
|
import io.element.android.libraries.core.data.toStableCharSequence
|
||||||
import io.element.android.x.matrix.room.MatrixRoom
|
import io.element.android.libraries.matrix.room.MatrixRoom
|
||||||
import io.element.android.x.textcomposer.MessageComposerMode
|
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -40,7 +41,7 @@ class MessageComposerPresenter @Inject constructor(
|
||||||
val isFullScreen = rememberSaveable {
|
val isFullScreen = rememberSaveable {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
val text: MutableState<StableCharSequence> = rememberSaveable {
|
val text: MutableState<StableCharSequence> = remember {
|
||||||
mutableStateOf(StableCharSequence(""))
|
mutableStateOf(StableCharSequence(""))
|
||||||
}
|
}
|
||||||
val composerMode: MutableState<MessageComposerMode> = rememberSaveable {
|
val composerMode: MutableState<MessageComposerMode> = rememberSaveable {
|
||||||
|
|
@ -14,11 +14,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.textcomposer
|
package io.element.android.features.messages.textcomposer
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import io.element.android.x.core.data.StableCharSequence
|
import io.element.android.libraries.core.data.StableCharSequence
|
||||||
import io.element.android.x.textcomposer.MessageComposerMode
|
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class MessageComposerState(
|
data class MessageComposerState(
|
||||||
|
|
@ -14,12 +14,12 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.textcomposer
|
package io.element.android.features.messages.textcomposer
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import io.element.android.x.designsystem.LocalIsDarkTheme
|
import io.element.android.libraries.designsystem.LocalIsDarkTheme
|
||||||
import io.element.android.x.textcomposer.TextComposer
|
import io.element.android.libraries.textcomposer.TextComposer
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MessageComposerView(
|
fun MessageComposerView(
|
||||||
|
|
@ -14,9 +14,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline
|
package io.element.android.features.messages.timeline
|
||||||
|
|
||||||
import io.element.android.x.matrix.core.EventId
|
import io.element.android.libraries.matrix.core.EventId
|
||||||
|
|
||||||
sealed interface TimelineEvents {
|
sealed interface TimelineEvents {
|
||||||
object LoadMore : TimelineEvents
|
object LoadMore : TimelineEvents
|
||||||
|
|
@ -14,30 +14,31 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline
|
package io.element.android.features.messages.timeline
|
||||||
|
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarSize
|
import io.element.android.features.messages.timeline.diff.CacheInvalidator
|
||||||
import io.element.android.x.features.messages.timeline.diff.CacheInvalidator
|
import io.element.android.features.messages.timeline.diff.MatrixTimelineItemsDiffCallback
|
||||||
import io.element.android.x.features.messages.timeline.diff.MatrixTimelineItemsDiffCallback
|
import io.element.android.features.messages.timeline.model.AggregatedReaction
|
||||||
import io.element.android.x.features.messages.timeline.model.AggregatedReaction
|
import io.element.android.features.messages.timeline.model.MessagesItemGroupPosition
|
||||||
import io.element.android.x.features.messages.timeline.model.MessagesItemGroupPosition
|
import io.element.android.features.messages.timeline.model.TimelineItem
|
||||||
import io.element.android.x.features.messages.timeline.model.TimelineItem
|
import io.element.android.features.messages.timeline.model.TimelineItemReactions
|
||||||
import io.element.android.x.features.messages.timeline.model.TimelineItemReactions
|
import io.element.android.features.messages.timeline.model.content.TimelineItemContent
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemContent
|
import io.element.android.features.messages.timeline.model.content.TimelineItemEmoteContent
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemEmoteContent
|
import io.element.android.features.messages.timeline.model.content.TimelineItemEncryptedContent
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemEncryptedContent
|
import io.element.android.features.messages.timeline.model.content.TimelineItemImageContent
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemImageContent
|
import io.element.android.features.messages.timeline.model.content.TimelineItemNoticeContent
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemNoticeContent
|
import io.element.android.features.messages.timeline.model.content.TimelineItemRedactedContent
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemRedactedContent
|
import io.element.android.features.messages.timeline.model.content.TimelineItemTextContent
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemTextContent
|
import io.element.android.features.messages.timeline.model.content.TimelineItemUnknownContent
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemUnknownContent
|
import io.element.android.features.messages.timeline.util.invalidateLast
|
||||||
import io.element.android.x.features.messages.timeline.util.invalidateLast
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.x.matrix.core.EventId
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.x.matrix.media.MediaResolver
|
import io.element.android.libraries.matrix.core.EventId
|
||||||
import io.element.android.x.matrix.room.MatrixRoom
|
import io.element.android.libraries.matrix.media.MediaResolver
|
||||||
import io.element.android.x.matrix.timeline.MatrixTimelineItem
|
import io.element.android.libraries.matrix.room.MatrixRoom
|
||||||
import io.element.android.x.matrix.ui.MatrixItemHelper
|
import io.element.android.libraries.matrix.timeline.MatrixTimelineItem
|
||||||
|
import io.element.android.libraries.matrix.ui.MatrixItemHelper
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
@ -154,12 +155,11 @@ class TimelineItemsFactory @Inject constructor(
|
||||||
computeGroupPosition(currentTimelineItem, timelineItems, index)
|
computeGroupPosition(currentTimelineItem, timelineItems, index)
|
||||||
val senderDisplayName = room.userDisplayName(currentSender).getOrNull()
|
val senderDisplayName = room.userDisplayName(currentSender).getOrNull()
|
||||||
val senderAvatarUrl = room.userAvatarUrl(currentSender).getOrNull()
|
val senderAvatarUrl = room.userAvatarUrl(currentSender).getOrNull()
|
||||||
val senderAvatarData =
|
val senderAvatarData = AvatarData(
|
||||||
matrixItemHelper.loadAvatarData(
|
name = senderDisplayName ?: currentSender,
|
||||||
name = senderDisplayName ?: currentSender,
|
url = senderAvatarUrl,
|
||||||
url = senderAvatarUrl,
|
size = AvatarSize.SMALL
|
||||||
size = AvatarSize.SMALL
|
)
|
||||||
)
|
|
||||||
return TimelineItem.MessageEvent(
|
return TimelineItem.MessageEvent(
|
||||||
id = EventId(currentTimelineItem.uniqueId),
|
id = EventId(currentTimelineItem.uniqueId),
|
||||||
senderId = currentSender,
|
senderId = currentSender,
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline
|
package io.element.android.features.messages.timeline
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
|
@ -24,14 +24,14 @@ import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
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.libraries.architecture.Presenter
|
||||||
import io.element.android.x.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.x.matrix.MatrixClient
|
import io.element.android.libraries.matrix.MatrixClient
|
||||||
import io.element.android.x.matrix.core.EventId
|
import io.element.android.libraries.matrix.core.EventId
|
||||||
import io.element.android.x.matrix.room.MatrixRoom
|
import io.element.android.libraries.matrix.room.MatrixRoom
|
||||||
import io.element.android.x.matrix.timeline.MatrixTimeline
|
import io.element.android.libraries.matrix.timeline.MatrixTimeline
|
||||||
import io.element.android.x.matrix.timeline.MatrixTimelineItem
|
import io.element.android.libraries.matrix.timeline.MatrixTimelineItem
|
||||||
import io.element.android.x.matrix.ui.MatrixItemHelper
|
import io.element.android.libraries.matrix.ui.MatrixItemHelper
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
|
@ -14,11 +14,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline
|
package io.element.android.features.messages.timeline
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import io.element.android.x.features.messages.timeline.model.TimelineItem
|
import io.element.android.features.messages.timeline.model.TimelineItem
|
||||||
import io.element.android.x.matrix.core.EventId
|
import io.element.android.libraries.matrix.core.EventId
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline
|
package io.element.android.features.messages.timeline
|
||||||
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
|
@ -58,29 +58,29 @@ 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 androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
import io.element.android.x.core.compose.PairCombinedPreviewParameter
|
import io.element.android.features.messages.timeline.components.MessageEventBubble
|
||||||
import io.element.android.x.designsystem.components.avatar.Avatar
|
import io.element.android.features.messages.timeline.components.TimelineItemEncryptedView
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
import io.element.android.features.messages.timeline.components.TimelineItemImageView
|
||||||
import io.element.android.x.features.messages.timeline.model.AggregatedReaction
|
import io.element.android.features.messages.timeline.components.TimelineItemReactionsView
|
||||||
import io.element.android.x.features.messages.timeline.model.MessagesItemGroupPosition
|
import io.element.android.features.messages.timeline.components.TimelineItemRedactedView
|
||||||
import io.element.android.x.features.messages.timeline.model.TimelineItemGroupPositionProvider
|
import io.element.android.features.messages.timeline.components.TimelineItemTextView
|
||||||
import io.element.android.x.features.messages.timeline.model.TimelineItemReactions
|
import io.element.android.features.messages.timeline.components.TimelineItemUnknownView
|
||||||
import io.element.android.x.features.messages.timeline.model.TimelineItem
|
import io.element.android.features.messages.timeline.model.AggregatedReaction
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemContent
|
import io.element.android.features.messages.timeline.model.MessagesItemGroupPosition
|
||||||
import io.element.android.x.features.messages.timeline.model.content.MessagesTimelineItemContentProvider
|
import io.element.android.features.messages.timeline.model.TimelineItem
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemEncryptedContent
|
import io.element.android.features.messages.timeline.model.TimelineItemGroupPositionProvider
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemImageContent
|
import io.element.android.features.messages.timeline.model.TimelineItemReactions
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemRedactedContent
|
import io.element.android.features.messages.timeline.model.content.MessagesTimelineItemContentProvider
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemTextBasedContent
|
import io.element.android.features.messages.timeline.model.content.TimelineItemContent
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemUnknownContent
|
import io.element.android.features.messages.timeline.model.content.TimelineItemEncryptedContent
|
||||||
import io.element.android.x.features.messages.timeline.components.MessageEventBubble
|
import io.element.android.features.messages.timeline.model.content.TimelineItemImageContent
|
||||||
import io.element.android.x.features.messages.timeline.components.TimelineItemReactionsView
|
import io.element.android.features.messages.timeline.model.content.TimelineItemRedactedContent
|
||||||
import io.element.android.x.features.messages.timeline.components.TimelineItemEncryptedView
|
import io.element.android.features.messages.timeline.model.content.TimelineItemTextBasedContent
|
||||||
import io.element.android.x.features.messages.timeline.components.TimelineItemImageView
|
import io.element.android.features.messages.timeline.model.content.TimelineItemUnknownContent
|
||||||
import io.element.android.x.features.messages.timeline.components.TimelineItemRedactedView
|
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||||
import io.element.android.x.features.messages.timeline.components.TimelineItemTextView
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.x.features.messages.timeline.components.TimelineItemUnknownView
|
import io.element.android.libraries.designsystem.utils.PairCombinedPreviewParameter
|
||||||
import io.element.android.x.matrix.core.EventId
|
import io.element.android.libraries.matrix.core.EventId
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.components
|
package io.element.android.features.messages.timeline.components
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
|
@ -29,14 +29,14 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.x.designsystem.LocalIsDarkTheme
|
import io.element.android.features.messages.timeline.model.MessagesItemGroupPosition
|
||||||
import io.element.android.x.designsystem.MessageHighlightDark
|
import io.element.android.libraries.designsystem.LocalIsDarkTheme
|
||||||
import io.element.android.x.designsystem.MessageHighlightLight
|
import io.element.android.libraries.designsystem.MessageHighlightDark
|
||||||
import io.element.android.x.designsystem.SystemGrey5Dark
|
import io.element.android.libraries.designsystem.MessageHighlightLight
|
||||||
import io.element.android.x.designsystem.SystemGrey5Light
|
import io.element.android.libraries.designsystem.SystemGrey5Dark
|
||||||
import io.element.android.x.designsystem.SystemGrey6Dark
|
import io.element.android.libraries.designsystem.SystemGrey5Light
|
||||||
import io.element.android.x.designsystem.SystemGrey6Light
|
import io.element.android.libraries.designsystem.SystemGrey6Dark
|
||||||
import io.element.android.x.features.messages.timeline.model.MessagesItemGroupPosition
|
import io.element.android.libraries.designsystem.SystemGrey6Light
|
||||||
|
|
||||||
private val BUBBLE_RADIUS = 16.dp
|
private val BUBBLE_RADIUS = 16.dp
|
||||||
|
|
||||||
|
|
@ -14,13 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.components
|
package io.element.android.features.messages.timeline.components
|
||||||
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Warning
|
import androidx.compose.material.icons.filled.Warning
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemEncryptedContent
|
import io.element.android.features.messages.timeline.model.content.TimelineItemEncryptedContent
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TimelineItemEncryptedView(
|
fun TimelineItemEncryptedView(
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
@file:OptIn(ExperimentalFoundationApi::class)
|
@file:OptIn(ExperimentalFoundationApi::class)
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.components
|
package io.element.android.features.messages.timeline.components
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
|
@ -33,7 +33,7 @@ import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemImageContent
|
import io.element.android.features.messages.timeline.model.content.TimelineItemImageContent
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TimelineItemImageView(
|
fun TimelineItemImageView(
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.components
|
package io.element.android.features.messages.timeline.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.components
|
package io.element.android.features.messages.timeline.components
|
||||||
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
|
@ -32,8 +32,8 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
import io.element.android.x.features.messages.timeline.model.AggregatedReaction
|
import io.element.android.features.messages.timeline.model.AggregatedReaction
|
||||||
import io.element.android.x.features.messages.timeline.model.TimelineItemReactions
|
import io.element.android.features.messages.timeline.model.TimelineItemReactions
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TimelineItemReactionsView(
|
fun TimelineItemReactionsView(
|
||||||
|
|
@ -14,13 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.components
|
package io.element.android.features.messages.timeline.components
|
||||||
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemRedactedContent
|
import io.element.android.features.messages.timeline.model.content.TimelineItemRedactedContent
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TimelineItemRedactedView(
|
fun TimelineItemRedactedView(
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.components
|
package io.element.android.features.messages.timeline.components
|
||||||
|
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.style.URLSpan
|
import android.text.style.URLSpan
|
||||||
|
|
@ -28,10 +28,10 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.core.text.util.LinkifyCompat
|
import androidx.core.text.util.LinkifyCompat
|
||||||
import io.element.android.x.designsystem.LinkColor
|
import io.element.android.features.messages.timeline.components.html.HtmlDocument
|
||||||
import io.element.android.x.designsystem.components.ClickableLinkText
|
import io.element.android.features.messages.timeline.model.content.TimelineItemTextBasedContent
|
||||||
import io.element.android.x.features.messages.timeline.components.html.HtmlDocument
|
import io.element.android.libraries.designsystem.LinkColor
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemTextBasedContent
|
import io.element.android.libraries.designsystem.components.ClickableLinkText
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TimelineItemTextView(
|
fun TimelineItemTextView(
|
||||||
|
|
@ -14,13 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.components
|
package io.element.android.features.messages.timeline.components
|
||||||
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Info
|
import androidx.compose.material.icons.filled.Info
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemUnknownContent
|
import io.element.android.features.messages.timeline.model.content.TimelineItemUnknownContent
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TimelineItemUnknownView(
|
fun TimelineItemUnknownView(
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.components.html
|
package io.element.android.features.messages.timeline.components.html
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
|
@ -47,10 +47,10 @@ import androidx.compose.ui.text.withStyle
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
import io.element.android.x.designsystem.LinkColor
|
import io.element.android.libraries.designsystem.LinkColor
|
||||||
import io.element.android.x.designsystem.components.ClickableLinkText
|
import io.element.android.libraries.designsystem.components.ClickableLinkText
|
||||||
import io.element.android.x.matrix.permalink.PermalinkData
|
import io.element.android.libraries.matrix.permalink.PermalinkData
|
||||||
import io.element.android.x.matrix.permalink.PermalinkParser
|
import io.element.android.libraries.matrix.permalink.PermalinkParser
|
||||||
import kotlinx.collections.immutable.persistentMapOf
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
|
|
@ -14,11 +14,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.diff
|
package io.element.android.features.messages.timeline.diff
|
||||||
|
|
||||||
import androidx.recyclerview.widget.ListUpdateCallback
|
import androidx.recyclerview.widget.ListUpdateCallback
|
||||||
import io.element.android.x.features.messages.timeline.model.TimelineItem
|
import io.element.android.features.messages.timeline.model.TimelineItem
|
||||||
import io.element.android.x.features.messages.timeline.util.invalidateLast
|
import io.element.android.features.messages.timeline.util.invalidateLast
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
internal class CacheInvalidator(private val itemStatesCache: MutableList<TimelineItem?>) :
|
internal class CacheInvalidator(private val itemStatesCache: MutableList<TimelineItem?>) :
|
||||||
|
|
@ -14,10 +14,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.diff
|
package io.element.android.features.messages.timeline.diff
|
||||||
|
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import io.element.android.x.matrix.timeline.MatrixTimelineItem
|
import io.element.android.libraries.matrix.timeline.MatrixTimelineItem
|
||||||
|
|
||||||
internal class MatrixTimelineItemsDiffCallback(
|
internal class MatrixTimelineItemsDiffCallback(
|
||||||
private val oldList: List<MatrixTimelineItem>,
|
private val oldList: List<MatrixTimelineItem>,
|
||||||
|
|
@ -14,12 +14,12 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.model
|
package io.element.android.features.messages.timeline.model
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
import io.element.android.features.messages.timeline.model.content.TimelineItemContent
|
||||||
import io.element.android.x.features.messages.timeline.model.content.TimelineItemContent
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.x.matrix.core.EventId
|
import io.element.android.libraries.matrix.core.EventId
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
sealed interface TimelineItem {
|
sealed interface TimelineItem {
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.model
|
package io.element.android.features.messages.timeline.model
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.model
|
package io.element.android.features.messages.timeline.model
|
||||||
|
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.model.content
|
package io.element.android.features.messages.timeline.model.content
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import org.matrix.rustcomponents.sdk.EncryptedMessage
|
import org.matrix.rustcomponents.sdk.EncryptedMessage
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.model.content
|
package io.element.android.features.messages.timeline.model.content
|
||||||
|
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.model.content
|
package io.element.android.features.messages.timeline.model.content
|
||||||
|
|
||||||
import org.matrix.rustcomponents.sdk.EncryptedMessage
|
import org.matrix.rustcomponents.sdk.EncryptedMessage
|
||||||
|
|
||||||
|
|
@ -14,9 +14,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.model.content
|
package io.element.android.features.messages.timeline.model.content
|
||||||
|
|
||||||
import io.element.android.x.matrix.media.MediaResolver
|
import io.element.android.libraries.matrix.media.MediaResolver
|
||||||
|
|
||||||
data class TimelineItemImageContent(
|
data class TimelineItemImageContent(
|
||||||
val body: String,
|
val body: String,
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.model.content
|
package io.element.android.features.messages.timeline.model.content
|
||||||
|
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
|
|
||||||
|
|
@ -14,6 +14,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.model.content
|
package io.element.android.features.messages.timeline.model.content
|
||||||
|
|
||||||
object TimelineItemRedactedContent : TimelineItemContent
|
object TimelineItemRedactedContent : TimelineItemContent
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.x.features.messages.timeline.model.content
|
package io.element.android.features.messages.timeline.model.content
|
||||||
|
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue