diff --git a/.github/workflows/dependabot.yml b/.github/dependabot.yml
similarity index 100%
rename from .github/workflows/dependabot.yml
rename to .github/dependabot.yml
diff --git a/.gitignore b/.gitignore
index cf226dd935..f6e1ef5b8b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -82,3 +82,4 @@ lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
+/.idea/deploymentTargetDropDown.xml
diff --git a/.idea/copyright/NewVector.xml b/.idea/copyright/NewVector.xml
new file mode 100644
index 0000000000..72a4f2e779
--- /dev/null
+++ b/.idea/copyright/NewVector.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000000..0875fcecb1
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 82cc63ea3b..caa8fff8ef 100644
--- a/README.md
+++ b/README.md
@@ -3,3 +3,25 @@
Proof Of Concept to run a Matrix client on Android devices using the Matrix Rust Sdk and Jetpack compose.
The plan is [here](https://github.com/vector-im/element-x-android-poc/issues/1)!
+
+
+### Modules
+
+This Android project is a multi modules project.
+
+- `app` module is the Android application module. Other modules are libraries;
+- `features` modules contain some UI and can be seen as screen of the application;
+- `libraries` modules contain classes that can be useful for other modules to work.
+
+A few details about some modules:
+
+- `libraries-core` module contains utility classes;
+- `libraries-designsystem` module contains Composables which can be used across the app (theme, etc.);
+- `libraries-elementresources` module contains resource from Element Android (mainly strings);
+- `libraries-matrix` module contains wrappers around the Matrix Rust SDK.
+
+Here is the current module dependency graph:
+
+
+
+
diff --git a/anvilannotations/build.gradle.kts b/anvilannotations/build.gradle.kts
index 80b108d11f..d7974f5643 100644
--- a/anvilannotations/build.gradle.kts
+++ b/anvilannotations/build.gradle.kts
@@ -1,7 +1,25 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
+@Suppress("DSL_SCOPE_VIOLATION")
plugins {
alias(libs.plugins.kotlin.jvm)
}
dependencies {
api(libs.inject)
-}
\ No newline at end of file
+}
diff --git a/anvilannotations/src/main/java/io/element/android/x/anvilannotations/ContributesViewModel.kt b/anvilannotations/src/main/java/io/element/android/x/anvilannotations/ContributesViewModel.kt
index e8fdc272c9..0695e93263 100644
--- a/anvilannotations/src/main/java/io/element/android/x/anvilannotations/ContributesViewModel.kt
+++ b/anvilannotations/src/main/java/io/element/android/x/anvilannotations/ContributesViewModel.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.anvilannotations
import kotlin.reflect.KClass
@@ -14,4 +30,4 @@ import kotlin.reflect.KClass
@Target(AnnotationTarget.CLASS)
annotation class ContributesViewModel(
val scope: KClass<*>,
-)
\ No newline at end of file
+)
diff --git a/anvilcodegen/build.gradle.kts b/anvilcodegen/build.gradle.kts
index d915eb3711..da817a5edb 100644
--- a/anvilcodegen/build.gradle.kts
+++ b/anvilcodegen/build.gradle.kts
@@ -1,3 +1,21 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
+@Suppress("DSL_SCOPE_VIOLATION")
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kapt)
@@ -7,8 +25,8 @@ dependencies {
implementation(project(":anvilannotations"))
api(libs.anvil.compiler.api)
implementation(libs.anvil.compiler.utils)
- implementation("com.squareup:kotlinpoet:1.10.2")
+ implementation("com.squareup:kotlinpoet:1.12.0")
implementation(libs.dagger)
compileOnly("com.google.auto.service:auto-service-annotations:1.0.1")
kapt("com.google.auto.service:auto-service:1.0.1")
-}
\ No newline at end of file
+}
diff --git a/anvilcodegen/src/main/java/io/element/android/x/anvilcodegen/ContributesViewModelCodeGenerator.kt b/anvilcodegen/src/main/java/io/element/android/x/anvilcodegen/ContributesViewModelCodeGenerator.kt
index a72d10ab84..e84d6cdd5f 100644
--- a/anvilcodegen/src/main/java/io/element/android/x/anvilcodegen/ContributesViewModelCodeGenerator.kt
+++ b/anvilcodegen/src/main/java/io/element/android/x/anvilcodegen/ContributesViewModelCodeGenerator.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
@file:OptIn(ExperimentalAnvilApi::class)
package io.element.android.x.anvilcodegen
@@ -31,10 +47,10 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.multibindings.IntoMap
import io.element.android.x.anvilannotations.ContributesViewModel
+import java.io.File
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
-import java.io.File
/**
* This is an anvil plugin that allows ViewModels to use [ContributesViewModel] alone and let this plugin automatically
@@ -69,7 +85,12 @@ class ContributesViewModelCodeGenerator : CodeGenerator {
.returns(assistedViewModelFactoryFqName.asClassName(module).parameterizedBy(STAR, STAR))
.addAnnotation(Binds::class)
.addAnnotation(IntoMap::class)
- .addAnnotation(AnnotationSpec.Companion.builder(viewModelKeyFqName.asClassName(module)).addMember("%T::class", vmClass.asClassName()).build())
+ .addAnnotation(
+ AnnotationSpec.Companion
+ .builder(viewModelKeyFqName.asClassName(module))
+ .addMember("%T::class", vmClass.asClassName())
+ .build()
+ )
.build(),
)
.build(),
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 0c4d93816c..d3ce5a7a78 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,3 +1,21 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
+@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id("io.element.android-compose-application")
alias(libs.plugins.kotlin.android)
@@ -136,12 +154,16 @@ knit {
dependencies {
implementation(project(":libraries:designsystem"))
implementation(project(":libraries:matrix"))
+ implementation(project(":libraries:matrixui"))
implementation(project(":libraries:core"))
implementation(project(":libraries:architecture"))
implementation(project(":features:onboarding"))
implementation(project(":features:login"))
+ implementation(project(":features:logout"))
implementation(project(":features:roomlist"))
implementation(project(":features:messages"))
+ implementation(project(":features:rageshake"))
+ implementation(project(":features:preferences"))
implementation(project(":libraries:di"))
implementation(project(":anvilannotations"))
anvil(project(":anvilcodegen"))
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d32f0dca75..61a03241a1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,4 +1,20 @@
+
+
@@ -32,8 +48,6 @@
android:exported="false"
tools:node="remove" />
-
-
diff --git a/app/src/main/java/io/element/android/x/ElementRootModule.kt b/app/src/main/java/io/element/android/x/ElementRootModule.kt
index ea6e1e7f31..ab59ae72fe 100644
--- a/app/src/main/java/io/element/android/x/ElementRootModule.kt
+++ b/app/src/main/java/io/element/android/x/ElementRootModule.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x
import com.airbnb.android.showkase.annotation.ShowkaseRoot
diff --git a/app/src/main/java/io/element/android/x/ElementXApplication.kt b/app/src/main/java/io/element/android/x/ElementXApplication.kt
index cc97438f31..f13ba5d070 100644
--- a/app/src/main/java/io/element/android/x/ElementXApplication.kt
+++ b/app/src/main/java/io/element/android/x/ElementXApplication.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x
import android.app.Application
@@ -9,8 +25,10 @@ import io.element.android.x.di.AppComponent
import io.element.android.x.di.DaggerAppComponent
import io.element.android.x.di.SessionComponentsOwner
import io.element.android.x.initializer.CoilInitializer
+import io.element.android.x.initializer.CrashInitializer
import io.element.android.x.initializer.MatrixInitializer
import io.element.android.x.initializer.MavericksInitializer
+import io.element.android.x.initializer.TimberInitializer
class ElementXApplication : Application(), DaggerComponentOwner {
@@ -25,6 +43,8 @@ class ElementXApplication : Application(), DaggerComponentOwner {
appComponent = DaggerAppComponent.factory().create(applicationContext)
sessionComponentsOwner = bindings().sessionComponentsOwner()
AppInitializer.getInstance(this).apply {
+ initializeComponent(CrashInitializer::class.java)
+ initializeComponent(TimberInitializer::class.java)
initializeComponent(MatrixInitializer::class.java)
initializeComponent(CoilInitializer::class.java)
initializeComponent(MavericksInitializer::class.java)
diff --git a/app/src/main/java/io/element/android/x/MainActivity.kt b/app/src/main/java/io/element/android/x/MainActivity.kt
index 0baf53fafd..33eac75262 100644
--- a/app/src/main/java/io/element/android/x/MainActivity.kt
+++ b/app/src/main/java/io/element/android/x/MainActivity.kt
@@ -1,13 +1,23 @@
-@file:OptIn(
- ExperimentalAnimationApi::class,
- ExperimentalMaterialNavigationApi::class
-)
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package io.element.android.x
import android.os.Bundle
import androidx.activity.compose.setContent
-import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@@ -15,7 +25,6 @@ import androidx.compose.ui.Modifier
import androidx.core.view.WindowCompat
import com.bumble.appyx.core.integration.NodeHost
import com.bumble.appyx.core.integrationpoint.NodeComponentActivity
-import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
import io.element.android.x.architecture.bindings
import io.element.android.x.core.di.DaggerComponentOwner
import io.element.android.x.designsystem.ElementXTheme
diff --git a/app/src/main/java/io/element/android/x/MainViewModel.kt b/app/src/main/java/io/element/android/x/MainViewModel.kt
deleted file mode 100644
index 0c498f3ab6..0000000000
--- a/app/src/main/java/io/element/android/x/MainViewModel.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-package io.element.android.x
-
-import com.airbnb.mvrx.MavericksState
-import com.airbnb.mvrx.MavericksViewModel
-import com.airbnb.mvrx.MavericksViewModelFactory
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedInject
-import io.element.android.x.anvilannotations.ContributesViewModel
-import io.element.android.x.architecture.viewmodel.daggerMavericksViewModelFactory
-import io.element.android.x.di.AppScope
-import io.element.android.x.di.SessionComponentsOwner
-import io.element.android.x.matrix.Matrix
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
-
-data class MainState(val fake: Boolean = false) : MavericksState
-
-@ContributesViewModel(AppScope::class)
-class MainViewModel @AssistedInject constructor(
- private val matrix: Matrix,
- private val sessionComponentsOwner: SessionComponentsOwner,
- @Assisted initialState: MainState
-) : MavericksViewModel(initialState) {
-
- companion object :
- MavericksViewModelFactory by daggerMavericksViewModelFactory()
-
- suspend fun isLoggedIn(): Boolean {
- return matrix.isLoggedIn().first()
- }
-
- fun startSyncIfLogged() {
- viewModelScope.launch {
- if (!isLoggedIn()) return@launch
- }
- }
-
- fun stopSyncIfLogged() {
- viewModelScope.launch {
- if (!isLoggedIn()) return@launch
- }
- }
-
- suspend fun restoreSession() {
- val matrixClient = matrix.restoreSession()
- if (matrixClient == null) {
- throw IllegalStateException("Couldn't restore session...")
- } else {
- sessionComponentsOwner.create(matrixClient)
- matrixClient.startSync()
- }
- }
-}
diff --git a/app/src/main/java/io/element/android/x/di/AppBindings.kt b/app/src/main/java/io/element/android/x/di/AppBindings.kt
index 73ae3bf846..f330346e80 100644
--- a/app/src/main/java/io/element/android/x/di/AppBindings.kt
+++ b/app/src/main/java/io/element/android/x/di/AppBindings.kt
@@ -1,14 +1,30 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.di
import com.squareup.anvil.annotations.ContributesTo
import io.element.android.x.matrix.Matrix
-import io.element.android.x.node.LoggedInFlowNode
-import io.element.android.x.node.RootFlowNode
+import io.element.android.x.matrix.ui.MatrixUi
import kotlinx.coroutines.CoroutineScope
@ContributesTo(AppScope::class)
interface AppBindings {
fun coroutineScope(): CoroutineScope
fun matrix(): Matrix
+ fun matrixUi(): MatrixUi
fun sessionComponentsOwner(): SessionComponentsOwner
}
diff --git a/app/src/main/java/io/element/android/x/di/AppComponent.kt b/app/src/main/java/io/element/android/x/di/AppComponent.kt
index fb6e924672..3320a88218 100644
--- a/app/src/main/java/io/element/android/x/di/AppComponent.kt
+++ b/app/src/main/java/io/element/android/x/di/AppComponent.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.di
import android.content.Context
diff --git a/app/src/main/java/io/element/android/x/di/AppModule.kt b/app/src/main/java/io/element/android/x/di/AppModule.kt
index 681365aa9f..b8e703c775 100644
--- a/app/src/main/java/io/element/android/x/di/AppModule.kt
+++ b/app/src/main/java/io/element/android/x/di/AppModule.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.di
import com.squareup.anvil.annotations.ContributesTo
@@ -17,4 +33,4 @@ object AppModule {
fun providesAppCoroutineScope(): CoroutineScope {
return MainScope() + CoroutineName("ElementX Scope")
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/io/element/android/x/di/SessionComponent.kt b/app/src/main/java/io/element/android/x/di/SessionComponent.kt
index fd9ffb5078..15fd19a9d8 100644
--- a/app/src/main/java/io/element/android/x/di/SessionComponent.kt
+++ b/app/src/main/java/io/element/android/x/di/SessionComponent.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.di
import com.squareup.anvil.annotations.ContributesTo
diff --git a/app/src/main/java/io/element/android/x/di/SessionComponentsOwner.kt b/app/src/main/java/io/element/android/x/di/SessionComponentsOwner.kt
index f289573821..13b886e0a4 100644
--- a/app/src/main/java/io/element/android/x/di/SessionComponentsOwner.kt
+++ b/app/src/main/java/io/element/android/x/di/SessionComponentsOwner.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.di
import android.content.Context
@@ -42,5 +58,4 @@ class SessionComponentsOwner @Inject constructor(@ApplicationContext private val
activeSessionComponent = null
}
}
-
}
diff --git a/app/src/main/java/io/element/android/x/initializer/CoilInitializer.kt b/app/src/main/java/io/element/android/x/initializer/CoilInitializer.kt
index 9594328881..43d44ba867 100644
--- a/app/src/main/java/io/element/android/x/initializer/CoilInitializer.kt
+++ b/app/src/main/java/io/element/android/x/initializer/CoilInitializer.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.initializer
import android.content.Context
@@ -19,22 +35,19 @@ class CoilInitializer : Initializer {
private class ElementImageLoaderFactory(
private val context: Context
-) :
- ImageLoaderFactory {
+) : ImageLoaderFactory {
override fun newImageLoader(): ImageLoader {
return ImageLoader
.Builder(context)
.components {
val appBindings = context.bindings()
- val matrix = appBindings.matrix()
+ val matrixUi = appBindings.matrixUi()
val matrixClientProvider = {
appBindings
.sessionComponentsOwner().activeSessionComponent?.matrixClient()
}
- matrix.registerCoilComponents(this, matrixClientProvider)
+ matrixUi.registerCoilComponents(this, matrixClientProvider)
}
.build()
}
-
-
}
diff --git a/app/src/main/java/io/element/android/x/initializer/CrashInitializer.kt b/app/src/main/java/io/element/android/x/initializer/CrashInitializer.kt
new file mode 100644
index 0000000000..df3dcacdbb
--- /dev/null
+++ b/app/src/main/java/io/element/android/x/initializer/CrashInitializer.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.initializer
+
+import android.content.Context
+import androidx.startup.Initializer
+import io.element.android.x.features.rageshake.crash.VectorUncaughtExceptionHandler
+
+class CrashInitializer : Initializer {
+
+ override fun create(context: Context) {
+ VectorUncaughtExceptionHandler(context).activate()
+ }
+
+ override fun dependencies(): List>> = emptyList()
+}
diff --git a/app/src/main/java/io/element/android/x/initializer/MatrixInitializer.kt b/app/src/main/java/io/element/android/x/initializer/MatrixInitializer.kt
index 30bf700297..f071893d19 100644
--- a/app/src/main/java/io/element/android/x/initializer/MatrixInitializer.kt
+++ b/app/src/main/java/io/element/android/x/initializer/MatrixInitializer.kt
@@ -1,11 +1,26 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.initializer
import android.content.Context
import androidx.startup.Initializer
-import com.airbnb.mvrx.Mavericks
+import io.element.android.x.BuildConfig
import io.element.android.x.matrix.tracing.TracingConfigurations
import io.element.android.x.matrix.tracing.setupTracing
-import io.element.android.x.sdk.matrix.BuildConfig
class MatrixInitializer : Initializer {
@@ -19,5 +34,4 @@ class MatrixInitializer : Initializer {
override fun dependencies(): List>> = listOf(TimberInitializer::class.java)
-
}
diff --git a/app/src/main/java/io/element/android/x/initializer/MavericksInitializer.kt b/app/src/main/java/io/element/android/x/initializer/MavericksInitializer.kt
index ff51aa1d60..f7d48b66c1 100644
--- a/app/src/main/java/io/element/android/x/initializer/MavericksInitializer.kt
+++ b/app/src/main/java/io/element/android/x/initializer/MavericksInitializer.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.initializer
import android.content.Context
@@ -11,6 +27,4 @@ class MavericksInitializer : Initializer {
}
override fun dependencies(): List>> = listOf()
-
-
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/io/element/android/x/initializer/TimberInitializer.kt b/app/src/main/java/io/element/android/x/initializer/TimberInitializer.kt
index ebb101cc59..10b7e17ffd 100644
--- a/app/src/main/java/io/element/android/x/initializer/TimberInitializer.kt
+++ b/app/src/main/java/io/element/android/x/initializer/TimberInitializer.kt
@@ -1,13 +1,34 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.initializer
import android.content.Context
import androidx.startup.Initializer
+import io.element.android.x.BuildConfig
+import io.element.android.x.features.rageshake.logs.VectorFileLogger
import timber.log.Timber
class TimberInitializer : Initializer {
override fun create(context: Context) {
- Timber.plant(Timber.DebugTree())
+ if (BuildConfig.DEBUG) {
+ Timber.plant(Timber.DebugTree())
+ }
+ Timber.plant(VectorFileLogger(context))
}
override fun dependencies(): List>> = emptyList()
diff --git a/app/src/main/java/io/element/android/x/node/RootFlowNode.kt b/app/src/main/java/io/element/android/x/node/RootFlowNode.kt
index a19e0f5a53..376f88cdb2 100644
--- a/app/src/main/java/io/element/android/x/node/RootFlowNode.kt
+++ b/app/src/main/java/io/element/android/x/node/RootFlowNode.kt
@@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -31,6 +32,9 @@ import io.element.android.x.BuildConfig
import io.element.android.x.component.ShowkaseButton
import io.element.android.x.core.di.DaggerComponentOwner
import io.element.android.x.di.SessionComponentsOwner
+import io.element.android.x.features.rageshake.bugreport.BugReportScreen
+import io.element.android.x.features.rageshake.crash.ui.CrashDetectionScreen
+import io.element.android.x.features.rageshake.detection.RageshakeDetectionScreen
import io.element.android.x.getBrowserIntent
import io.element.android.x.matrix.Matrix
import io.element.android.x.matrix.core.SessionId
@@ -118,6 +122,28 @@ class RootFlowNode(
onCloseClicked = { isShowkaseButtonVisible = false },
onClick = { startActivity(context, Showkase.getBrowserIntent(context), null) }
)
+
+ /*
+ var isBugReportVisible by rememberSaveable {
+ mutableStateOf(false)
+ }
+ RageshakeDetectionScreen(
+ onOpenBugReport = {
+ isBugReportVisible = true
+ }
+ )
+ CrashDetectionScreen(
+ onOpenBugReport = {
+ isBugReportVisible = true
+ }
+ )
+ if (isBugReportVisible) {
+ // TODO Improve the navigation, when pressing back here, it closes the app.
+ BugReportScreen(
+ onDone = { isBugReportVisible = false }
+ )
+ }
+ */
}
}
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index f8c6127d32..bd16b87387 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,4 +1,20 @@
+
+
#FFBB86FC#FF6200EE
@@ -7,4 +23,4 @@
#FF018786#FF000000#FFFFFFFF
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6b30d8d9d4..120af4c3da 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,19 @@
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index bb56da5868..e75bc2cc4d 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,5 +1,21 @@
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
index fa0f996d2c..37fe0011dc 100644
--- a/app/src/main/res/xml/backup_rules.xml
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -1,4 +1,20 @@
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
index 9ee9997b0b..a6ecda4638 100644
--- a/app/src/main/res/xml/data_extraction_rules.xml
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -1,4 +1,20 @@
+
+
-
\ No newline at end of file
+
diff --git a/build.gradle.kts b/build.gradle.kts
index b2946b17cc..5199a030ea 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,4 +1,22 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
// Top-level build file where you can add configuration options common to all sub-projects/modules.
+// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
+@Suppress("DSL_SCOPE_VIOLATION")
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
@@ -10,7 +28,7 @@ plugins {
alias(libs.plugins.kapt) apply false
alias(libs.plugins.detekt)
alias(libs.plugins.ktlint)
-
+ alias(libs.plugins.dependencygraph)
}
@@ -81,6 +99,8 @@ allprojects {
"experimental:kdoc-wrapping",
// Ignore error "Redundant curly braces", since we use it to fix false positives, for instance in "elementLogs.${i}.txt"
"string-template",
+ // Not the same order than Android Studio formatter...
+ "import-ordering",
)
)
}
diff --git a/docs/images/module_graph.png b/docs/images/module_graph.png
new file mode 100644
index 0000000000..3be0256646
Binary files /dev/null and b/docs/images/module_graph.png differ
diff --git a/features/login/build.gradle.kts b/features/login/build.gradle.kts
index b483712e4d..de9192e437 100644
--- a/features/login/build.gradle.kts
+++ b/features/login/build.gradle.kts
@@ -1,3 +1,21 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
+@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id("io.element.android-compose-library")
alias(libs.plugins.ksp)
@@ -13,7 +31,6 @@ anvil {
generateDaggerFactories.set(true)
}
-
dependencies {
implementation(project(":anvilannotations"))
anvil(project(":anvilcodegen"))
diff --git a/features/login/src/androidTest/java/io/element/android/x/features/login/ExampleInstrumentedTest.kt b/features/login/src/androidTest/java/io/element/android/x/features/login/ExampleInstrumentedTest.kt
index ec5ace41e6..88ae6c4133 100644
--- a/features/login/src/androidTest/java/io/element/android/x/features/login/ExampleInstrumentedTest.kt
+++ b/features/login/src/androidTest/java/io/element/android/x/features/login/ExampleInstrumentedTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.login
import androidx.test.ext.junit.runners.AndroidJUnit4
diff --git a/features/login/src/main/AndroidManifest.xml b/features/login/src/main/AndroidManifest.xml
index e100076157..19db0c3d57 100644
--- a/features/login/src/main/AndroidManifest.xml
+++ b/features/login/src/main/AndroidManifest.xml
@@ -1,4 +1,20 @@
+
+
diff --git a/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerView.kt b/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerView.kt
index 6c7b63f06c..3bcf580c28 100644
--- a/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerView.kt
+++ b/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerView.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
@file:OptIn(ExperimentalMaterial3Api::class)
package io.element.android.x.features.login.changeserver
diff --git a/features/login/src/main/java/io/element/android/x/features/login/error/ErrorFormatter.kt b/features/login/src/main/java/io/element/android/x/features/login/error/ErrorFormatter.kt
index 32b801d95a..3afc697572 100644
--- a/features/login/src/main/java/io/element/android/x/features/login/error/ErrorFormatter.kt
+++ b/features/login/src/main/java/io/element/android/x/features/login/error/ErrorFormatter.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.login.error
import androidx.compose.runtime.Composable
diff --git a/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootScreen.kt b/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootScreen.kt
index 4403c2c972..f746965ddb 100644
--- a/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootScreen.kt
+++ b/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootScreen.kt
@@ -1,3 +1,21 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalMaterial3Api::class)
+
package io.element.android.x.features.login.root
import androidx.compose.foundation.layout.Box
diff --git a/features/login/src/main/res/drawable/ic_baseline_dataset_24.xml b/features/login/src/main/res/drawable/ic_baseline_dataset_24.xml
index aa885cbf10..9391d552f4 100644
--- a/features/login/src/main/res/drawable/ic_baseline_dataset_24.xml
+++ b/features/login/src/main/res/drawable/ic_baseline_dataset_24.xml
@@ -1,3 +1,19 @@
+
+
+
+
+
+
+
diff --git a/features/logout/src/main/java/io/element/android/x/features/logout/LogoutScreen.kt b/features/logout/src/main/java/io/element/android/x/features/logout/LogoutScreen.kt
new file mode 100644
index 0000000000..029b2dd1d8
--- /dev/null
+++ b/features/logout/src/main/java/io/element/android/x/features/logout/LogoutScreen.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.logout
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Logout
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.compose.collectAsState
+import com.airbnb.mvrx.compose.mavericksViewModel
+import io.element.android.x.designsystem.ElementXTheme
+import io.element.android.x.designsystem.components.ProgressDialog
+import io.element.android.x.designsystem.components.dialogs.ConfirmationDialog
+import io.element.android.x.designsystem.components.preferences.PreferenceCategory
+import io.element.android.x.designsystem.components.preferences.PreferenceText
+import io.element.android.x.element.resources.R as ElementR
+
+@Composable
+fun LogoutPreference(
+ viewModel: LogoutViewModel = mavericksViewModel(),
+ onSuccessLogout: () -> Unit = { },
+) {
+ val state: LogoutViewState by viewModel.collectAsState()
+ if (state.logoutAction is Success) {
+ onSuccessLogout()
+ return
+ }
+
+ val openDialog = remember { mutableStateOf(false) }
+
+ LogoutPreferenceContent(
+ onClick = {
+ openDialog.value = true
+ }
+ )
+
+ // Log out confirmation dialog
+ if (openDialog.value) {
+ ConfirmationDialog(
+ title = stringResource(id = ElementR.string.action_sign_out),
+ content = stringResource(id = ElementR.string.action_sign_out_confirmation_simple),
+ submitText = stringResource(id = ElementR.string.action_sign_out),
+ onCancelClicked = {
+ openDialog.value = false
+ },
+ onSubmitClicked = {
+ openDialog.value = false
+ viewModel.logout()
+ },
+ onDismiss = {
+ openDialog.value = false
+ }
+ )
+ }
+
+ if (state.logoutAction is Loading) {
+ ProgressDialog(text = "Login out...")
+ }
+}
+
+@Composable
+fun LogoutPreferenceContent(
+ onClick: () -> Unit = {},
+) {
+ PreferenceCategory(title = stringResource(id = ElementR.string.settings_general_title)) {
+ PreferenceText(
+ title = stringResource(id = ElementR.string.action_sign_out),
+ icon = Icons.Default.Logout,
+ onClick = onClick
+ )
+ }
+}
+
+@Composable
+@Preview
+fun LogoutContentPreview() {
+ ElementXTheme(darkTheme = false) {
+ LogoutPreference()
+ }
+}
diff --git a/features/logout/src/main/java/io/element/android/x/features/logout/LogoutViewModel.kt b/features/logout/src/main/java/io/element/android/x/features/logout/LogoutViewModel.kt
new file mode 100644
index 0000000000..32a4ad9bce
--- /dev/null
+++ b/features/logout/src/main/java/io/element/android/x/features/logout/LogoutViewModel.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.logout
+
+import com.airbnb.mvrx.MavericksViewModel
+import com.airbnb.mvrx.MavericksViewModelFactory
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import io.element.android.x.anvilannotations.ContributesViewModel
+import io.element.android.x.architecture.viewmodel.daggerMavericksViewModelFactory
+import io.element.android.x.di.SessionScope
+import io.element.android.x.matrix.MatrixClient
+import kotlinx.coroutines.launch
+
+@ContributesViewModel(SessionScope::class)
+class LogoutViewModel @AssistedInject constructor(
+ private val client: MatrixClient,
+ @Assisted initialState: LogoutViewState
+) : MavericksViewModel(initialState) {
+
+ companion object : MavericksViewModelFactory by daggerMavericksViewModelFactory()
+
+ fun logout() {
+ viewModelScope.launch {
+ suspend {
+ client.logout()
+ }.execute {
+ copy(logoutAction = it)
+ }
+ }
+ }
+}
diff --git a/features/logout/src/main/java/io/element/android/x/features/logout/LogoutViewState.kt b/features/logout/src/main/java/io/element/android/x/features/logout/LogoutViewState.kt
new file mode 100644
index 0000000000..e72442af05
--- /dev/null
+++ b/features/logout/src/main/java/io/element/android/x/features/logout/LogoutViewState.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.logout
+
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.MavericksState
+import com.airbnb.mvrx.Uninitialized
+
+data class LogoutViewState(
+ val logoutAction: Async = Uninitialized,
+) : MavericksState
diff --git a/features/logout/src/test/java/io/element/android/x/features/logout/ExampleUnitTest.kt b/features/logout/src/test/java/io/element/android/x/features/logout/ExampleUnitTest.kt
new file mode 100644
index 0000000000..b2b9726114
--- /dev/null
+++ b/features/logout/src/test/java/io/element/android/x/features/logout/ExampleUnitTest.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.logout
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/features/messages/build.gradle.kts b/features/messages/build.gradle.kts
index 74662c13f3..9fc69683f1 100644
--- a/features/messages/build.gradle.kts
+++ b/features/messages/build.gradle.kts
@@ -1,3 +1,21 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
+@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id("io.element.android-compose-library")
alias(libs.plugins.ksp)
@@ -12,7 +30,6 @@ anvil {
generateDaggerFactories.set(true)
}
-
dependencies {
implementation(project(":anvilannotations"))
anvil(project(":anvilcodegen"))
@@ -20,6 +37,7 @@ dependencies {
implementation(project(":libraries:core"))
implementation(project(":libraries:architecture"))
implementation(project(":libraries:matrix"))
+ implementation(project(":libraries:matrixui"))
implementation(project(":libraries:designsystem"))
implementation(project(":libraries:textcomposer"))
implementation(libs.appyx.core)
diff --git a/features/messages/src/androidTest/java/io/element/android/x/features/messages/ExampleInstrumentedTest.kt b/features/messages/src/androidTest/java/io/element/android/x/features/messages/ExampleInstrumentedTest.kt
index 6c7e4fce97..86305659f1 100644
--- a/features/messages/src/androidTest/java/io/element/android/x/features/messages/ExampleInstrumentedTest.kt
+++ b/features/messages/src/androidTest/java/io/element/android/x/features/messages/ExampleInstrumentedTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages
import androidx.test.ext.junit.runners.AndroidJUnit4
diff --git a/features/messages/src/main/AndroidManifest.xml b/features/messages/src/main/AndroidManifest.xml
index e100076157..19db0c3d57 100644
--- a/features/messages/src/main/AndroidManifest.xml
+++ b/features/messages/src/main/AndroidManifest.xml
@@ -1,4 +1,20 @@
+
+
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/MessageTimelineItemStateFactory.kt b/features/messages/src/main/java/io/element/android/x/features/messages/MessageTimelineItemStateFactory.kt
index 1dfd5712e7..68bf073de7 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/MessageTimelineItemStateFactory.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/MessageTimelineItemStateFactory.kt
@@ -1,7 +1,22 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages
import androidx.recyclerview.widget.DiffUtil
-import io.element.android.x.designsystem.components.avatar.AvatarData
import io.element.android.x.designsystem.components.avatar.AvatarSize
import io.element.android.x.features.messages.diff.CacheInvalidator
import io.element.android.x.features.messages.diff.MatrixTimelineItemsDiffCallback
@@ -18,11 +33,10 @@ import io.element.android.x.features.messages.model.content.MessagesTimelineItem
import io.element.android.x.features.messages.model.content.MessagesTimelineItemTextContent
import io.element.android.x.features.messages.model.content.MessagesTimelineItemUnknownContent
import io.element.android.x.features.messages.util.invalidateLast
-import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.media.MediaResolver
import io.element.android.x.matrix.room.MatrixRoom
import io.element.android.x.matrix.timeline.MatrixTimelineItem
-import kotlin.system.measureTimeMillis
+import io.element.android.x.matrix.ui.MatrixItemHelper
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -36,9 +50,10 @@ import org.matrix.rustcomponents.sdk.FormattedBody
import org.matrix.rustcomponents.sdk.MessageFormat
import org.matrix.rustcomponents.sdk.MessageType
import timber.log.Timber
+import kotlin.system.measureTimeMillis
class MessageTimelineItemStateFactory(
- private val client: MatrixClient,
+ private val matrixItemHelper: MatrixItemHelper,
private val room: MatrixRoom,
private val dispatcher: CoroutineDispatcher,
) {
@@ -137,7 +152,11 @@ class MessageTimelineItemStateFactory(
val senderDisplayName = room.userDisplayName(currentSender).getOrNull()
val senderAvatarUrl = room.userAvatarUrl(currentSender).getOrNull()
val senderAvatarData =
- loadAvatarData(senderDisplayName ?: currentSender, senderAvatarUrl)
+ matrixItemHelper.loadAvatarData(
+ name = senderDisplayName ?: currentSender,
+ url = senderAvatarUrl,
+ size = AvatarSize.SMALL
+ )
return MessagesTimelineItemState.MessageEvent(
id = currentTimelineItem.uniqueId,
senderId = currentSender,
@@ -227,14 +246,4 @@ class MessageTimelineItemStateFactory(
else -> MessagesItemGroupPosition.None
}
}
-
- private suspend fun loadAvatarData(
- name: String,
- url: String?,
- size: AvatarSize = AvatarSize.SMALL
- ): AvatarData {
- val model = client.mediaResolver()
- .resolve(url, kind = MediaResolver.Kind.Thumbnail(size.value))
- return AvatarData(name, model, size)
- }
}
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/MessagesScreen.kt b/features/messages/src/main/java/io/element/android/x/features/messages/MessagesScreen.kt
index a5a44e38f5..7d9b8146ae 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/MessagesScreen.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/MessagesScreen.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
@file:OptIn(
ExperimentalMaterial3Api::class,
ExperimentalMaterialApi::class,
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/MessagesViewModel.kt b/features/messages/src/main/java/io/element/android/x/features/messages/MessagesViewModel.kt
index 35ad154666..c7171b7754 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/MessagesViewModel.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/MessagesViewModel.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages
import com.airbnb.mvrx.MavericksViewModel
@@ -7,7 +23,6 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.x.anvilannotations.ContributesViewModel
import io.element.android.x.architecture.viewmodel.daggerMavericksViewModelFactory
-import io.element.android.x.designsystem.components.avatar.AvatarData
import io.element.android.x.designsystem.components.avatar.AvatarSize
import io.element.android.x.di.SessionScope
import io.element.android.x.features.messages.model.MessagesItemAction
@@ -17,9 +32,9 @@ import io.element.android.x.features.messages.model.MessagesViewState
import io.element.android.x.features.messages.model.content.MessagesTimelineItemRedactedContent
import io.element.android.x.features.messages.model.content.MessagesTimelineItemTextBasedContent
import io.element.android.x.matrix.MatrixClient
-import io.element.android.x.matrix.media.MediaResolver
import io.element.android.x.matrix.timeline.MatrixTimeline
import io.element.android.x.matrix.timeline.MatrixTimelineItem
+import io.element.android.x.matrix.ui.MatrixItemHelper
import io.element.android.x.textcomposer.MessageComposerMode
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
@@ -37,9 +52,10 @@ class MessagesViewModel @AssistedInject constructor(
companion object : MavericksViewModelFactory by daggerMavericksViewModelFactory()
+ private val matrixItemHelper = MatrixItemHelper(client)
private val room = client.getRoom(initialState.roomId)!!
private val messageTimelineItemStateFactory =
- MessageTimelineItemStateFactory(client, room, Dispatchers.Default)
+ MessageTimelineItemStateFactory(matrixItemHelper, room, Dispatchers.Default)
private val timeline = room.timeline()
private val timelineCallback = object : MatrixTimeline.Callback {
@@ -145,7 +161,10 @@ class MessagesViewModel @AssistedInject constructor(
room.syncUpdateFlow()
.onEach {
val avatarData =
- loadAvatarData(room.name ?: room.roomId.value, room.avatarUrl, AvatarSize.SMALL)
+ matrixItemHelper.loadAvatarData(
+ room = room,
+ size = AvatarSize.SMALL
+ )
setState {
copy(
roomName = room.name, roomAvatar = avatarData,
@@ -201,16 +220,6 @@ class MessagesViewModel @AssistedInject constructor(
setSnackbarContent("Not implemented yet!")
}
- private suspend fun loadAvatarData(
- name: String,
- url: String?,
- size: AvatarSize = AvatarSize.MEDIUM
- ): AvatarData {
- val model = client.mediaResolver()
- .resolve(url, kind = MediaResolver.Kind.Thumbnail(size.value))
- return AvatarData(name, model, size)
- }
-
override fun onCleared() {
super.onCleared()
timeline.callback = null
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/components/MessageEventBubble.kt b/features/messages/src/main/java/io/element/android/x/features/messages/components/MessageEventBubble.kt
index 51d0faf677..60f0c147a7 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/components/MessageEventBubble.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/components/MessageEventBubble.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.components
import androidx.compose.foundation.ExperimentalFoundationApi
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesReactionsView.kt b/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesReactionsView.kt
index 0802493a67..34d80f0a6e 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesReactionsView.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesReactionsView.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.components
import androidx.compose.foundation.BorderStroke
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemActionsSheet.kt b/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemActionsSheet.kt
index b34c65916d..f5bdb70471 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemActionsSheet.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemActionsSheet.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
@file:OptIn(ExperimentalMaterialApi::class)
package io.element.android.x.features.messages.components
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemEncryptedView.kt b/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemEncryptedView.kt
index cf89c3418a..b5a2a7753b 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemEncryptedView.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemEncryptedView.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.components
import androidx.compose.material.icons.Icons
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemImageView.kt b/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemImageView.kt
index 35836505db..9b0a270637 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemImageView.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemImageView.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
@file:OptIn(ExperimentalFoundationApi::class)
package io.element.android.x.features.messages.components
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemInformativeView.kt b/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemInformativeView.kt
index 56f5723486..e2c775198f 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemInformativeView.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemInformativeView.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.components
import androidx.compose.foundation.layout.Row
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemRedactedView.kt b/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemRedactedView.kt
index f87ef4c18e..6ad7bb0772 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemRedactedView.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemRedactedView.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.components
import androidx.compose.material.icons.Icons
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemTextView.kt b/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemTextView.kt
index 3bbc334533..94f450e5c5 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemTextView.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemTextView.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.components
import android.text.SpannableString
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemUnknownView.kt b/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemUnknownView.kt
index 9445103c5f..51021ab678 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemUnknownView.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/components/MessagesTimelineItemUnknownView.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.components
import androidx.compose.material.icons.Icons
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/components/html/HtmlDocument.kt b/features/messages/src/main/java/io/element/android/x/features/messages/components/html/HtmlDocument.kt
index 388435c390..56a7684a47 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/components/html/HtmlDocument.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/components/html/HtmlDocument.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.components.html
import androidx.compose.foundation.background
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/diff/CacheInvalidator.kt b/features/messages/src/main/java/io/element/android/x/features/messages/diff/CacheInvalidator.kt
index 542bd98b8d..a904951239 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/diff/CacheInvalidator.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/diff/CacheInvalidator.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.diff
import androidx.recyclerview.widget.ListUpdateCallback
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/diff/MatrixTimelineItemsDiffCallback.kt b/features/messages/src/main/java/io/element/android/x/features/messages/diff/MatrixTimelineItemsDiffCallback.kt
index 6801c14029..e0198d626a 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/diff/MatrixTimelineItemsDiffCallback.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/diff/MatrixTimelineItemsDiffCallback.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.diff
import androidx.recyclerview.widget.DiffUtil
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemAction.kt b/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemAction.kt
index c9609388f3..151f6be67a 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemAction.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemAction.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.model
import androidx.annotation.DrawableRes
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemActionsSheetState.kt b/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemActionsSheetState.kt
index 74aa7dbd56..264fa85085 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemActionsSheetState.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemActionsSheetState.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.model
import androidx.compose.runtime.Stable
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemGroupPosition.kt b/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemGroupPosition.kt
index d4a2d73a19..b5385538a4 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemGroupPosition.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemGroupPosition.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.model
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemReactionState.kt b/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemReactionState.kt
index f68107db48..b7cf57cf0a 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemReactionState.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemReactionState.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.model
import androidx.compose.runtime.Stable
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesTimelineItemState.kt b/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesTimelineItemState.kt
index fd4bf88f18..6dab9e37ae 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesTimelineItemState.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesTimelineItemState.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.model
import io.element.android.x.designsystem.components.avatar.AvatarData
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesViewState.kt b/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesViewState.kt
index aad0ee57f2..4441180ce0 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesViewState.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesViewState.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.model
import androidx.compose.runtime.Stable
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemContent.kt b/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemContent.kt
index 8c758347f8..fdfb2448d4 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemContent.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemContent.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.model.content
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemEmoteContent.kt b/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemEmoteContent.kt
index 318c7bf234..2d13b93471 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemEmoteContent.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemEmoteContent.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.model.content
import org.jsoup.nodes.Document
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemEncryptedContent.kt b/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemEncryptedContent.kt
index 28c73f3a3d..0e23c0ab87 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemEncryptedContent.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemEncryptedContent.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.model.content
import org.matrix.rustcomponents.sdk.EncryptedMessage
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemImageContent.kt b/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemImageContent.kt
index 7bead2b82b..682c9838d2 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemImageContent.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemImageContent.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.model.content
import io.element.android.x.matrix.media.MediaResolver
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemNoticeContent.kt b/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemNoticeContent.kt
index 93b9c21545..8d85ba0c70 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemNoticeContent.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemNoticeContent.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.model.content
import org.jsoup.nodes.Document
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemRedactedContent.kt b/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemRedactedContent.kt
index 6524737475..36f7a29d55 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemRedactedContent.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemRedactedContent.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.model.content
object MessagesTimelineItemRedactedContent : MessagesTimelineItemContent
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemTextBasedContent.kt b/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemTextBasedContent.kt
index d091aaa807..14395a91fa 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemTextBasedContent.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemTextBasedContent.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.model.content
import org.jsoup.nodes.Document
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemTextContent.kt b/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemTextContent.kt
index fab3ea726f..6c4820dc51 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemTextContent.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemTextContent.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.model.content
import org.jsoup.nodes.Document
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemUnknownContent.kt b/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemUnknownContent.kt
index afc494e847..e46710c9fd 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemUnknownContent.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/model/content/MessagesTimelineItemUnknownContent.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.model.content
object MessagesTimelineItemUnknownContent : MessagesTimelineItemContent
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/textcomposer/MessageComposerViewModel.kt b/features/messages/src/main/java/io/element/android/x/features/messages/textcomposer/MessageComposerViewModel.kt
index cf8c2f4f9c..f4c83843dc 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/textcomposer/MessageComposerViewModel.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/textcomposer/MessageComposerViewModel.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.textcomposer
import com.airbnb.mvrx.MavericksViewModel
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/textcomposer/MessageComposerViewState.kt b/features/messages/src/main/java/io/element/android/x/features/messages/textcomposer/MessageComposerViewState.kt
index e0dc8c8312..b85ce0da0c 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/textcomposer/MessageComposerViewState.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/textcomposer/MessageComposerViewState.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.textcomposer
import androidx.compose.runtime.Stable
diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/util/MutableListExt.kt b/features/messages/src/main/java/io/element/android/x/features/messages/util/MutableListExt.kt
index ecdd1c189e..383cb04b95 100644
--- a/features/messages/src/main/java/io/element/android/x/features/messages/util/MutableListExt.kt
+++ b/features/messages/src/main/java/io/element/android/x/features/messages/util/MutableListExt.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages.util
internal inline fun MutableList.invalidateLast() {
diff --git a/features/messages/src/test/java/io/element/android/x/features/messages/ExampleUnitTest.kt b/features/messages/src/test/java/io/element/android/x/features/messages/ExampleUnitTest.kt
index 01e16c5949..87aedb9588 100644
--- a/features/messages/src/test/java/io/element/android/x/features/messages/ExampleUnitTest.kt
+++ b/features/messages/src/test/java/io/element/android/x/features/messages/ExampleUnitTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.messages
import org.junit.Assert.assertEquals
diff --git a/features/onboarding/build.gradle.kts b/features/onboarding/build.gradle.kts
index a686b4cdbd..791b3c47bc 100644
--- a/features/onboarding/build.gradle.kts
+++ b/features/onboarding/build.gradle.kts
@@ -1,3 +1,21 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
+@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id("io.element.android-compose-library")
alias(libs.plugins.ksp)
diff --git a/features/onboarding/src/androidTest/java/io/element/android/x/features/login/ExampleInstrumentedTest.kt b/features/onboarding/src/androidTest/java/io/element/android/x/features/login/ExampleInstrumentedTest.kt
index ec5ace41e6..88ae6c4133 100644
--- a/features/onboarding/src/androidTest/java/io/element/android/x/features/login/ExampleInstrumentedTest.kt
+++ b/features/onboarding/src/androidTest/java/io/element/android/x/features/login/ExampleInstrumentedTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.login
import androidx.test.ext.junit.runners.AndroidJUnit4
diff --git a/features/onboarding/src/main/AndroidManifest.xml b/features/onboarding/src/main/AndroidManifest.xml
index e100076157..19db0c3d57 100644
--- a/features/onboarding/src/main/AndroidManifest.xml
+++ b/features/onboarding/src/main/AndroidManifest.xml
@@ -1,4 +1,20 @@
+
+
diff --git a/features/onboarding/src/main/java/io/element/android/x/features/onboarding/OnBoardingScreen.kt b/features/onboarding/src/main/java/io/element/android/x/features/onboarding/OnBoardingScreen.kt
index a1907929d9..77f798fb4e 100644
--- a/features/onboarding/src/main/java/io/element/android/x/features/onboarding/OnBoardingScreen.kt
+++ b/features/onboarding/src/main/java/io/element/android/x/features/onboarding/OnBoardingScreen.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
@file:OptIn(ExperimentalMaterial3Api::class)
package io.element.android.x.features.onboarding
diff --git a/features/onboarding/src/main/java/io/element/android/x/features/onboarding/OnBoardingViewModel.kt b/features/onboarding/src/main/java/io/element/android/x/features/onboarding/OnBoardingViewModel.kt
new file mode 100644
index 0000000000..a4a170515c
--- /dev/null
+++ b/features/onboarding/src/main/java/io/element/android/x/features/onboarding/OnBoardingViewModel.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.onboarding
+
+import com.airbnb.mvrx.MavericksViewModel
+
+class OnBoardingViewModel(initialState: OnBoardingViewState) :
+ MavericksViewModel(initialState) {
+
+ fun onPageChanged(page: Int) {
+ setState {
+ copy(
+ currentPage = page,
+ )
+ }
+ }
+}
diff --git a/features/onboarding/src/main/java/io/element/android/x/features/onboarding/OnBoardingViewState.kt b/features/onboarding/src/main/java/io/element/android/x/features/onboarding/OnBoardingViewState.kt
new file mode 100644
index 0000000000..e728e8345c
--- /dev/null
+++ b/features/onboarding/src/main/java/io/element/android/x/features/onboarding/OnBoardingViewState.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.onboarding
+
+import com.airbnb.mvrx.MavericksState
+
+data class OnBoardingViewState(
+ val currentPage: Int = 0,
+) : MavericksState
diff --git a/features/onboarding/src/main/java/io/element/android/x/features/onboarding/SplashCarouselState.kt b/features/onboarding/src/main/java/io/element/android/x/features/onboarding/SplashCarouselState.kt
index e21ccf9058..0d491bb236 100644
--- a/features/onboarding/src/main/java/io/element/android/x/features/onboarding/SplashCarouselState.kt
+++ b/features/onboarding/src/main/java/io/element/android/x/features/onboarding/SplashCarouselState.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 New Vector Ltd
+ * Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/features/onboarding/src/main/java/io/element/android/x/features/onboarding/SplashCarouselStateFactory.kt b/features/onboarding/src/main/java/io/element/android/x/features/onboarding/SplashCarouselStateFactory.kt
index ddefc79cc1..d12f83cfbe 100644
--- a/features/onboarding/src/main/java/io/element/android/x/features/onboarding/SplashCarouselStateFactory.kt
+++ b/features/onboarding/src/main/java/io/element/android/x/features/onboarding/SplashCarouselStateFactory.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.onboarding
import androidx.annotation.DrawableRes
diff --git a/features/onboarding/src/main/res/drawable/bg_carousel_page_1.xml b/features/onboarding/src/main/res/drawable/bg_carousel_page_1.xml
index fa3aea4cab..03414760f5 100644
--- a/features/onboarding/src/main/res/drawable/bg_carousel_page_1.xml
+++ b/features/onboarding/src/main/res/drawable/bg_carousel_page_1.xml
@@ -1,7 +1,23 @@
+
+
-
\ No newline at end of file
+
diff --git a/features/onboarding/src/main/res/drawable/bg_carousel_page_2.xml b/features/onboarding/src/main/res/drawable/bg_carousel_page_2.xml
index f696823a6e..216f37c056 100644
--- a/features/onboarding/src/main/res/drawable/bg_carousel_page_2.xml
+++ b/features/onboarding/src/main/res/drawable/bg_carousel_page_2.xml
@@ -1,7 +1,23 @@
+
+
-
\ No newline at end of file
+
diff --git a/features/onboarding/src/main/res/drawable/bg_carousel_page_3.xml b/features/onboarding/src/main/res/drawable/bg_carousel_page_3.xml
index b114f9c804..b206670820 100644
--- a/features/onboarding/src/main/res/drawable/bg_carousel_page_3.xml
+++ b/features/onboarding/src/main/res/drawable/bg_carousel_page_3.xml
@@ -1,7 +1,23 @@
+
+
-
\ No newline at end of file
+
diff --git a/features/onboarding/src/main/res/drawable/bg_carousel_page_4.xml b/features/onboarding/src/main/res/drawable/bg_carousel_page_4.xml
index e8ee364431..8eca5f922f 100644
--- a/features/onboarding/src/main/res/drawable/bg_carousel_page_4.xml
+++ b/features/onboarding/src/main/res/drawable/bg_carousel_page_4.xml
@@ -1,7 +1,23 @@
+
+
-
\ No newline at end of file
+
diff --git a/features/onboarding/src/main/res/drawable/bg_color_background.xml b/features/onboarding/src/main/res/drawable/bg_color_background.xml
index 2542ff2b1d..df950fd479 100644
--- a/features/onboarding/src/main/res/drawable/bg_color_background.xml
+++ b/features/onboarding/src/main/res/drawable/bg_color_background.xml
@@ -1,4 +1,20 @@
+
+
-
\ No newline at end of file
+
diff --git a/features/onboarding/src/main/res/drawable/bg_gradient_ftue_breaker.xml b/features/onboarding/src/main/res/drawable/bg_gradient_ftue_breaker.xml
index cdd4c20a4d..a03a34bd35 100644
--- a/features/onboarding/src/main/res/drawable/bg_gradient_ftue_breaker.xml
+++ b/features/onboarding/src/main/res/drawable/bg_gradient_ftue_breaker.xml
@@ -1,4 +1,20 @@
+
+
diff --git a/features/onboarding/src/main/res/values/strings.xml b/features/onboarding/src/main/res/values/strings.xml
index e996efefaf..43ee27cb35 100644
--- a/features/onboarding/src/main/res/values/strings.xml
+++ b/features/onboarding/src/main/res/values/strings.xml
@@ -1,4 +1,20 @@
+
+
Cut the slack from teams.
-
\ No newline at end of file
+
diff --git a/features/onboarding/src/test/java/io/element/android/x/features/login/ExampleUnitTest.kt b/features/onboarding/src/test/java/io/element/android/x/features/login/ExampleUnitTest.kt
index f1768db5bc..39a03196df 100644
--- a/features/onboarding/src/test/java/io/element/android/x/features/login/ExampleUnitTest.kt
+++ b/features/onboarding/src/test/java/io/element/android/x/features/login/ExampleUnitTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.login
import org.junit.Assert.assertEquals
diff --git a/features/preferences/.gitignore b/features/preferences/.gitignore
new file mode 100644
index 0000000000..42afabfd2a
--- /dev/null
+++ b/features/preferences/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/features/preferences/build.gradle.kts b/features/preferences/build.gradle.kts
new file mode 100644
index 0000000000..4eacb51268
--- /dev/null
+++ b/features/preferences/build.gradle.kts
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
+@Suppress("DSL_SCOPE_VIOLATION")
+plugins {
+ id("io.element.android-compose-library")
+ alias(libs.plugins.ksp)
+ alias(libs.plugins.anvil)
+}
+
+android {
+ namespace = "io.element.android.x.features.preferences"
+}
+
+anvil {
+ generateDaggerFactories.set(true)
+}
+
+dependencies {
+ implementation(project(":anvilannotations"))
+ anvil(project(":anvilcodegen"))
+ implementation(project(":libraries:di"))
+ implementation(project(":libraries:architecture"))
+ implementation(project(":libraries:core"))
+ implementation(project(":libraries:matrixui"))
+ implementation(project(":features:rageshake"))
+ implementation(project(":features:logout"))
+ implementation(project(":libraries:designsystem"))
+ implementation(project(":libraries:elementresources"))
+ implementation(libs.mavericks.compose)
+ implementation(libs.datetime)
+ implementation(libs.accompanist.placeholder)
+ testImplementation(libs.test.junit)
+ androidTestImplementation(libs.test.junitext)
+ ksp(libs.showkase.processor)
+}
diff --git a/features/preferences/consumer-rules.pro b/features/preferences/consumer-rules.pro
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/features/preferences/proguard-rules.pro b/features/preferences/proguard-rules.pro
new file mode 100644
index 0000000000..481bb43481
--- /dev/null
+++ b/features/preferences/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/features/preferences/src/androidTest/java/io/element/android/x/features/preferences/ExampleInstrumentedTest.kt b/features/preferences/src/androidTest/java/io/element/android/x/features/preferences/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000000..6492607f4a
--- /dev/null
+++ b/features/preferences/src/androidTest/java/io/element/android/x/features/preferences/ExampleInstrumentedTest.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.preferences
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("io.element.android.x.features.preferences.test", appContext.packageName)
+ }
+}
diff --git a/features/preferences/src/main/AndroidManifest.xml b/features/preferences/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..19db0c3d57
--- /dev/null
+++ b/features/preferences/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/features/preferences/src/main/java/io/element/android/x/features/preferences/PreferencesScreen.kt b/features/preferences/src/main/java/io/element/android/x/features/preferences/PreferencesScreen.kt
new file mode 100644
index 0000000000..61f38f4a14
--- /dev/null
+++ b/features/preferences/src/main/java/io/element/android/x/features/preferences/PreferencesScreen.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.preferences
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import io.element.android.x.designsystem.components.preferences.PreferenceScreen
+import io.element.android.x.element.resources.R as ElementR
+import io.element.android.x.features.logout.LogoutPreference
+import io.element.android.x.features.preferences.user.UserPreferences
+import io.element.android.x.features.rageshake.preferences.RageshakePreferences
+
+@Composable
+fun PreferencesScreen(
+ onBackPressed: () -> Unit = {},
+ onOpenRageShake: () -> Unit = {},
+ onSuccessLogout: () -> Unit = {},
+) {
+ // TODO Hierarchy!
+ // Include pref from other modules
+ PreferencesContent(
+ onBackPressed = onBackPressed,
+ onOpenRageShake = onOpenRageShake,
+ onSuccessLogout = onSuccessLogout,
+ )
+}
+
+@Composable
+fun PreferencesContent(
+ modifier: Modifier = Modifier,
+ onBackPressed: () -> Unit = {},
+ onOpenRageShake: () -> Unit = {},
+ onSuccessLogout: () -> Unit = {},
+) {
+ PreferenceScreen(
+ modifier = modifier,
+ onBackPressed = onBackPressed,
+ title = stringResource(id = ElementR.string.settings)
+ ) {
+ UserPreferences()
+ RageshakePreferences(onOpenRageShake = onOpenRageShake)
+ LogoutPreference(onSuccessLogout = onSuccessLogout)
+ }
+}
+
+@Preview
+@Composable
+fun PreferencesContentPreview() {
+ PreferencesContent()
+}
diff --git a/features/preferences/src/main/java/io/element/android/x/features/preferences/user/UserPreferences.kt b/features/preferences/src/main/java/io/element/android/x/features/preferences/user/UserPreferences.kt
new file mode 100644
index 0000000000..e3a58c8c46
--- /dev/null
+++ b/features/preferences/src/main/java/io/element/android/x/features/preferences/user/UserPreferences.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.preferences.user
+
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.airbnb.mvrx.compose.collectAsState
+import com.airbnb.mvrx.compose.mavericksViewModel
+import io.element.android.x.matrix.ui.components.MatrixUserHeader
+import io.element.android.x.matrix.ui.viewmodels.user.UserViewModel
+import io.element.android.x.matrix.ui.viewmodels.user.UserViewState
+
+@Composable
+fun UserPreferences(
+ modifier: Modifier = Modifier,
+ viewModel: UserViewModel = mavericksViewModel(),
+) {
+ val user by viewModel.collectAsState(UserViewState::user)
+ when (user()) {
+ null -> Spacer(modifier = modifier.height(1.dp))
+ else -> MatrixUserHeader(
+ modifier = modifier,
+ matrixUser = user.invoke()!!
+ )
+ }
+}
diff --git a/features/preferences/src/test/java/io/element/android/x/features/preferences/ExampleUnitTest.kt b/features/preferences/src/test/java/io/element/android/x/features/preferences/ExampleUnitTest.kt
new file mode 100644
index 0000000000..fda74bd660
--- /dev/null
+++ b/features/preferences/src/test/java/io/element/android/x/features/preferences/ExampleUnitTest.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.preferences
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/features/rageshake/.gitignore b/features/rageshake/.gitignore
new file mode 100644
index 0000000000..42afabfd2a
--- /dev/null
+++ b/features/rageshake/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/features/rageshake/build.gradle.kts b/features/rageshake/build.gradle.kts
new file mode 100644
index 0000000000..653e0e55ef
--- /dev/null
+++ b/features/rageshake/build.gradle.kts
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
+@Suppress("DSL_SCOPE_VIOLATION")
+plugins {
+ id("io.element.android-compose-library")
+ alias(libs.plugins.ksp)
+ alias(libs.plugins.anvil)
+}
+
+android {
+ namespace = "io.element.android.x.features.rageshake"
+}
+
+anvil {
+ generateDaggerFactories.set(true)
+}
+
+dependencies {
+ implementation(project(":libraries:core"))
+ anvil(project(":anvilcodegen"))
+ implementation(project(":libraries:di"))
+ implementation(project(":libraries:architecture"))
+ implementation(project(":anvilannotations"))
+ implementation(project(":libraries:designsystem"))
+ implementation(project(":libraries:elementresources"))
+ implementation(libs.mavericks.compose)
+ implementation(libs.squareup.seismic)
+ implementation(libs.androidx.datastore.preferences)
+ implementation(libs.coil)
+ implementation(libs.coil.compose)
+ ksp(libs.showkase.processor)
+ testImplementation(libs.test.junit)
+ androidTestImplementation(libs.test.junitext)
+}
diff --git a/features/rageshake/consumer-rules.pro b/features/rageshake/consumer-rules.pro
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/features/rageshake/proguard-rules.pro b/features/rageshake/proguard-rules.pro
new file mode 100644
index 0000000000..ff59496d81
--- /dev/null
+++ b/features/rageshake/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.kts.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/features/rageshake/src/main/AndroidManifest.xml b/features/rageshake/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..19db0c3d57
--- /dev/null
+++ b/features/rageshake/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportScreen.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportScreen.kt
new file mode 100644
index 0000000000..cbd9ae8be4
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportScreen.kt
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalMaterial3Api::class)
+
+package io.element.android.x.features.rageshake.bugreport
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.imePadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBarsPadding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Button
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import coil.compose.AsyncImage
+import coil.request.ImageRequest
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.Uninitialized
+import com.airbnb.mvrx.compose.collectAsState
+import com.airbnb.mvrx.compose.mavericksViewModel
+import io.element.android.x.core.compose.LogCompositions
+import io.element.android.x.designsystem.ElementXTheme
+import io.element.android.x.designsystem.components.LabelledCheckbox
+import io.element.android.x.designsystem.components.dialogs.ErrorDialog
+import io.element.android.x.element.resources.R as ElementR
+
+@Composable
+fun BugReportScreen(
+ viewModel: BugReportViewModel = mavericksViewModel(),
+ onDone: () -> Unit = { },
+) {
+ val state: BugReportViewState by viewModel.collectAsState()
+ val formState: BugReportFormState by viewModel.formState
+ LogCompositions(tag = "Rageshake", msg = "Root")
+ if (state.sending is Success) {
+ onDone()
+ }
+ BugReportContent(
+ state = state,
+ formState = formState,
+ onDescriptionChanged = viewModel::onSetDescription,
+ onSetSendLog = viewModel::onSetSendLog,
+ onSetSendCrashLog = viewModel::onSetSendCrashLog,
+ onSetCanContact = viewModel::onSetCanContact,
+ onSetSendScreenshot = viewModel::onSetSendScreenshot,
+ onSubmit = viewModel::onSubmit,
+ onFailureDialogClosed = viewModel::onFailureDialogClosed,
+ onDone = onDone,
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun BugReportContent(
+ state: BugReportViewState,
+ formState: BugReportFormState,
+ modifier: Modifier = Modifier,
+ onDescriptionChanged: (String) -> Unit = {},
+ onSetSendLog: (Boolean) -> Unit = {},
+ onSetSendCrashLog: (Boolean) -> Unit = {},
+ onSetCanContact: (Boolean) -> Unit = {},
+ onSetSendScreenshot: (Boolean) -> Unit = {},
+ onSubmit: () -> Unit = {},
+ onFailureDialogClosed: () -> Unit = { },
+ onDone: () -> Unit = { },
+) {
+ Surface(
+ modifier = modifier,
+ color = MaterialTheme.colorScheme.background,
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .systemBarsPadding()
+ .imePadding()
+ ) {
+ val scrollState = rememberScrollState()
+ Column(
+ modifier = Modifier
+ .verticalScroll(
+ state = scrollState,
+ )
+ .padding(horizontal = 16.dp),
+ ) {
+ val isError = state.sending is Fail
+ val isFormEnabled = state.sending !is Loading
+ // Title
+ Text(
+ text = stringResource(id = ElementR.string.send_bug_report),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp, vertical = 16.dp),
+ textAlign = TextAlign.Center,
+ fontWeight = FontWeight.Bold,
+ fontSize = 24.sp,
+ )
+ // Form
+ Text(
+ text = stringResource(id = ElementR.string.send_bug_report_description),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp, vertical = 16.dp),
+ fontSize = 16.sp,
+ )
+ Column(
+ // modifier = Modifier.weight(1f),
+ ) {
+ OutlinedTextField(
+ value = formState.description,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 16.dp),
+ enabled = isFormEnabled,
+ label = {
+ Text(text = stringResource(id = ElementR.string.send_bug_report_placeholder))
+ },
+ supportingText = {
+ Text(text = stringResource(id = ElementR.string.send_bug_report_description_in_english))
+ },
+ onValueChange = onDescriptionChanged,
+ keyboardOptions = KeyboardOptions(
+ keyboardType = KeyboardType.Text,
+ imeAction = ImeAction.Next
+ ),
+ // TODO Error text too short
+ )
+ }
+ LabelledCheckbox(
+ checked = state.sendLogs,
+ onCheckedChange = onSetSendLog,
+ enabled = isFormEnabled,
+ text = stringResource(id = ElementR.string.send_bug_report_include_logs)
+ )
+ if (state.hasCrashLogs) {
+ LabelledCheckbox(
+ checked = state.sendCrashLogs,
+ onCheckedChange = onSetSendCrashLog,
+ enabled = isFormEnabled,
+ text = stringResource(id = ElementR.string.send_bug_report_include_crash_logs)
+ )
+ }
+ LabelledCheckbox(
+ checked = state.canContact,
+ onCheckedChange = onSetCanContact,
+ enabled = isFormEnabled,
+ text = stringResource(id = ElementR.string.you_may_contact_me)
+ )
+ if (state.screenshotUri != null) {
+ LabelledCheckbox(
+ checked = state.sendScreenshot,
+ onCheckedChange = onSetSendScreenshot,
+ enabled = isFormEnabled,
+ text = stringResource(id = ElementR.string.send_bug_report_include_screenshot)
+ )
+ if (state.sendScreenshot) {
+ Box(
+ modifier = Modifier.fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ val context = LocalContext.current
+ val model = ImageRequest.Builder(context)
+ .data(state.screenshotUri)
+ .build()
+ AsyncImage(
+ modifier = Modifier.fillMaxWidth(fraction = 0.5f),
+ model = model,
+ contentDescription = null
+ )
+ }
+ }
+ }
+ // Submit
+ Button(
+ onClick = onSubmit,
+ enabled = state.submitEnabled,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 32.dp)
+ ) {
+ Text(text = stringResource(id = ElementR.string.action_send))
+ }
+ }
+ when (state.sending) {
+ Uninitialized -> Unit
+ is Loading -> {
+ CircularProgressIndicator(
+ progress = state.sendingProgress,
+ modifier = Modifier.align(Alignment.Center)
+ )
+ }
+ is Fail -> ErrorDialog(
+ content = state.sending.error.toString(),
+ onDismiss = onFailureDialogClosed,
+ )
+ is Success -> onDone()
+ }
+ }
+ }
+}
+
+@Composable
+@Preview
+fun BugReportContentPreview() {
+ ElementXTheme(darkTheme = false) {
+ BugReportContent(
+ state = BugReportViewState(),
+ formState = BugReportFormState.Default
+ )
+ }
+}
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportViewModel.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportViewModel.kt
new file mode 100644
index 0000000000..588dcffaa9
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportViewModel.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.bugreport
+
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.snapshotFlow
+import androidx.core.net.toUri
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.MavericksViewModel
+import com.airbnb.mvrx.MavericksViewModelFactory
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.Uninitialized
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import io.element.android.x.anvilannotations.ContributesViewModel
+import io.element.android.x.architecture.viewmodel.daggerMavericksViewModelFactory
+import io.element.android.x.di.AppScope
+import io.element.android.x.features.rageshake.crash.CrashDataStore
+import io.element.android.x.features.rageshake.logs.VectorFileLogger
+import io.element.android.x.features.rageshake.reporter.BugReporter
+import io.element.android.x.features.rageshake.reporter.ReportType
+import io.element.android.x.features.rageshake.screenshot.ScreenshotHolder
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+@ContributesViewModel(AppScope::class)
+class BugReportViewModel @AssistedInject constructor(
+ @Assisted initialState: BugReportViewState,
+ private val bugReporter: BugReporter,
+ private val crashDataStore: CrashDataStore,
+ private val screenshotHolder: ScreenshotHolder,
+ private val appCoroutineScope: CoroutineScope
+) :
+ MavericksViewModel(initialState) {
+
+ companion object :
+ MavericksViewModelFactory by daggerMavericksViewModelFactory()
+
+ var formState = mutableStateOf(BugReportFormState.Default)
+ private set
+
+ init {
+ snapshotFlow { formState.value }
+ .onEach {
+ setState { copy(formState = it) }
+ }.launchIn(viewModelScope)
+ observerCrashDataStore()
+ setState {
+ copy(
+ screenshotUri = screenshotHolder.getFile()?.toUri()?.toString()
+ )
+ }
+ }
+
+ private fun observerCrashDataStore() {
+ viewModelScope.launch {
+ crashDataStore.crashInfo().collect {
+ setState {
+ copy(
+ hasCrashLogs = it.isNotEmpty()
+ )
+ }
+ }
+ }
+ }
+
+ private val listener: BugReporter.IMXBugReportListener = object : BugReporter.IMXBugReportListener {
+ override fun onUploadCancelled() {
+ setState {
+ copy(
+ sendingProgress = 0F,
+ sending = Uninitialized
+ )
+ }
+ }
+
+ override fun onUploadFailed(reason: String?) {
+ setState {
+ copy(
+ sendingProgress = 0F,
+ sending = Fail(Exception(reason))
+ )
+ }
+ }
+
+ override fun onProgress(progress: Int) {
+ setState {
+ copy(
+ sendingProgress = progress.toFloat() / 100,
+ sending = Loading()
+ )
+ }
+ }
+
+ override fun onUploadSucceed(reportUrl: String?) {
+ setState {
+ copy(
+ sendingProgress = 1F,
+ sending = Success(Unit)
+ )
+ }
+ }
+ }
+
+ override fun onCleared() {
+ // Use appCoroutineScope because we don't want this coroutine to be cancelled
+ appCoroutineScope.launch(Dispatchers.IO) {
+ screenshotHolder.reset()
+ crashDataStore.reset()
+ VectorFileLogger.getFromTimber().reset()
+ }
+ super.onCleared()
+ }
+
+ fun onSubmit() {
+ setState {
+ copy(
+ sendingProgress = 0F,
+ sending = Loading()
+ )
+ }
+ withState { state ->
+ bugReporter.sendBugReport(
+ coroutineScope = viewModelScope,
+ reportType = ReportType.BUG_REPORT,
+ withDevicesLogs = state.sendLogs,
+ withCrashLogs = state.hasCrashLogs && state.sendCrashLogs,
+ withKeyRequestHistory = false,
+ withScreenshot = state.sendScreenshot,
+ theBugDescription = state.formState.description,
+ serverVersion = "",
+ canContact = state.canContact,
+ customFields = emptyMap(),
+ listener = listener
+ )
+ }
+ }
+
+ fun onFailureDialogClosed() {
+ setState {
+ copy(
+ sendingProgress = 0F,
+ sending = Uninitialized
+ )
+ }
+ }
+
+ fun onSetDescription(str: String) {
+ formState.value = formState.value.copy(description = str)
+ setState { copy(sending = Uninitialized) }
+ }
+
+ fun onSetSendLog(value: Boolean) = setState { copy(sendLogs = value) }
+ fun onSetSendCrashLog(value: Boolean) = setState { copy(sendCrashLogs = value) }
+ fun onSetCanContact(value: Boolean) = setState { copy(canContact = value) }
+ fun onSetSendScreenshot(value: Boolean) = setState { copy(sendScreenshot = value) }
+}
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportViewState.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportViewState.kt
new file mode 100644
index 0000000000..813a25dec4
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportViewState.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.bugreport
+
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.MavericksState
+import com.airbnb.mvrx.Uninitialized
+
+data class BugReportViewState(
+ val formState: BugReportFormState = BugReportFormState.Default,
+ val sendLogs: Boolean = true,
+ val hasCrashLogs: Boolean = false,
+ val sendCrashLogs: Boolean = true,
+ val canContact: Boolean = false,
+ val sendScreenshot: Boolean = false,
+ val screenshotUri: String? = null,
+ val sendingProgress: Float = 0F,
+ val sending: Async = Uninitialized,
+) : MavericksState {
+ val submitEnabled =
+ formState.description.length > 10 && sending !is Loading
+}
+
+data class BugReportFormState(
+ val description: String,
+) {
+ companion object {
+ val Default = BugReportFormState("")
+ }
+}
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/CrashDataStore.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/CrashDataStore.kt
new file mode 100644
index 0000000000..07783f3e0b
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/CrashDataStore.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.crash
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.stringPreferencesKey
+import androidx.datastore.preferences.preferencesDataStore
+import io.element.android.x.core.bool.orFalse
+import io.element.android.x.di.ApplicationContext
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.runBlocking
+
+private val Context.dataStore: DataStore by preferencesDataStore(name = "elementx_crash")
+
+private val appHasCrashedKey = booleanPreferencesKey("appHasCrashed")
+private val crashDataKey = stringPreferencesKey("crashData")
+
+class CrashDataStore @Inject constructor(
+ @ApplicationContext context: Context
+) {
+ private val store = context.dataStore
+
+ fun setCrashData(crashData: String) {
+ // Must block
+ runBlocking {
+ store.edit { prefs ->
+ prefs[appHasCrashedKey] = true
+ prefs[crashDataKey] = crashData
+ }
+ }
+ }
+
+ suspend fun resetAppHasCrashed() {
+ store.edit { prefs ->
+ prefs[appHasCrashedKey] = false
+ }
+ }
+
+ fun appHasCrashed(): Flow {
+ return store.data.map { prefs ->
+ prefs[appHasCrashedKey].orFalse()
+ }
+ }
+
+ fun crashInfo(): Flow {
+ return store.data.map { prefs ->
+ prefs[crashDataKey].orEmpty()
+ }
+ }
+
+ suspend fun reset() {
+ store.edit { it.clear() }
+ }
+}
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/VectorUncaughtExceptionHandler.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/VectorUncaughtExceptionHandler.kt
new file mode 100644
index 0000000000..08a23da860
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/VectorUncaughtExceptionHandler.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.crash
+
+import android.content.Context
+import android.os.Build
+import io.element.android.x.core.data.tryOrNull
+import java.io.PrintWriter
+import java.io.StringWriter
+import timber.log.Timber
+
+class VectorUncaughtExceptionHandler(
+ context: Context
+) : Thread.UncaughtExceptionHandler {
+ private val crashDataStore = CrashDataStore(context)
+ private var previousHandler: Thread.UncaughtExceptionHandler? = null
+
+ /**
+ * Activate this handler.
+ */
+ fun activate() {
+ previousHandler = Thread.getDefaultUncaughtExceptionHandler()
+ Thread.setDefaultUncaughtExceptionHandler(this)
+ }
+
+ /**
+ * An uncaught exception has been triggered.
+ *
+ * @param thread the thread
+ * @param throwable the throwable
+ */
+ @Suppress("PrintStackTrace")
+ override fun uncaughtException(thread: Thread, throwable: Throwable) {
+ Timber.v("Uncaught exception: $throwable")
+ val bugDescription = buildString {
+ val appName = "ElementX"
+ // append(appName + " Build : " + versionCodeProvider.getVersionCode() + "\n")
+ append("$appName Version : 1.0") // ${versionProvider.getVersion(longFormat = true)}\n")
+ // append("SDK Version : ${Matrix.getSdkVersion()}\n")
+ append("Phone : " + Build.MODEL.trim() + " (" + Build.VERSION.INCREMENTAL + " " + Build.VERSION.RELEASE + " " + Build.VERSION.CODENAME + ")\n")
+ append("Memory statuses \n")
+ var freeSize = 0L
+ var totalSize = 0L
+ var usedSize = -1L
+ tryOrNull {
+ val info = Runtime.getRuntime()
+ freeSize = info.freeMemory()
+ totalSize = info.totalMemory()
+ usedSize = totalSize - freeSize
+ }
+ append("usedSize " + usedSize / 1048576L + " MB\n")
+ append("freeSize " + freeSize / 1048576L + " MB\n")
+ append("totalSize " + totalSize / 1048576L + " MB\n")
+ append("Thread: ")
+ append(thread.name)
+ append(", Exception: ")
+ val sw = StringWriter()
+ val pw = PrintWriter(sw, true)
+ throwable.printStackTrace(pw)
+ append(sw.buffer.toString())
+ }
+ Timber.e("FATAL EXCEPTION $bugDescription")
+ crashDataStore.setCrashData(bugDescription)
+ // Show the classical system popup
+ previousHandler?.uncaughtException(thread, throwable)
+ }
+}
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionScreen.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionScreen.kt
new file mode 100644
index 0000000000..7c5cc36acb
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionScreen.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.crash.ui
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import com.airbnb.mvrx.compose.collectAsState
+import com.airbnb.mvrx.compose.mavericksViewModel
+import io.element.android.x.core.compose.LogCompositions
+import io.element.android.x.designsystem.ElementXTheme
+import io.element.android.x.designsystem.components.dialogs.ConfirmationDialog
+import io.element.android.x.element.resources.R as ElementR
+
+@Composable
+fun CrashDetectionScreen(
+ viewModel: CrashDetectionViewModel = mavericksViewModel(),
+ onOpenBugReport: () -> Unit = { },
+) {
+ val state: CrashDetectionViewState by viewModel.collectAsState()
+ LogCompositions(tag = "Crash", msg = "CrashDetectionScreen")
+
+ if (state.crashDetected) {
+ CrashDetectionContent(
+ state,
+ onYesClicked = {
+ viewModel.onYes()
+ onOpenBugReport()
+ },
+ onNoClicked = viewModel::onPopupDismissed,
+ onDismiss = viewModel::onPopupDismissed,
+ )
+ }
+}
+
+@Composable
+fun CrashDetectionContent(
+ state: CrashDetectionViewState,
+ onNoClicked: () -> Unit = { },
+ onYesClicked: () -> Unit = { },
+ onDismiss: () -> Unit = { },
+) {
+ ConfirmationDialog(
+ title = stringResource(id = ElementR.string.send_bug_report),
+ content = stringResource(id = ElementR.string.send_bug_report_app_crashed),
+ submitText = stringResource(id = ElementR.string.yes),
+ cancelText = stringResource(id = ElementR.string.no),
+ onCancelClicked = onNoClicked,
+ onSubmitClicked = onYesClicked,
+ onDismiss = onDismiss,
+ )
+}
+
+@Preview
+@Composable
+fun CrashDetectionContentPreview() {
+ ElementXTheme {
+ CrashDetectionContent(
+ state = CrashDetectionViewState()
+ )
+ }
+}
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionViewModel.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionViewModel.kt
new file mode 100644
index 0000000000..cd387bfa1f
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionViewModel.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.crash.ui
+
+import com.airbnb.mvrx.MavericksViewModel
+import com.airbnb.mvrx.MavericksViewModelFactory
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import io.element.android.x.anvilannotations.ContributesViewModel
+import io.element.android.x.architecture.viewmodel.daggerMavericksViewModelFactory
+import io.element.android.x.di.AppScope
+import io.element.android.x.features.rageshake.crash.CrashDataStore
+import kotlinx.coroutines.launch
+
+@ContributesViewModel(AppScope::class)
+class CrashDetectionViewModel @AssistedInject constructor(
+ @Assisted initialState: CrashDetectionViewState,
+ private val crashDataStore: CrashDataStore,
+) : MavericksViewModel(initialState) {
+
+ companion object :
+ MavericksViewModelFactory by daggerMavericksViewModelFactory()
+
+ init {
+ observeDataStore()
+ }
+
+ private fun observeDataStore() {
+ viewModelScope.launch {
+ crashDataStore.appHasCrashed().collect { appHasCrashed ->
+ setState {
+ copy(
+ crashDetected = appHasCrashed
+ )
+ }
+ }
+ }
+ }
+
+ fun onYes() {
+ viewModelScope.launch {
+ crashDataStore.resetAppHasCrashed()
+ }
+ }
+
+ fun onPopupDismissed() {
+ viewModelScope.launch {
+ crashDataStore.reset()
+ }
+ }
+}
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionViewState.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionViewState.kt
new file mode 100644
index 0000000000..e7a50645f9
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionViewState.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.crash.ui
+
+import com.airbnb.mvrx.MavericksState
+
+data class CrashDetectionViewState(
+ val crashDetected: Boolean = false,
+) : MavericksState
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionScreen.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionScreen.kt
new file mode 100644
index 0000000000..576f4e7a92
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionScreen.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.detection
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.lifecycle.Lifecycle
+import com.airbnb.mvrx.compose.collectAsState
+import com.airbnb.mvrx.compose.mavericksViewModel
+import io.element.android.x.core.compose.LogCompositions
+import io.element.android.x.core.compose.OnLifecycleEvent
+import io.element.android.x.core.hardware.vibrate
+import io.element.android.x.core.screenshot.ImageResult
+import io.element.android.x.core.screenshot.screenshot
+import io.element.android.x.designsystem.ElementXTheme
+import io.element.android.x.designsystem.components.dialogs.ConfirmationDialog
+import io.element.android.x.element.resources.R as ElementR
+
+@Composable
+fun RageshakeDetectionScreen(
+ viewModel: RageshakeDetectionViewModel = mavericksViewModel(),
+ onOpenBugReport: () -> Unit = { },
+) {
+ val state: RageshakeDetectionViewState by viewModel.collectAsState()
+ LogCompositions(tag = "Rageshake", msg = "RageshakeDetectionScreen")
+ val context = LocalContext.current
+ OnLifecycleEvent { _, event ->
+ when (event) {
+ Lifecycle.Event.ON_RESUME -> viewModel.start()
+ Lifecycle.Event.ON_PAUSE -> viewModel.stop()
+ else -> Unit
+ }
+ }
+
+ when {
+ state.takeScreenshot -> TakeScreenshot(
+ onScreenshotTaken = viewModel::onScreenshotTaken
+ )
+ state.showDialog -> {
+ LaunchedEffect(key1 = "RS_diag") {
+ context.vibrate()
+ }
+ RageshakeDialogContent(
+ state,
+ onNoClicked = viewModel::onNo,
+ onDisableClicked = {
+ viewModel.onEnableClicked(false)
+ },
+ onYesClicked = {
+ onOpenBugReport()
+ viewModel.onYes()
+ }
+ )
+ }
+ }
+}
+
+@Composable
+private fun TakeScreenshot(
+ onScreenshotTaken: (ImageResult) -> Unit = {}
+) {
+ val view = LocalView.current
+ view.screenshot {
+ onScreenshotTaken(it)
+ }
+}
+
+@Composable
+fun RageshakeDialogContent(
+ state: RageshakeDetectionViewState,
+ onNoClicked: () -> Unit = { },
+ onDisableClicked: () -> Unit = { },
+ onYesClicked: () -> Unit = { },
+) {
+ ConfirmationDialog(
+ title = stringResource(id = ElementR.string.send_bug_report),
+ content = stringResource(id = ElementR.string.send_bug_report_alert_message),
+ thirdButtonText = stringResource(id = ElementR.string.action_disable),
+ submitText = stringResource(id = ElementR.string.yes),
+ cancelText = stringResource(id = ElementR.string.no),
+ onThirdButtonClicked = onDisableClicked,
+ onSubmitClicked = onYesClicked,
+ onDismiss = onNoClicked,
+ )
+}
+
+@Preview
+@Composable
+fun RageshakeDialogContentPreview() {
+ ElementXTheme {
+ RageshakeDialogContent(
+ state = RageshakeDetectionViewState()
+ )
+ }
+}
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionViewModel.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionViewModel.kt
new file mode 100644
index 0000000000..e3ccde755d
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionViewModel.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.detection
+
+import com.airbnb.mvrx.MavericksViewModel
+import com.airbnb.mvrx.MavericksViewModelFactory
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import io.element.android.x.anvilannotations.ContributesViewModel
+import io.element.android.x.architecture.viewmodel.daggerMavericksViewModelFactory
+import io.element.android.x.core.screenshot.ImageResult
+import io.element.android.x.di.AppScope
+import io.element.android.x.features.rageshake.rageshake.RageShake
+import io.element.android.x.features.rageshake.rageshake.RageshakeDataStore
+import io.element.android.x.features.rageshake.screenshot.ScreenshotHolder
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import timber.log.Timber
+
+@ContributesViewModel(AppScope::class)
+class RageshakeDetectionViewModel @AssistedInject constructor(
+ @Assisted initialState: RageshakeDetectionViewState,
+ private val rageshakeDataStore: RageshakeDataStore,
+ private val screenshotHolder: ScreenshotHolder,
+ private val rageShake: RageShake,
+) : MavericksViewModel(initialState) {
+
+ companion object :
+ MavericksViewModelFactory by daggerMavericksViewModelFactory()
+
+ init {
+ setState {
+ copy(
+ isSupported = rageShake.isAvailable()
+ )
+ }
+ observeDataStore()
+ observeState()
+ }
+
+ private fun observeDataStore() {
+ viewModelScope.launch {
+ rageshakeDataStore.isEnabled().collect { isEnabled ->
+ setState {
+ copy(
+ isEnabled = isEnabled
+ )
+ }
+ }
+ }
+ viewModelScope.launch {
+ rageshakeDataStore.sensitivity().collect { sensitivity ->
+ setState {
+ copy(
+ sensitivity = sensitivity
+ )
+ }
+ }
+ }
+ }
+
+ private fun observeState() {
+ viewModelScope.launch {
+ stateFlow
+ .map {
+ it.isSupported &&
+ it.isEnabled &&
+ it.isStarted &&
+ !it.takeScreenshot &&
+ !it.showDialog
+ }
+ .distinctUntilChanged()
+ .collect(::handleRageShake)
+ }
+ viewModelScope.launch {
+ stateFlow
+ .map {
+ it.sensitivity
+ }
+ .distinctUntilChanged()
+ .collect {
+ rageShake.setSensitivity(it)
+ }
+ }
+ }
+
+ private fun handleRageShake(shouldStart: Boolean) {
+ if (shouldStart) {
+ withState {
+ rageShake.start(it.sensitivity)
+ }
+ rageShake.interceptor = {
+ setState {
+ copy(
+ takeScreenshot = true
+ )
+ }
+ }
+ } else {
+ rageShake.stop()
+ rageShake.interceptor = null
+ }
+ }
+
+ fun onScreenshotTaken(imageResult: ImageResult) {
+ viewModelScope.launch(Dispatchers.IO) {
+ screenshotHolder.reset()
+ when (imageResult) {
+ is ImageResult.Error -> {
+ Timber.e(imageResult.exception, "Unable to write screenshot")
+ }
+ is ImageResult.Success -> {
+ screenshotHolder.writeBitmap(imageResult.data)
+ }
+ }
+ setState {
+ copy(
+ takeScreenshot = false,
+ showDialog = true,
+ )
+ }
+ }
+ }
+
+ fun start() {
+ setState {
+ copy(isStarted = true)
+ }
+ }
+
+ private fun onPopupDismissed() {
+ setState {
+ copy(
+ showDialog = false
+ )
+ }
+ }
+
+ fun onNo() {
+ onPopupDismissed()
+ }
+
+ fun onYes() {
+ onPopupDismissed()
+ }
+
+ fun onEnableClicked(enabled: Boolean) {
+ viewModelScope.launch {
+ rageshakeDataStore.setIsEnabled(enabled)
+ }
+ if (!enabled) {
+ onPopupDismissed()
+ }
+ }
+
+ fun onSensitivityChange(sensitivity: Float) {
+ viewModelScope.launch {
+ rageshakeDataStore.setSensitivity(sensitivity)
+ }
+ rageShake.setSensitivity(sensitivity)
+ }
+
+ fun stop() {
+ setState {
+ copy(isStarted = false)
+ }
+ }
+
+ override fun onCleared() {
+ super.onCleared()
+ stop()
+ handleRageShake(false)
+ }
+}
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionViewState.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionViewState.kt
new file mode 100644
index 0000000000..4e426a079e
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionViewState.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.detection
+
+import com.airbnb.mvrx.MavericksState
+
+data class RageshakeDetectionViewState(
+ val takeScreenshot: Boolean = false,
+ val showDialog: Boolean = false,
+ val isEnabled: Boolean = true,
+ val isStarted: Boolean = false,
+ val isSupported: Boolean = false,
+ val sensitivity: Float = 0.5f,
+) : MavericksState
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/logs/LogFormatter.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/logs/LogFormatter.kt
new file mode 100644
index 0000000000..007c87f3e1
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/logs/LogFormatter.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.logs
+
+import java.io.PrintWriter
+import java.io.StringWriter
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+import java.util.TimeZone
+import java.util.logging.Formatter
+import java.util.logging.LogRecord
+
+internal class LogFormatter : Formatter() {
+
+ override fun format(r: LogRecord): String {
+ if (!mIsTimeZoneSet) {
+ DATE_FORMAT.timeZone = TimeZone.getTimeZone("UTC")
+ mIsTimeZoneSet = true
+ }
+
+ val thrown = r.thrown
+ if (thrown != null) {
+ val sw = StringWriter()
+ val pw = PrintWriter(sw)
+ sw.write(r.message)
+ sw.write(LINE_SEPARATOR)
+ thrown.printStackTrace(pw)
+ pw.flush()
+ return sw.toString()
+ } else {
+ val b = StringBuilder()
+ val date = DATE_FORMAT.format(Date(r.millis))
+ b.append(date)
+ b.append("Z ")
+ b.append(r.message)
+ b.append(LINE_SEPARATOR)
+ return b.toString()
+ }
+ }
+
+ companion object {
+ private val LINE_SEPARATOR = System.getProperty("line.separator") ?: "\n"
+
+ // private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
+ private val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss*SSSZZZZ", Locale.US)
+
+ private var mIsTimeZoneSet = false
+ }
+}
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/logs/VectorFileLogger.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/logs/VectorFileLogger.kt
new file mode 100644
index 0000000000..5b39b79cf5
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/logs/VectorFileLogger.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.logs
+
+import android.content.Context
+import android.util.Log
+import io.element.android.x.core.data.tryOrNull
+import java.io.File
+import java.io.PrintWriter
+import java.io.StringWriter
+import java.util.logging.FileHandler
+import java.util.logging.Level
+import java.util.logging.Logger
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import timber.log.Timber
+
+/**
+ * Will be planted in Timber.
+ */
+class VectorFileLogger(
+ context: Context,
+ // private val vectorPreferences: VectorPreferences
+) : Timber.Tree() {
+
+ companion object {
+ fun getFromTimber(): VectorFileLogger {
+ return Timber.forest().filterIsInstance().first()
+ }
+
+ private const val SIZE_20MB = 20 * 1024 * 1024
+ private const val SIZE_50MB = 50 * 1024 * 1024
+ }
+
+ /*
+ private val maxLogSizeByte = if (vectorPreferences.labAllowedExtendedLogging()) SIZE_50MB else SIZE_20MB
+ private val logRotationCount = if (vectorPreferences.labAllowedExtendedLogging()) 15 else 7
+ */
+ private val maxLogSizeByte = SIZE_20MB
+ private val logRotationCount = 7
+
+ private val logger = Logger.getLogger(context.packageName).apply {
+ tryOrNull {
+ useParentHandlers = false
+ level = Level.ALL
+ }
+ }
+
+ private val fileHandler: FileHandler?
+ private val cacheDirectory = File(context.cacheDir, "logs")
+ private var fileNamePrefix = "logs"
+
+ private val prioPrefixes = mapOf(
+ Log.VERBOSE to "V/ ",
+ Log.DEBUG to "D/ ",
+ Log.INFO to "I/ ",
+ Log.WARN to "W/ ",
+ Log.ERROR to "E/ ",
+ Log.ASSERT to "WTF/ "
+ )
+
+ init {
+ if (!cacheDirectory.exists()) {
+ cacheDirectory.mkdirs()
+ }
+
+ for (i in 0..15) {
+ val file = File(cacheDirectory, "elementLogs.${i}.txt")
+ tryOrNull { file.delete() }
+ }
+
+ fileHandler = tryOrNull("Failed to initialize FileLogger") {
+ FileHandler(
+ cacheDirectory.absolutePath + "/" + fileNamePrefix + ".%g.txt",
+ maxLogSizeByte,
+ logRotationCount
+ )
+ .also { it.formatter = LogFormatter() }
+ .also { logger.addHandler(it) }
+ }
+ }
+
+ fun reset() {
+ // Delete all files
+ getLogFiles().map {
+ tryOrNull { it.delete() }
+ }
+ }
+
+ @OptIn(DelicateCoroutinesApi::class)
+ override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
+ fileHandler ?: return
+ GlobalScope.launch(Dispatchers.IO) {
+ if (skipLog(priority)) return@launch
+ if (t != null) {
+ logToFile(t)
+ }
+ logToFile(prioPrefixes[priority] ?: "$priority ", tag ?: "Tag", message)
+ }
+ }
+
+ private fun skipLog(priority: Int): Boolean {
+ /*
+ return if (vectorPreferences.labAllowedExtendedLogging()) {
+ false
+ } else {
+ // Exclude verbose logs
+ priority < Log.DEBUG
+ }
+ */
+ // Exclude verbose logs
+ return priority < Log.DEBUG
+ }
+
+ /**
+ * Adds our own log files to the provided list of files.
+ *
+ * @return The list of files with logs.
+ */
+ fun getLogFiles(): List {
+ return tryOrNull("## getLogFiles() failed") {
+ fileHandler
+ ?.flush()
+ ?.let { 0 until logRotationCount }
+ ?.mapNotNull { index ->
+ File(cacheDirectory, "$fileNamePrefix.${index}.txt")
+ .takeIf { it.exists() }
+ }
+ }
+ .orEmpty()
+ }
+
+ /**
+ * Log an Throwable.
+ *
+ * @param throwable the throwable to log
+ */
+ private fun logToFile(throwable: Throwable?) {
+ throwable ?: return
+
+ val errors = StringWriter()
+ throwable.printStackTrace(PrintWriter(errors))
+
+ logger.info(errors.toString())
+ }
+
+ private fun logToFile(level: String, tag: String, content: String) {
+ val b = StringBuilder()
+ b.append(Thread.currentThread().id)
+ b.append(" ")
+ b.append(level)
+ b.append("/")
+ b.append(tag)
+ b.append(": ")
+ b.append(content)
+ logger.info(b.toString())
+ }
+}
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferenceCategory.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferenceCategory.kt
new file mode 100644
index 0000000000..050689ad44
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferenceCategory.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.preferences
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.BugReport
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import com.airbnb.mvrx.compose.collectAsState
+import com.airbnb.mvrx.compose.mavericksViewModel
+import io.element.android.x.designsystem.components.preferences.PreferenceCategory
+import io.element.android.x.designsystem.components.preferences.PreferenceSlide
+import io.element.android.x.designsystem.components.preferences.PreferenceSwitch
+import io.element.android.x.designsystem.components.preferences.PreferenceText
+import io.element.android.x.element.resources.R as ElementR
+import io.element.android.x.features.rageshake.detection.RageshakeDetectionViewModel
+import io.element.android.x.features.rageshake.detection.RageshakeDetectionViewState
+
+@Composable
+fun RageshakePreferences(
+ onOpenRageShake: () -> Unit = {},
+) {
+ RageshakePreferencesContent(
+ onOpenRageShake = onOpenRageShake,
+ )
+}
+
+@Composable
+fun RageshakePreferencesContent(
+ modifier: Modifier = Modifier,
+ viewModel: RageshakeDetectionViewModel = mavericksViewModel(),
+ onOpenRageShake: () -> Unit = {},
+) {
+ val state: RageshakeDetectionViewState by viewModel.collectAsState()
+ Column(modifier = modifier) {
+ PreferenceCategory(title = stringResource(id = ElementR.string.send_bug_report)) {
+ PreferenceText(
+ title = stringResource(id = ElementR.string.send_bug_report),
+ icon = Icons.Default.BugReport,
+ onClick = onOpenRageShake
+ )
+ }
+ PreferenceCategory(title = stringResource(id = ElementR.string.settings_rageshake)) {
+ if (state.isSupported) {
+ PreferenceSwitch(
+ title = stringResource(id = ElementR.string.send_bug_report_rage_shake),
+ isChecked = state.isEnabled,
+ onCheckedChange = viewModel::onEnableClicked
+ )
+ PreferenceSlide(
+ title = stringResource(id = ElementR.string.settings_rageshake_detection_threshold),
+ // summary = stringResource(id = ElementR.string.settings_rageshake_detection_threshold_summary),
+ value = state.sensitivity,
+ enabled = state.isEnabled,
+ steps = 3 /* 5 possible values - steps are in ]0, 1[ */,
+ onValueChange = viewModel::onSensitivityChange
+ )
+ } else {
+ PreferenceText(title = "Rageshaking is not supported by your device")
+ }
+ }
+ }
+}
+
+@Composable
+@Preview
+fun RageshakePreferencePreview() {
+ RageshakePreferences()
+}
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/rageshake/RageShake.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/rageshake/RageShake.kt
new file mode 100644
index 0000000000..ae9c3531c5
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/rageshake/RageShake.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.rageshake
+
+import android.content.Context
+import android.hardware.Sensor
+import android.hardware.SensorManager
+import androidx.core.content.getSystemService
+import com.squareup.seismic.ShakeDetector
+import io.element.android.x.di.AppScope
+import io.element.android.x.di.ApplicationContext
+import io.element.android.x.di.SingleIn
+import javax.inject.Inject
+
+@SingleIn(AppScope::class)
+class RageShake @Inject constructor(
+ @ApplicationContext context: Context,
+) : ShakeDetector.Listener {
+
+ private var sensorManager = context.getSystemService()
+ private var shakeDetector: ShakeDetector? = null
+
+ var interceptor: (() -> Unit)? = null
+
+ /**
+ * Check if the feature is available on this device.
+ */
+ fun isAvailable(): Boolean {
+ return sensorManager?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null
+ }
+
+ fun start(sensitivity: Float) {
+ sensorManager?.let {
+ shakeDetector = ShakeDetector(this).apply {
+ start(it, SensorManager.SENSOR_DELAY_GAME)
+ }
+ setSensitivity(sensitivity)
+ }
+ }
+
+ fun stop() {
+ shakeDetector?.stop()
+ }
+
+ /**
+ * sensitivity will be {0, O.25, 0.5, 0.75, 1} and converted to
+ * [ShakeDetector.SENSITIVITY_LIGHT (=11), ShakeDetector.SENSITIVITY_HARD (=15)].
+ */
+ fun setSensitivity(sensitivity: Float) {
+ shakeDetector?.setSensitivity(
+ ShakeDetector.SENSITIVITY_LIGHT + (sensitivity * 4).toInt()
+ )
+ }
+
+ override fun hearShake() {
+ interceptor?.invoke()
+ }
+}
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/rageshake/RageshakeDataStore.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/rageshake/RageshakeDataStore.kt
new file mode 100644
index 0000000000..35d0198b9e
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/rageshake/RageshakeDataStore.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.rageshake
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.floatPreferencesKey
+import androidx.datastore.preferences.preferencesDataStore
+import io.element.android.x.core.bool.orTrue
+import io.element.android.x.di.ApplicationContext
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+private val Context.dataStore: DataStore by preferencesDataStore(name = "elementx_rageshake")
+
+private val enabledKey = booleanPreferencesKey("enabled")
+private val sensitivityKey = floatPreferencesKey("sensitivity")
+
+class RageshakeDataStore @Inject constructor(
+ @ApplicationContext context: Context
+) {
+ private val store = context.dataStore
+
+ fun isEnabled(): Flow {
+ return store.data.map { prefs ->
+ prefs[enabledKey].orTrue()
+ }
+ }
+
+ suspend fun setIsEnabled(isEnabled: Boolean) {
+ store.edit { prefs ->
+ prefs[enabledKey] = isEnabled
+ }
+ }
+
+ fun sensitivity(): Flow {
+ return store.data.map { prefs ->
+ prefs[sensitivityKey] ?: 0.5f
+ }
+ }
+
+ suspend fun setSensitivity(sensitivity: Float) {
+ store.edit { prefs ->
+ prefs[sensitivityKey] = sensitivity
+ }
+ }
+
+ suspend fun reset() {
+ store.edit { it.clear() }
+ }
+}
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/reporter/BugReporter.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/reporter/BugReporter.kt
new file mode 100755
index 0000000000..fe3ca71720
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/reporter/BugReporter.kt
@@ -0,0 +1,550 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.reporter
+
+import android.content.Context
+import android.os.Build
+import io.element.android.x.core.extensions.toOnOff
+import io.element.android.x.core.file.compressFile
+import io.element.android.x.core.mimetype.MimeTypes
+import io.element.android.x.di.ApplicationContext
+import io.element.android.x.features.rageshake.R
+import io.element.android.x.features.rageshake.crash.CrashDataStore
+import io.element.android.x.features.rageshake.logs.VectorFileLogger
+import io.element.android.x.features.rageshake.screenshot.ScreenshotHolder
+import java.io.File
+import java.io.IOException
+import java.io.OutputStreamWriter
+import java.net.HttpURLConnection
+import java.util.Locale
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import okhttp3.Call
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.RequestBody.Companion.asRequestBody
+import okhttp3.Response
+import org.json.JSONException
+import org.json.JSONObject
+import timber.log.Timber
+
+/**
+ * BugReporter creates and sends the bug reports.
+ */
+class BugReporter @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val screenshotHolder: ScreenshotHolder,
+ private val crashDataStore: CrashDataStore,
+ /*
+ private val activeSessionHolder: ActiveSessionHolder,
+ private val versionProvider: VersionProvider,
+ private val vectorPreferences: VectorPreferences,
+ private val vectorFileLogger: VectorFileLogger,
+ private val systemLocaleProvider: SystemLocaleProvider,
+ private val matrix: Matrix,
+ private val buildMeta: BuildMeta,
+ private val processInfo: ProcessInfo,
+ private val sdkIntProvider: BuildVersionSdkIntProvider,
+ private val vectorLocale: VectorLocaleProvider,
+ */
+) {
+ var inMultiWindowMode = false
+
+ companion object {
+ // filenames
+ private const val LOG_CAT_ERROR_FILENAME = "logcatError.log"
+ private const val LOG_CAT_FILENAME = "logcat.log"
+ private const val KEY_REQUESTS_FILENAME = "keyRequests.log"
+
+ private const val BUFFER_SIZE = 1024 * 1024 * 50
+ }
+
+ // the http client
+ private val mOkHttpClient = OkHttpClient()
+
+ // the pending bug report call
+ private var mBugReportCall: Call? = null
+
+ // boolean to cancel the bug report
+ private val mIsCancelled = false
+
+ /*
+ val adapter = MatrixJsonParser.getMoshi()
+ .adapter(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java))
+ */
+
+ private val LOGCAT_CMD_ERROR = arrayOf(
+ "logcat", // /< Run 'logcat' command
+ "-d", // /< Dump the log rather than continue outputting it
+ "-v", // formatting
+ "threadtime", // include timestamps
+ "AndroidRuntime:E " + // /< Pick all AndroidRuntime errors (such as uncaught exceptions)"communicatorjni:V " + ///< All communicatorjni logging
+ "libcommunicator:V " + // /< All libcommunicator logging
+ "DEBUG:V " + // /< All DEBUG logging - which includes native land crashes (seg faults, etc)
+ "*:S" // /< Everything else silent, so don't pick it..
+ )
+
+ private val LOGCAT_CMD_DEBUG = arrayOf("logcat", "-d", "-v", "threadtime", "*:*")
+
+ /**
+ * Bug report upload listener.
+ */
+ interface IMXBugReportListener {
+ /**
+ * The bug report has been cancelled.
+ */
+ fun onUploadCancelled()
+
+ /**
+ * The bug report upload failed.
+ *
+ * @param reason the failure reason
+ */
+ fun onUploadFailed(reason: String?)
+
+ /**
+ * The upload progress (in percent).
+ *
+ * @param progress the upload progress
+ */
+ fun onProgress(progress: Int)
+
+ /**
+ * The bug report upload succeeded.
+ */
+ fun onUploadSucceed(reportUrl: String?)
+ }
+
+ /**
+ * Send a bug report.
+ *
+ * @param coroutineScope The coroutine scope
+ * @param reportType The report type (bug, suggestion, feedback)
+ * @param withDevicesLogs true to include the device log
+ * @param withCrashLogs true to include the crash logs
+ * @param withKeyRequestHistory true to include the crash logs
+ * @param withScreenshot true to include the screenshot
+ * @param theBugDescription the bug description
+ * @param serverVersion version of the server
+ * @param canContact true if the user opt in to be contacted directly
+ * @param customFields fields which will be sent with the report
+ * @param listener the listener
+ */
+ fun sendBugReport(
+ coroutineScope: CoroutineScope,
+ reportType: ReportType,
+ withDevicesLogs: Boolean,
+ withCrashLogs: Boolean,
+ withKeyRequestHistory: Boolean,
+ withScreenshot: Boolean,
+ theBugDescription: String,
+ serverVersion: String,
+ canContact: Boolean = false,
+ customFields: Map? = null,
+ listener: IMXBugReportListener?
+ ) {
+ // enumerate files to delete
+ val mBugReportFiles: MutableList = ArrayList()
+
+ coroutineScope.launch {
+ var serverError: String? = null
+ var reportURL: String? = null
+ withContext(Dispatchers.IO) {
+ var bugDescription = theBugDescription
+ val crashCallStack = crashDataStore.crashInfo().first()
+
+ if (crashCallStack.isNotEmpty() && withCrashLogs) {
+ bugDescription += "\n\n\n\n--------------------------------- crash call stack ---------------------------------\n"
+ bugDescription += crashCallStack
+ }
+
+ val gzippedFiles = ArrayList()
+
+ val vectorFileLogger = VectorFileLogger.getFromTimber()
+ if (withDevicesLogs) {
+ val files = vectorFileLogger.getLogFiles()
+ files.mapNotNullTo(gzippedFiles) { f ->
+ if (!mIsCancelled) {
+ compressFile(f)
+ } else {
+ null
+ }
+ }
+ }
+
+ if (!mIsCancelled && (withCrashLogs || withDevicesLogs)) {
+ val gzippedLogcat = saveLogCat(false)
+
+ if (null != gzippedLogcat) {
+ if (gzippedFiles.size == 0) {
+ gzippedFiles.add(gzippedLogcat)
+ } else {
+ gzippedFiles.add(0, gzippedLogcat)
+ }
+ }
+ }
+
+ /*
+ activeSessionHolder.getSafeActiveSession()
+ ?.takeIf { !mIsCancelled && withKeyRequestHistory }
+ ?.cryptoService()
+ ?.getGossipingEvents()
+ ?.let { GossipingEventsSerializer().serialize(it) }
+ ?.toByteArray()
+ ?.let { rawByteArray ->
+ File(context.cacheDir.absolutePath, KEY_REQUESTS_FILENAME)
+ .also {
+ it.outputStream()
+ .use { os -> os.write(rawByteArray) }
+ }
+ }
+ ?.let { compressFile(it) }
+ ?.let { gzippedFiles.add(it) }
+ */
+
+ var deviceId = "undefined"
+ var userId = "undefined"
+ var olmVersion = "undefined"
+
+ /*
+ activeSessionHolder.getSafeActiveSession()?.let { session ->
+ userId = session.myUserId
+ deviceId = session.sessionParams.deviceId ?: "undefined"
+ olmVersion = session.cryptoService().getCryptoVersion(context, true)
+ }
+ */
+
+ if (!mIsCancelled) {
+ val text = when (reportType) {
+ ReportType.BUG_REPORT -> "[ElementX] $bugDescription"
+ ReportType.SUGGESTION -> "[ElementX] [Suggestion] $bugDescription"
+ ReportType.SPACE_BETA_FEEDBACK -> "[ElementX] [spaces-feedback] $bugDescription"
+ ReportType.THREADS_BETA_FEEDBACK -> "[ElementX] [threads-feedback] $bugDescription"
+ ReportType.AUTO_UISI_SENDER,
+ ReportType.AUTO_UISI -> bugDescription
+ }
+
+ // build the multi part request
+ val builder = BugReporterMultipartBody.Builder()
+ .addFormDataPart("text", text)
+ .addFormDataPart("app", rageShakeAppNameForReport(reportType))
+ // .addFormDataPart("user_agent", matrix.getUserAgent())
+ .addFormDataPart("user_id", userId)
+ .addFormDataPart("can_contact", canContact.toString())
+ .addFormDataPart("device_id", deviceId)
+ // .addFormDataPart("version", versionProvider.getVersion(longFormat = true))
+ // .addFormDataPart("branch_name", buildMeta.gitBranchName)
+ // .addFormDataPart("matrix_sdk_version", Matrix.getSdkVersion())
+ .addFormDataPart("olm_version", olmVersion)
+ .addFormDataPart("device", Build.MODEL.trim())
+ // .addFormDataPart("verbose_log", vectorPreferences.labAllowedExtendedLogging().toOnOff())
+ .addFormDataPart("multi_window", inMultiWindowMode.toOnOff())
+ // .addFormDataPart(
+ // "os", Build.VERSION.RELEASE + " (API " + sdkIntProvider.get() + ") " +
+ // Build.VERSION.INCREMENTAL + "-" + Build.VERSION.CODENAME
+ // )
+ .addFormDataPart("locale", Locale.getDefault().toString())
+ // .addFormDataPart("app_language", vectorLocale.applicationLocale.toString())
+ // .addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString())
+ // .addFormDataPart("theme", ThemeUtils.getApplicationTheme(context))
+ .addFormDataPart("server_version", serverVersion)
+ .apply {
+ customFields?.forEach { (name, value) ->
+ addFormDataPart(name, value)
+ }
+ }
+
+ // add the gzipped files
+ for (file in gzippedFiles) {
+ builder.addFormDataPart("compressed-log", file.name, file.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull()))
+ }
+
+ mBugReportFiles.addAll(gzippedFiles)
+
+ if (withScreenshot) {
+ screenshotHolder.getFile()?.let { screenshotFile ->
+ try {
+ builder.addFormDataPart(
+ "file",
+ screenshotFile.name, screenshotFile.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull())
+ )
+ } catch (e: Exception) {
+ Timber.e(e, "## sendBugReport() : fail to write screenshot")
+ }
+ }
+ }
+
+ // add some github labels
+ // builder.addFormDataPart("label", buildMeta.versionName)
+ // builder.addFormDataPart("label", buildMeta.flavorDescription)
+ // builder.addFormDataPart("label", buildMeta.gitBranchName)
+
+ // Special for ElementX
+ builder.addFormDataPart("label", "[ElementX]")
+
+ // Possible values for BuildConfig.BUILD_TYPE: "debug", "nightly", "release".
+ // builder.addFormDataPart("label", BuildConfig.BUILD_TYPE)
+
+ when (reportType) {
+ ReportType.BUG_REPORT -> {
+ /* nop */
+ }
+ ReportType.SUGGESTION -> builder.addFormDataPart("label", "[Suggestion]")
+ ReportType.SPACE_BETA_FEEDBACK -> builder.addFormDataPart("label", "spaces-feedback")
+ ReportType.THREADS_BETA_FEEDBACK -> builder.addFormDataPart("label", "threads-feedback")
+ ReportType.AUTO_UISI -> {
+ builder.addFormDataPart("label", "Z-UISI")
+ builder.addFormDataPart("label", "android")
+ builder.addFormDataPart("label", "uisi-recipient")
+ }
+ ReportType.AUTO_UISI_SENDER -> {
+ builder.addFormDataPart("label", "Z-UISI")
+ builder.addFormDataPart("label", "android")
+ builder.addFormDataPart("label", "uisi-sender")
+ }
+ }
+
+ if (crashCallStack.isNotEmpty() && withCrashLogs) {
+ builder.addFormDataPart("label", "crash")
+ }
+
+ val requestBody = builder.build()
+
+ // add a progress listener
+ requestBody.setWriteListener { totalWritten, contentLength ->
+ val percentage = if (-1L != contentLength) {
+ if (totalWritten > contentLength) {
+ 100
+ } else {
+ (totalWritten * 100 / contentLength).toInt()
+ }
+ } else {
+ 0
+ }
+
+ if (mIsCancelled && null != mBugReportCall) {
+ mBugReportCall!!.cancel()
+ }
+
+ Timber.v("## onWrite() : $percentage%")
+ try {
+ listener?.onProgress(percentage)
+ } catch (e: Exception) {
+ Timber.e(e, "## onProgress() : failed")
+ }
+ }
+
+ // build the request
+ val request = Request.Builder()
+ .url(context.getString(R.string.bug_report_url))
+ .post(requestBody)
+ .build()
+
+ var responseCode = HttpURLConnection.HTTP_INTERNAL_ERROR
+ var response: Response? = null
+ var errorMessage: String? = null
+
+ // trigger the request
+ try {
+ mBugReportCall = mOkHttpClient.newCall(request)
+ response = mBugReportCall!!.execute()
+ responseCode = response.code
+ } catch (e: Exception) {
+ Timber.e(e, "response")
+ errorMessage = e.localizedMessage
+ }
+
+ // if the upload failed, try to retrieve the reason
+ if (responseCode != HttpURLConnection.HTTP_OK) {
+ if (null != errorMessage) {
+ serverError = "Failed with error $errorMessage"
+ } else if (response?.body == null) {
+ serverError = "Failed with error $responseCode"
+ } else {
+ try {
+ val inputStream = response.body!!.byteStream()
+
+ serverError = inputStream.use {
+ buildString {
+ var ch = it.read()
+ while (ch != -1) {
+ append(ch.toChar())
+ ch = it.read()
+ }
+ }
+ }
+
+ // check if the error message
+ serverError?.let {
+ try {
+ val responseJSON = JSONObject(it)
+ serverError = responseJSON.getString("error")
+ } catch (e: JSONException) {
+ Timber.e(e, "doInBackground ; Json conversion failed")
+ }
+ }
+
+ // should never happen
+ if (null == serverError) {
+ serverError = "Failed with error $responseCode"
+ }
+ } catch (e: Exception) {
+ Timber.e(e, "## sendBugReport() : failed to parse error")
+ }
+ }
+ } else {
+ /*
+ reportURL = response?.body?.string()?.let { stringBody ->
+ adapter.fromJson(stringBody)?.get("report_url")?.toString()
+ }
+ */
+ }
+ }
+ }
+
+ withContext(Dispatchers.Main) {
+ mBugReportCall = null
+
+ // delete when the bug report has been successfully sent
+ for (file in mBugReportFiles) {
+ file.delete()
+ }
+
+ if (null != listener) {
+ try {
+ if (mIsCancelled) {
+ listener.onUploadCancelled()
+ } else if (null == serverError) {
+ listener.onUploadSucceed(reportURL)
+ } else {
+ listener.onUploadFailed(serverError)
+ }
+ } catch (e: Exception) {
+ Timber.e(e, "## onPostExecute() : failed")
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Send a bug report either with email or with Vector.
+ */
+ /* TODO Remove
+ fun openBugReportScreen(activity: FragmentActivity, reportType: ReportType = ReportType.BUG_REPORT) {
+ screenshot = takeScreenshot(activity)
+ logDbInfo()
+ logProcessInfo()
+ logOtherInfo()
+ activity.startActivity(BugReportActivity.intent(activity, reportType))
+ }
+ */
+
+ // private fun logOtherInfo() {
+ // Timber.i("SyncThread state: " + activeSessionHolder.getSafeActiveSession()?.syncService()?.getSyncState())
+ // }
+
+ // private fun logDbInfo() {
+ // val dbInfo = matrix.debugService().getDbUsageInfo()
+ // Timber.i(dbInfo)
+ // }
+
+ // private fun logProcessInfo() {
+ // val pInfo = processInfo.getInfo()
+ // Timber.i(pInfo)
+ // }
+
+ private fun rageShakeAppNameForReport(reportType: ReportType): String {
+ // As per https://github.com/matrix-org/rageshake
+ // app: Identifier for the application (eg 'riot-web').
+ // Should correspond to a mapping configured in the configuration file for github issue reporting to work.
+ // (see R.string.bug_report_url for configured RS server)
+ return context.getString(
+ when (reportType) {
+ ReportType.AUTO_UISI_SENDER,
+ ReportType.AUTO_UISI -> R.string.bug_report_auto_uisi_app_name
+ else -> R.string.bug_report_app_name
+ }
+ )
+ }
+
+ // ==============================================================================================================
+ // Logcat management
+ // ==============================================================================================================
+
+ /**
+ * Save the logcat.
+ *
+ * @param isErrorLogcat true to save the error logcat
+ * @return the file if the operation succeeds
+ */
+ private fun saveLogCat(isErrorLogcat: Boolean): File? {
+ val logCatErrFile = File(context.cacheDir.absolutePath, if (isErrorLogcat) LOG_CAT_ERROR_FILENAME else LOG_CAT_FILENAME)
+
+ if (logCatErrFile.exists()) {
+ logCatErrFile.delete()
+ }
+
+ try {
+ logCatErrFile.writer().use {
+ getLogCatError(it, isErrorLogcat)
+ }
+
+ return compressFile(logCatErrFile)
+ } catch (error: OutOfMemoryError) {
+ Timber.e(error, "## saveLogCat() : fail to write logcat$error")
+ } catch (e: Exception) {
+ Timber.e(e, "## saveLogCat() : fail to write logcat$e")
+ }
+
+ return null
+ }
+
+ /**
+ * Retrieves the logs.
+ *
+ * @param streamWriter the stream writer
+ * @param isErrorLogCat true to save the error logs
+ */
+ private fun getLogCatError(streamWriter: OutputStreamWriter, isErrorLogCat: Boolean) {
+ val logcatProc: Process
+
+ try {
+ logcatProc = Runtime.getRuntime().exec(if (isErrorLogCat) LOGCAT_CMD_ERROR else LOGCAT_CMD_DEBUG)
+ } catch (e1: IOException) {
+ return
+ }
+
+ try {
+ val separator = System.getProperty("line.separator")
+ logcatProc.inputStream
+ .reader()
+ .buffered(BUFFER_SIZE)
+ .forEachLine { line ->
+ streamWriter.append(line)
+ streamWriter.append(separator)
+ }
+ } catch (e: IOException) {
+ Timber.e(e, "getLog fails")
+ }
+ }
+}
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/reporter/BugReporterMultipartBody.java b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/reporter/BugReporterMultipartBody.java
new file mode 100755
index 0000000000..275ea89298
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/reporter/BugReporterMultipartBody.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.reporter;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import okhttp3.Headers;
+import okhttp3.MediaType;
+import okhttp3.RequestBody;
+import okhttp3.internal.Util;
+import okio.Buffer;
+import okio.BufferedSink;
+import okio.ByteString;
+
+// simplified version of MultipartBody (OkHttp 3.6.0)
+public class BugReporterMultipartBody extends RequestBody {
+
+ /**
+ * Listener
+ */
+ public interface WriteListener {
+ /**
+ * Upload listener
+ *
+ * @param totalWritten total written bytes
+ * @param contentLength content length
+ */
+ void onWrite(long totalWritten, long contentLength);
+ }
+
+ private static final MediaType FORM = MediaType.parse("multipart/form-data");
+
+ private static final byte[] COLONSPACE = {':', ' '};
+ private static final byte[] CRLF = {'\r', '\n'};
+ private static final byte[] DASHDASH = {'-', '-'};
+
+ private final ByteString mBoundary;
+ private final MediaType mContentType;
+ private final List mParts;
+ private long mContentLength = -1L;
+
+ // listener
+ private WriteListener mWriteListener;
+
+ //
+ private List mContentLengthSize = null;
+
+ private BugReporterMultipartBody(ByteString boundary, List parts) {
+ mBoundary = boundary;
+ mContentType = MediaType.parse(FORM + "; boundary=" + boundary.utf8());
+ mParts = Util.toImmutableList(parts);
+ }
+
+ @Override
+ public MediaType contentType() {
+ return mContentType;
+ }
+
+ @Override
+ public long contentLength() throws IOException {
+ long result = mContentLength;
+ if (result != -1L) return result;
+ return mContentLength = writeOrCountBytes(null, true);
+ }
+
+ @Override
+ public void writeTo(BufferedSink sink) throws IOException {
+ writeOrCountBytes(sink, false);
+ }
+
+ /**
+ * Set the listener
+ *
+ * @param listener the
+ */
+ public void setWriteListener(WriteListener listener) {
+ mWriteListener = listener;
+ }
+
+ /**
+ * Warn the listener that some bytes have been written
+ *
+ * @param totalWrittenBytes the total written bytes
+ */
+ private void onWrite(long totalWrittenBytes) {
+ if ((null != mWriteListener) && (mContentLength > 0)) {
+ mWriteListener.onWrite(totalWrittenBytes, mContentLength);
+ }
+ }
+
+ /**
+ * Either writes this request to {@code sink} or measures its content length. We have one method
+ * do double-duty to make sure the counting and content are consistent, particularly when it comes
+ * to awkward operations like measuring the encoded length of header strings, or the
+ * length-in-digits of an encoded integer.
+ */
+ private long writeOrCountBytes(BufferedSink sink, boolean countBytes) throws IOException {
+ long byteCount = 0L;
+
+ Buffer byteCountBuffer = null;
+ if (countBytes) {
+ sink = byteCountBuffer = new Buffer();
+ mContentLengthSize = new ArrayList<>();
+ }
+
+ for (int p = 0, partCount = mParts.size(); p < partCount; p++) {
+ Part part = mParts.get(p);
+ Headers headers = part.headers;
+ RequestBody body = part.body;
+
+ sink.write(DASHDASH);
+ sink.write(mBoundary);
+ sink.write(CRLF);
+
+ if (headers != null) {
+ for (int h = 0, headerCount = headers.size(); h < headerCount; h++) {
+ sink.writeUtf8(headers.name(h))
+ .write(COLONSPACE)
+ .writeUtf8(headers.value(h))
+ .write(CRLF);
+ }
+ }
+
+ MediaType contentType = body.contentType();
+ if (contentType != null) {
+ sink.writeUtf8("Content-Type: ")
+ .writeUtf8(contentType.toString())
+ .write(CRLF);
+ }
+
+ int contentLength = (int) body.contentLength();
+ if (contentLength != -1) {
+ sink.writeUtf8("Content-Length: ")
+ .writeUtf8(contentLength + "")
+ .write(CRLF);
+ } else if (countBytes) {
+ // We can't measure the body's size without the sizes of its components.
+ byteCountBuffer.clear();
+ return -1L;
+ }
+
+ sink.write(CRLF);
+
+ if (countBytes) {
+ byteCount += contentLength;
+ mContentLengthSize.add(byteCount);
+ } else {
+ body.writeTo(sink);
+
+ // warn the listener of upload progress
+ // sink.buffer().size() does not give the right value
+ // assume that some data are popped
+ if ((null != mContentLengthSize) && (p < mContentLengthSize.size())) {
+ onWrite(mContentLengthSize.get(p));
+ }
+ }
+ sink.write(CRLF);
+ }
+
+ sink.write(DASHDASH);
+ sink.write(mBoundary);
+ sink.write(DASHDASH);
+ sink.write(CRLF);
+
+ if (countBytes) {
+ byteCount += byteCountBuffer.size();
+ byteCountBuffer.clear();
+ }
+
+ return byteCount;
+ }
+
+ private static void appendQuotedString(StringBuilder target, String key) {
+ target.append('"');
+ for (int i = 0, len = key.length(); i < len; i++) {
+ char ch = key.charAt(i);
+ switch (ch) {
+ case '\n':
+ target.append("%0A");
+ break;
+ case '\r':
+ target.append("%0D");
+ break;
+ case '"':
+ target.append("%22");
+ break;
+ default:
+ target.append(ch);
+ break;
+ }
+ }
+ target.append('"');
+ }
+
+ public static final class Part {
+ public static Part create(Headers headers, RequestBody body) {
+ if (body == null) {
+ throw new NullPointerException("body == null");
+ }
+ if (headers != null && headers.get("Content-Type") != null) {
+ throw new IllegalArgumentException("Unexpected header: Content-Type");
+ }
+ if (headers != null && headers.get("Content-Length") != null) {
+ throw new IllegalArgumentException("Unexpected header: Content-Length");
+ }
+ return new Part(headers, body);
+ }
+
+ public static Part createFormData(String name, String value) {
+ return createFormData(name, null, RequestBody.create(value, null));
+ }
+
+ public static Part createFormData(String name, String filename, RequestBody body) {
+ if (name == null) {
+ throw new NullPointerException("name == null");
+ }
+ StringBuilder disposition = new StringBuilder("form-data; name=");
+ appendQuotedString(disposition, name);
+
+ if (filename != null) {
+ disposition.append("; filename=");
+ appendQuotedString(disposition, filename);
+ }
+
+ return create(Headers.of("Content-Disposition", disposition.toString()), body);
+ }
+
+ final Headers headers;
+ final RequestBody body;
+
+ private Part(Headers headers, RequestBody body) {
+ this.headers = headers;
+ this.body = body;
+ }
+ }
+
+ public static final class Builder {
+ private final ByteString boundary;
+ private final List parts = new ArrayList<>();
+
+ public Builder() {
+ this(UUID.randomUUID().toString());
+ }
+
+ public Builder(String boundary) {
+ this.boundary = ByteString.encodeUtf8(boundary);
+ }
+
+ /**
+ * Add a form data part to the body.
+ */
+ public Builder addFormDataPart(String name, String value) {
+ return addPart(Part.createFormData(name, value));
+ }
+
+ /**
+ * Add a form data part to the body.
+ */
+ public Builder addFormDataPart(String name, String filename, RequestBody body) {
+ return addPart(Part.createFormData(name, filename, body));
+ }
+
+ /**
+ * Add a part to the body.
+ */
+ public Builder addPart(Part part) {
+ if (part == null) throw new NullPointerException("part == null");
+ parts.add(part);
+ return this;
+ }
+
+ /**
+ * Assemble the specified parts into a request body.
+ */
+ public BugReporterMultipartBody build() {
+ if (parts.isEmpty()) {
+ throw new IllegalStateException("Multipart body must have at least one part.");
+ }
+ return new BugReporterMultipartBody(boundary, parts);
+ }
+ }
+}
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/reporter/ReportType.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/reporter/ReportType.kt
new file mode 100644
index 0000000000..6523f99e2b
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/reporter/ReportType.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.reporter
+
+enum class ReportType {
+ BUG_REPORT,
+ SUGGESTION,
+ SPACE_BETA_FEEDBACK,
+ THREADS_BETA_FEEDBACK,
+ AUTO_UISI,
+ AUTO_UISI_SENDER,
+}
diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/screenshot/ScreenshotHolder.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/screenshot/ScreenshotHolder.kt
new file mode 100644
index 0000000000..1b028ffb2b
--- /dev/null
+++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/screenshot/ScreenshotHolder.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.rageshake.screenshot
+
+import android.content.Context
+import android.graphics.Bitmap
+import io.element.android.x.core.bitmap.writeBitmap
+import io.element.android.x.di.ApplicationContext
+import java.io.File
+import javax.inject.Inject
+
+class ScreenshotHolder @Inject constructor(
+ @ApplicationContext private val context: Context,
+) {
+ private val file = File(context.filesDir, "screenshot.png")
+
+ fun writeBitmap(data: Bitmap) {
+ file.writeBitmap(data, Bitmap.CompressFormat.PNG, 85)
+ }
+
+ fun getFile() = file.takeIf { it.exists() && it.length() > 0 }
+
+ fun reset() {
+ file.delete()
+ }
+}
diff --git a/features/rageshake/src/main/res/values/strings.xml b/features/rageshake/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..846f0ca048
--- /dev/null
+++ b/features/rageshake/src/main/res/values/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+ https://riot.im/bugreports/submit
+ riot-android
+ element-auto-uisi
+
+
diff --git a/features/rageshake/src/test/java/io/element/android/x/features/login/ExampleUnitTest.kt b/features/rageshake/src/test/java/io/element/android/x/features/login/ExampleUnitTest.kt
new file mode 100644
index 0000000000..39a03196df
--- /dev/null
+++ b/features/rageshake/src/test/java/io/element/android/x/features/login/ExampleUnitTest.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.login
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/features/roomlist/build.gradle.kts b/features/roomlist/build.gradle.kts
index fae8ad97d2..674ec25d88 100644
--- a/features/roomlist/build.gradle.kts
+++ b/features/roomlist/build.gradle.kts
@@ -1,3 +1,21 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
+@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id("io.element.android-compose-library")
alias(libs.plugins.ksp)
@@ -19,8 +37,10 @@ dependencies {
implementation(project(":libraries:core"))
implementation(project(":libraries:architecture"))
implementation(project(":libraries:matrix"))
+ implementation(project(":libraries:matrixui"))
implementation(project(":libraries:designsystem"))
implementation(libs.appyx.core)
+ implementation(project(":libraries:elementresources"))
implementation(libs.datetime)
implementation(libs.accompanist.placeholder)
testImplementation(libs.test.junit)
diff --git a/features/roomlist/src/androidTest/java/io/element/android/x/features/roomlist/ExampleInstrumentedTest.kt b/features/roomlist/src/androidTest/java/io/element/android/x/features/roomlist/ExampleInstrumentedTest.kt
index c16a44bebb..487d999142 100644
--- a/features/roomlist/src/androidTest/java/io/element/android/x/features/roomlist/ExampleInstrumentedTest.kt
+++ b/features/roomlist/src/androidTest/java/io/element/android/x/features/roomlist/ExampleInstrumentedTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.roomlist
import androidx.test.ext.junit.runners.AndroidJUnit4
diff --git a/features/roomlist/src/main/AndroidManifest.xml b/features/roomlist/src/main/AndroidManifest.xml
index e100076157..19db0c3d57 100644
--- a/features/roomlist/src/main/AndroidManifest.xml
+++ b/features/roomlist/src/main/AndroidManifest.xml
@@ -1,4 +1,20 @@
+
+
diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/LastMessageFormatter.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/LastMessageFormatter.kt
index 62b08b6fcf..5e90a52898 100644
--- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/LastMessageFormatter.kt
+++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/LastMessageFormatter.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.roomlist
import android.text.format.DateFormat
diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListNode.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListNode.kt
index 06b089b807..66f17a2f64 100644
--- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListNode.kt
+++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListNode.kt
@@ -53,7 +53,7 @@ class RoomListNode @AssistedInject constructor(
onRoomClicked = this::onRoomClicked,
onFilterChanged = this::updateFilter,
onScrollOver = this::updateVisibleRange,
- onLogoutClicked = this::logout
+ onOpenSettings = this::logout
)
}
}
diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListPresenter.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListPresenter.kt
index 492d098d6d..ee3aa7b6fb 100644
--- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListPresenter.kt
+++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListPresenter.kt
@@ -12,7 +12,6 @@ import androidx.compose.runtime.setValue
import io.element.android.x.core.coroutine.parallelMap
import io.element.android.x.designsystem.components.avatar.AvatarData
import io.element.android.x.designsystem.components.avatar.AvatarSize
-import io.element.android.x.features.roomlist.model.MatrixUser
import io.element.android.x.features.roomlist.model.RoomListEvents
import io.element.android.x.features.roomlist.model.RoomListRoomSummary
import io.element.android.x.features.roomlist.model.RoomListRoomSummaryPlaceholders
@@ -21,6 +20,7 @@ import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.media.MediaResolver
import io.element.android.x.matrix.room.RoomSummary
import io.element.android.x.architecture.Presenter
+import io.element.android.x.matrix.ui.model.MatrixUser
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@@ -93,6 +93,7 @@ class RoomListPresenter @Inject constructor(
AvatarSize.SMALL
)
matrixUser.value = MatrixUser(
+ id = client.userId(),
username = userDisplayName ?: client.userId().value,
avatarUrl = userAvatarUrl,
avatarData = avatarData,
diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListView.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListView.kt
index 8b860e0d66..97c42f7eeb 100644
--- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListView.kt
+++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListView.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
@file:OptIn(ExperimentalMaterial3Api::class)
package io.element.android.x.features.roomlist
@@ -22,15 +38,15 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Velocity
import io.element.android.x.core.compose.LogCompositions
import io.element.android.x.designsystem.ElementXTheme
-import io.element.android.x.designsystem.components.ProgressDialog
import io.element.android.x.designsystem.components.avatar.AvatarData
import io.element.android.x.features.roomlist.components.RoomListTopBar
import io.element.android.x.features.roomlist.components.RoomSummaryRow
-import io.element.android.x.features.roomlist.model.MatrixUser
import io.element.android.x.features.roomlist.model.RoomListRoomSummary
import io.element.android.x.features.roomlist.model.RoomListState
import io.element.android.x.features.roomlist.model.stubbedRoomSummaries
import io.element.android.x.matrix.core.RoomId
+import io.element.android.x.matrix.core.UserId
+import io.element.android.x.matrix.ui.model.MatrixUser
import kotlinx.collections.immutable.ImmutableList
@Composable
@@ -39,19 +55,18 @@ fun RoomListView(
modifier: Modifier = Modifier,
onRoomClicked: (RoomId) -> Unit = {},
onFilterChanged: (String) -> Unit = {},
- onLogoutClicked: () -> Unit = {},
+ onOpenSettings: () -> Unit = {},
onScrollOver: (IntRange) -> Unit = {},
) {
RoomListView(
roomSummaries = state.roomList,
matrixUser = state.matrixUser,
filter = state.filter,
- isLoginOut = state.isLoginOut,
modifier = modifier,
onRoomClicked = onRoomClicked,
onFilterChanged = onFilterChanged,
- onLogoutClicked = onLogoutClicked,
- onScrollOver = onScrollOver
+ onOpenSettings = onOpenSettings,
+ onScrollOver = onScrollOver,
)
}
@@ -60,11 +75,10 @@ fun RoomListView(
roomSummaries: ImmutableList,
matrixUser: MatrixUser?,
filter: String,
- isLoginOut: Boolean,
modifier: Modifier = Modifier,
onRoomClicked: (RoomId) -> Unit = {},
onFilterChanged: (String) -> Unit = {},
- onLogoutClicked: () -> Unit = {},
+ onOpenSettings: () -> Unit = {},
onScrollOver: (IntRange) -> Unit = {},
) {
fun onRoomClicked(room: RoomListRoomSummary) {
@@ -101,7 +115,7 @@ fun RoomListView(
matrixUser = matrixUser,
filter = filter,
onFilterChanged = onFilterChanged,
- onLogoutClicked = onLogoutClicked,
+ onOpenSettings = onOpenSettings,
scrollBehavior = scrollBehavior
)
},
@@ -123,9 +137,6 @@ fun RoomListView(
}
}
)
- if (isLoginOut) {
- ProgressDialog(text = "Login out...")
- }
}
private fun RoomListRoomSummary.contentType() = isPlaceholder
@@ -136,12 +147,10 @@ fun PreviewableRoomListView() {
ElementXTheme(darkTheme = false) {
RoomListView(
roomSummaries = stubbedRoomSummaries(),
- matrixUser = MatrixUser("User#1", avatarData = AvatarData("U")),
+ matrixUser = MatrixUser(id = UserId("@id"), username = "User#1", avatarData = AvatarData("U")),
onRoomClicked = {},
- onLogoutClicked = {},
filter = "filter",
onFilterChanged = {},
- isLoginOut = false,
onScrollOver = {}
)
}
@@ -153,12 +162,10 @@ fun PreviewableDarkRoomListView() {
ElementXTheme(darkTheme = true) {
RoomListView(
roomSummaries = stubbedRoomSummaries(),
- matrixUser = MatrixUser("User#1", avatarData = AvatarData("U")),
+ matrixUser = MatrixUser(id = UserId("@id"), username = "User#1", avatarData = AvatarData("U")),
onRoomClicked = {},
- onLogoutClicked = {},
filter = "filter",
onFilterChanged = {},
- isLoginOut = true,
onScrollOver = {}
)
}
diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomListTopBar.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomListTopBar.kt
index 667357cf73..a6db83f09d 100644
--- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomListTopBar.kt
+++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomListTopBar.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
@file:OptIn(ExperimentalMaterial3Api::class)
package io.element.android.x.features.roomlist.components
@@ -8,8 +24,8 @@ import androidx.compose.material.ContentAlpha
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Close
-import androidx.compose.material.icons.filled.Logout
import androidx.compose.material.icons.filled.Search
+import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -38,15 +54,14 @@ import androidx.compose.ui.unit.sp
import io.element.android.x.core.compose.LogCompositions
import io.element.android.x.core.compose.textFieldState
import io.element.android.x.designsystem.components.avatar.Avatar
-import io.element.android.x.designsystem.components.dialogs.ConfirmationDialog
-import io.element.android.x.features.roomlist.model.MatrixUser
+import io.element.android.x.matrix.ui.model.MatrixUser
@Composable
fun RoomListTopBar(
matrixUser: MatrixUser?,
filter: String,
onFilterChanged: (String) -> Unit,
- onLogoutClicked: () -> Unit,
+ onOpenSettings: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior
) {
LogCompositions(tag = "RoomListScreen", msg = "TopBar")
@@ -71,7 +86,7 @@ fun RoomListTopBar(
} else {
DefaultRoomListTopBar(
matrixUser = matrixUser,
- onLogoutClicked = onLogoutClicked,
+ onOpenSettings = onOpenSettings,
onSearchClicked = {
searchWidgetStateIsOpened = true
},
@@ -160,11 +175,10 @@ fun SearchRoomListTopBar(
@Composable
private fun DefaultRoomListTopBar(
matrixUser: MatrixUser?,
- onLogoutClicked: () -> Unit,
+ onOpenSettings: () -> Unit,
onSearchClicked: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior
) {
- val openDialog = remember { mutableStateOf(false) }
MediumTopAppBar(
modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection),
@@ -188,22 +202,11 @@ private fun DefaultRoomListTopBar(
Icon(Icons.Default.Search, contentDescription = "search")
}
IconButton(
- onClick = { openDialog.value = true }
+ onClick = onOpenSettings
) {
- Icon(Icons.Default.Logout, contentDescription = "logout")
+ Icon(Icons.Default.Settings, contentDescription = "Settings")
}
},
scrollBehavior = scrollBehavior,
)
- // Log out confirmation dialog
- ConfirmationDialog(
- isDisplayed = openDialog.value,
- title = "Log out",
- content = "Do you confirm you want to log out?",
- submitText = "Log out",
- onSubmitClicked = onLogoutClicked,
- onDismiss = {
- openDialog.value = false
- }
- )
}
diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomSummaryRow.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomSummaryRow.kt
index a83d2cd067..ef8b4ac341 100644
--- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomSummaryRow.kt
+++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomSummaryRow.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.roomlist.components
import androidx.compose.foundation.background
diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/MatrixUser.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/MatrixUser.kt
deleted file mode 100644
index f002fc17b5..0000000000
--- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/MatrixUser.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.element.android.x.features.roomlist.model
-
-import androidx.compose.runtime.Stable
-import io.element.android.x.designsystem.components.avatar.AvatarData
-
-@Stable
-data class MatrixUser(
- val username: String? = null,
- val avatarUrl: String? = null,
- val avatarData: AvatarData = AvatarData(),
-)
diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListRoomSummary.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListRoomSummary.kt
index 893824756f..2f17cc0b25 100644
--- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListRoomSummary.kt
+++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListRoomSummary.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.roomlist.model
import androidx.compose.runtime.Stable
diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListRoomSummaryPlaceholders.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListRoomSummaryPlaceholders.kt
index 645114507f..c714baf3d3 100644
--- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListRoomSummaryPlaceholders.kt
+++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListRoomSummaryPlaceholders.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.roomlist.model
import io.element.android.x.designsystem.components.avatar.AvatarData
diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListState.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListState.kt
index a71b2535c1..8e5b159676 100644
--- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListState.kt
+++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListState.kt
@@ -1,6 +1,7 @@
package io.element.android.x.features.roomlist.model
import androidx.compose.runtime.Stable
+import io.element.android.x.matrix.ui.model.MatrixUser
import kotlinx.collections.immutable.ImmutableList
@Stable
diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListViewState.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListViewState.kt
new file mode 100644
index 0000000000..4fb02b054c
--- /dev/null
+++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListViewState.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.features.roomlist.model
+
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.MavericksState
+import com.airbnb.mvrx.Uninitialized
+import io.element.android.x.matrix.core.RoomId
+
+data class RoomListViewState(
+ // Will contain the filtered rooms, using ::filter (if filter is not empty)
+ val rooms: Async> = Uninitialized,
+ val filter: String = "",
+ val canLoadMore: Boolean = false,
+ val roomsById: Map = emptyMap()
+) : MavericksState
diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/stubbed.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/stubbed.kt
index 7813171e45..71ba70829f 100644
--- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/stubbed.kt
+++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/stubbed.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.roomlist.model
import io.element.android.x.designsystem.components.avatar.AvatarData
diff --git a/features/roomlist/src/test/java/io/element/android/x/features/roomlist/ExampleUnitTest.kt b/features/roomlist/src/test/java/io/element/android/x/features/roomlist/ExampleUnitTest.kt
index 38bdf65d63..867f8aa958 100644
--- a/features/roomlist/src/test/java/io/element/android/x/features/roomlist/ExampleUnitTest.kt
+++ b/features/roomlist/src/test/java/io/element/android/x/features/roomlist/ExampleUnitTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.features.roomlist
import org.junit.Assert.assertEquals
diff --git a/gradle.properties b/gradle.properties
index 9caae7208e..27df5e30ca 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,3 +1,19 @@
+#
+# Copyright (c) 2022 New Vector Ltd
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index b71792afba..9010629926 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -51,6 +51,7 @@ showkase = "1.0.0-beta14"
compose_destinations = "1.7.23-beta"
jsoup = "1.15.3"
appyx = "1.0.1"
+seismic = "1.0.3"
# DI
dagger = "2.43"
@@ -59,6 +60,7 @@ anvil = "2.4.2"
# quality
detekt = "1.22.0"
ktlint = "11.0.0"
+dependencygraph = "0.10"
[libraries]
# Project
@@ -74,10 +76,13 @@ androidx_datastore_datastore = { module = "androidx.datastore:datastore", versio
androidx_constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
androidx_recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
androidx_lifecycle_runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" }
+androidx_lifecycle_compose = { module = "androidx.lifecycle:compose", version.ref = "lifecycle" }
+androidx_lifecycle_process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle" }
+
androidx_lifecycle_viewmodel_compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" }
androidx_activity_compose = { module = "androidx.activity:activity-compose", version.ref = "activity_compose" }
-androidx_fragment = {module = "androidx.fragment:fragment-ktx", version.ref = "fragment"}
-androidx_startup = { module = "androidx.startup:startup-runtime", version.ref = "startup"}
+androidx_fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "fragment" }
+androidx_startup = { module = "androidx.startup:startup-runtime", version.ref = "startup" }
androidx_compose_bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose_bom" }
androidx_compose_foundation = { group = "androidx.compose.foundation", name = "foundation" }
@@ -96,6 +101,9 @@ accompanist_pager = { module = "com.google.accompanist:accompanist-pager", versi
accompanist_pagerindicator = { module = "com.google.accompanist:accompanist-pager-indicators", version.ref = "accompanist" }
accompanist_flowlayout = { module = "com.google.accompanist:accompanist-flowlayout", version.ref = "accompanist" }
+# Libraries
+squareup_seismic = { module = "com.squareup:seismic", version.ref = "seismic" }
+
# Test
test_junit = { module = "junit:junit", version.ref = "test_junit" }
test_runner = { module = "androidx.test:runner", version.ref = "test_runner" }
@@ -122,7 +130,7 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
appyx_core = {module = "com.bumble.appyx:core", version.ref = "appyx"}
# Di
-inject = {module = "javax.inject:javax.inject", version = "1"}
+inject = { module = "javax.inject:javax.inject", version = "1" }
dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" }
dagger_compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" }
anvil_compiler_api = { module = "com.squareup.anvil:compiler-api", version.ref = "anvil" }
@@ -138,9 +146,10 @@ android_application = { id = "com.android.application", version.ref = "android_g
android_library = { id = "com.android.library", version.ref = "android_gradle_plugin" }
kotlin_android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin_jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
-kapt = {id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin"}
+kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
-anvil = {id = "com.squareup.anvil", version.ref = "anvil"}
+anvil = { id = "com.squareup.anvil", version.ref = "anvil" }
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
molecule = {id = "app.cash.molecule", version.ref = "molecule"}
+dependencygraph = { id = "com.savvasdalkitsis.module-dependency-graph", version.ref = "dependencygraph" }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 523f15d46c..3da921f2e8 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,3 +1,19 @@
+#
+# Copyright (c) 2022 New Vector Ltd
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
#Fri Oct 07 15:02:00 CEST 2022
distributionBase=GRADLE_USER_HOME
distributionSha256Sum=db9c8211ed63f61f60292c69e80d89196f9eb36665e369e7f00ac4cc841c2219
diff --git a/libraries/architecture/src/main/java/io/element/android/x/architecture/viewmodel/AssistedViewModelFactory.kt b/libraries/architecture/src/main/java/io/element/android/x/architecture/viewmodel/AssistedViewModelFactory.kt
index 6dee6da6d3..9adc3558aa 100644
--- a/libraries/architecture/src/main/java/io/element/android/x/architecture/viewmodel/AssistedViewModelFactory.kt
+++ b/libraries/architecture/src/main/java/io/element/android/x/architecture/viewmodel/AssistedViewModelFactory.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.architecture.viewmodel
import com.airbnb.mvrx.MavericksState
diff --git a/libraries/architecture/src/main/java/io/element/android/x/architecture/viewmodel/DaggerMavericksViewModelFactory.kt b/libraries/architecture/src/main/java/io/element/android/x/architecture/viewmodel/DaggerMavericksViewModelFactory.kt
index 30fd38a3ee..5c31a2b22d 100644
--- a/libraries/architecture/src/main/java/io/element/android/x/architecture/viewmodel/DaggerMavericksViewModelFactory.kt
+++ b/libraries/architecture/src/main/java/io/element/android/x/architecture/viewmodel/DaggerMavericksViewModelFactory.kt
@@ -1,3 +1,18 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package io.element.android.x.architecture.viewmodel
import com.airbnb.mvrx.MavericksState
@@ -29,6 +44,8 @@ inline fun , S : MavericksState> daggerMaveri
* using its AssistedInject Factory. This class should be implemented by the companion object
* of every ViewModel which uses AssistedInject via [daggerMavericksViewModelFactory].
*
+ * @param VM The ViewModel type
+ * @param S The ViewState type
* @param viewModelClass The [Class] of the ViewModel being requested for creation
*
* This class accesses the map of ViewModel class to [AssistedViewModelFactory]s from the nearest [DaggerComponentOwner] and
diff --git a/libraries/architecture/src/main/java/io/element/android/x/architecture/viewmodel/ViewModelKey.kt b/libraries/architecture/src/main/java/io/element/android/x/architecture/viewmodel/ViewModelKey.kt
index 0d4f718caf..35b9b487be 100644
--- a/libraries/architecture/src/main/java/io/element/android/x/architecture/viewmodel/ViewModelKey.kt
+++ b/libraries/architecture/src/main/java/io/element/android/x/architecture/viewmodel/ViewModelKey.kt
@@ -1,11 +1,11 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
diff --git a/libraries/core/build.gradle.kts b/libraries/core/build.gradle.kts
index 6706fcdfa8..4004ca1041 100644
--- a/libraries/core/build.gradle.kts
+++ b/libraries/core/build.gradle.kts
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
plugins {
id("io.element.android-compose-library")
}
diff --git a/libraries/core/src/main/AndroidManifest.xml b/libraries/core/src/main/AndroidManifest.xml
index 568741e54f..c7c3fad8ff 100644
--- a/libraries/core/src/main/AndroidManifest.xml
+++ b/libraries/core/src/main/AndroidManifest.xml
@@ -1,2 +1,22 @@
-
\ No newline at end of file
+
+
+
+
+
+
+
diff --git a/libraries/core/src/main/java/io/element/android/x/core/bitmap/Bitmap.kt b/libraries/core/src/main/java/io/element/android/x/core/bitmap/Bitmap.kt
new file mode 100644
index 0000000000..028c871041
--- /dev/null
+++ b/libraries/core/src/main/java/io/element/android/x/core/bitmap/Bitmap.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.core.bitmap
+
+import android.graphics.Bitmap
+import java.io.File
+
+fun File.writeBitmap(bitmap: Bitmap, format: Bitmap.CompressFormat, quality: Int) {
+ outputStream().use { out ->
+ bitmap.compress(format, quality, out)
+ out.flush()
+ }
+}
diff --git a/libraries/core/src/main/java/io/element/android/x/core/bool/Booleans.kt b/libraries/core/src/main/java/io/element/android/x/core/bool/Booleans.kt
new file mode 100644
index 0000000000..46244de753
--- /dev/null
+++ b/libraries/core/src/main/java/io/element/android/x/core/bool/Booleans.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.core.bool
+
+fun Boolean?.orTrue() = this ?: true
+
+fun Boolean?.orFalse() = this ?: false
diff --git a/libraries/core/src/main/java/io/element/android/x/core/compose/Keyboard.kt b/libraries/core/src/main/java/io/element/android/x/core/compose/Keyboard.kt
index 100d0ba86f..b0b95fc2dc 100644
--- a/libraries/core/src/main/java/io/element/android/x/core/compose/Keyboard.kt
+++ b/libraries/core/src/main/java/io/element/android/x/core/compose/Keyboard.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.core.compose
import androidx.compose.foundation.layout.ExperimentalLayoutApi
diff --git a/libraries/core/src/main/java/io/element/android/x/core/compose/LogCompositions.kt b/libraries/core/src/main/java/io/element/android/x/core/compose/LogCompositions.kt
index 16f57ab409..ec88d4433a 100644
--- a/libraries/core/src/main/java/io/element/android/x/core/compose/LogCompositions.kt
+++ b/libraries/core/src/main/java/io/element/android/x/core/compose/LogCompositions.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.core.compose
import androidx.compose.runtime.Composable
diff --git a/libraries/core/src/main/java/io/element/android/x/core/compose/OnLifecycleEvent.kt b/libraries/core/src/main/java/io/element/android/x/core/compose/OnLifecycleEvent.kt
index c022703236..499ab20bbe 100644
--- a/libraries/core/src/main/java/io/element/android/x/core/compose/OnLifecycleEvent.kt
+++ b/libraries/core/src/main/java/io/element/android/x/core/compose/OnLifecycleEvent.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.core.compose
import androidx.compose.runtime.Composable
diff --git a/libraries/core/src/main/java/io/element/android/x/core/compose/PairCombinedPreviewParameter.kt b/libraries/core/src/main/java/io/element/android/x/core/compose/PairCombinedPreviewParameter.kt
index 1026014b20..74c04cdded 100644
--- a/libraries/core/src/main/java/io/element/android/x/core/compose/PairCombinedPreviewParameter.kt
+++ b/libraries/core/src/main/java/io/element/android/x/core/compose/PairCombinedPreviewParameter.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.core.compose
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
diff --git a/libraries/core/src/main/java/io/element/android/x/core/compose/TextFieldLocalState.kt b/libraries/core/src/main/java/io/element/android/x/core/compose/TextFieldLocalState.kt
index 0b3067968c..fa4b9eea85 100644
--- a/libraries/core/src/main/java/io/element/android/x/core/compose/TextFieldLocalState.kt
+++ b/libraries/core/src/main/java/io/element/android/x/core/compose/TextFieldLocalState.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.core.compose
import androidx.compose.runtime.Composable
diff --git a/libraries/core/src/main/java/io/element/android/x/core/coroutine/CoroutineDispatchers.kt b/libraries/core/src/main/java/io/element/android/x/core/coroutine/CoroutineDispatchers.kt
index f7414fb40b..d36b9240dd 100644
--- a/libraries/core/src/main/java/io/element/android/x/core/coroutine/CoroutineDispatchers.kt
+++ b/libraries/core/src/main/java/io/element/android/x/core/coroutine/CoroutineDispatchers.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.core.coroutine
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/libraries/core/src/main/java/io/element/android/x/core/coroutine/TimingOperators.kt b/libraries/core/src/main/java/io/element/android/x/core/coroutine/TimingOperators.kt
index c29cb79d9e..67405aeb9f 100644
--- a/libraries/core/src/main/java/io/element/android/x/core/coroutine/TimingOperators.kt
+++ b/libraries/core/src/main/java/io/element/android/x/core/coroutine/TimingOperators.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.core.coroutine
import android.os.SystemClock
diff --git a/libraries/core/src/main/java/io/element/android/x/core/coroutine/pmap.kt b/libraries/core/src/main/java/io/element/android/x/core/coroutine/pmap.kt
index 90d2b38984..a952d74222 100644
--- a/libraries/core/src/main/java/io/element/android/x/core/coroutine/pmap.kt
+++ b/libraries/core/src/main/java/io/element/android/x/core/coroutine/pmap.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.core.coroutine
import kotlinx.coroutines.async
diff --git a/libraries/core/src/main/java/io/element/android/x/core/data/StableCharSequence.kt b/libraries/core/src/main/java/io/element/android/x/core/data/StableCharSequence.kt
index 0475b41599..92e9cae64a 100644
--- a/libraries/core/src/main/java/io/element/android/x/core/data/StableCharSequence.kt
+++ b/libraries/core/src/main/java/io/element/android/x/core/data/StableCharSequence.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.core.data
/**
diff --git a/libraries/core/src/main/java/io/element/android/x/core/data/Try.kt b/libraries/core/src/main/java/io/element/android/x/core/data/Try.kt
index d6a4148ee1..ccaf5fb54b 100644
--- a/libraries/core/src/main/java/io/element/android/x/core/data/Try.kt
+++ b/libraries/core/src/main/java/io/element/android/x/core/data/Try.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.core.data
import timber.log.Timber
diff --git a/libraries/core/src/main/java/io/element/android/x/core/di/DaggerComponentOwner.kt b/libraries/core/src/main/java/io/element/android/x/core/di/DaggerComponentOwner.kt
index d8e469befe..12f51350b8 100644
--- a/libraries/core/src/main/java/io/element/android/x/core/di/DaggerComponentOwner.kt
+++ b/libraries/core/src/main/java/io/element/android/x/core/di/DaggerComponentOwner.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.core.di
/**
@@ -7,4 +23,4 @@ package io.element.android.x.core.di
interface DaggerComponentOwner {
/** This is either a component, or a list of components. */
val daggerComponent: Any
-}
\ No newline at end of file
+}
diff --git a/libraries/core/src/main/java/io/element/android/x/core/extensions/BasicExtensions.kt b/libraries/core/src/main/java/io/element/android/x/core/extensions/BasicExtensions.kt
new file mode 100644
index 0000000000..5e77bb3b91
--- /dev/null
+++ b/libraries/core/src/main/java/io/element/android/x/core/extensions/BasicExtensions.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.core.extensions
+
+import android.util.Patterns
+
+fun Boolean.toOnOff() = if (this) "ON" else "OFF"
+
+inline fun T.ooi(block: (T) -> Unit): T = also(block)
+
+/**
+ * Check if a CharSequence is an email.
+ */
+fun CharSequence.isEmail() = Patterns.EMAIL_ADDRESS.matcher(this).matches()
+
+// fun CharSequence.isMatrixId() = MatrixPatterns.isUserId(this.toString())
+
+/**
+ * Return empty CharSequence if the CharSequence is null.
+ */
+fun CharSequence?.orEmpty() = this ?: ""
+
+/**
+ * Check if a CharSequence is a phone number.
+ */
+/*
+fun CharSequence.isMsisdn(): Boolean {
+ return try {
+ PhoneNumberUtil.getInstance().parse(ensurePrefix("+"), null)
+ true
+ } catch (e: NumberParseException) {
+ false
+ }
+}
+ */
+
+/**
+ * Useful to append a String at the end of a filename but before the extension if any
+ * Ex:
+ * - "file.txt".insertBeforeLast("_foo") will return "file_foo.txt"
+ * - "file".insertBeforeLast("_foo") will return "file_foo"
+ * - "fi.le.txt".insertBeforeLast("_foo") will return "fi.le_foo.txt"
+ * - null.insertBeforeLast("_foo") will return "_foo".
+ */
+fun String?.insertBeforeLast(insert: String, delimiter: String = "."): String {
+ if (this == null) return insert
+ val idx = lastIndexOf(delimiter)
+ return if (idx == -1) {
+ this + insert
+ } else {
+ replaceRange(idx, idx, insert)
+ }
+}
+
+inline fun Any?.takeAs(): R? {
+ return takeIf { it is R } as R?
+}
diff --git a/libraries/core/src/main/java/io/element/android/x/core/file/compressFile.kt b/libraries/core/src/main/java/io/element/android/x/core/file/compressFile.kt
new file mode 100644
index 0000000000..aade7bf8e3
--- /dev/null
+++ b/libraries/core/src/main/java/io/element/android/x/core/file/compressFile.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.core.file
+
+import java.io.File
+import java.util.zip.GZIPOutputStream
+import timber.log.Timber
+
+/**
+ * GZip a file.
+ *
+ * @param file the input file
+ * @return the gzipped file
+ */
+fun compressFile(file: File): File? {
+ Timber.v("## compressFile() : compress ${file.name}")
+
+ val dstFile = file.resolveSibling(file.name + ".gz")
+
+ if (dstFile.exists()) {
+ dstFile.delete()
+ }
+
+ return try {
+ GZIPOutputStream(dstFile.outputStream()).use { gos ->
+ file.inputStream().use {
+ it.copyTo(gos, 2048)
+ }
+ }
+
+ Timber.v("## compressFile() : ${file.length()} compressed to ${dstFile.length()} bytes")
+ dstFile
+ } catch (e: Exception) {
+ Timber.e(e, "## compressFile() failed")
+ null
+ } catch (oom: OutOfMemoryError) {
+ Timber.e(oom, "## compressFile() failed")
+ null
+ }
+}
diff --git a/libraries/core/src/main/java/io/element/android/x/core/hardware/vibrator.kt b/libraries/core/src/main/java/io/element/android/x/core/hardware/vibrator.kt
new file mode 100644
index 0000000000..de22f9c463
--- /dev/null
+++ b/libraries/core/src/main/java/io/element/android/x/core/hardware/vibrator.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.core.hardware
+
+import android.content.Context
+import android.os.Build
+import android.os.VibrationEffect
+import android.os.Vibrator
+import androidx.core.content.getSystemService
+
+fun Context.vibrate(durationMillis: Long = 100) {
+ val vibrator = getSystemService() ?: return
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ vibrator.vibrate(VibrationEffect.createOneShot(durationMillis, VibrationEffect.DEFAULT_AMPLITUDE))
+ } else {
+ @Suppress("DEPRECATION")
+ vibrator.vibrate(durationMillis)
+ }
+}
diff --git a/libraries/core/src/main/java/io/element/android/x/core/mimetype/MimeTypes.kt b/libraries/core/src/main/java/io/element/android/x/core/mimetype/MimeTypes.kt
new file mode 100644
index 0000000000..fa21816cfd
--- /dev/null
+++ b/libraries/core/src/main/java/io/element/android/x/core/mimetype/MimeTypes.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.core.mimetype
+
+import io.element.android.x.core.bool.orFalse
+
+// The Android SDK does not provide constant for mime type, add some of them here
+object MimeTypes {
+ const val Any: String = "*/*"
+ const val OctetStream = "application/octet-stream"
+ const val Apk = "application/vnd.android.package-archive"
+
+ const val Images = "image/*"
+
+ const val Png = "image/png"
+ const val BadJpg = "image/jpg"
+ const val Jpeg = "image/jpeg"
+ const val Gif = "image/gif"
+
+ const val Ogg = "audio/ogg"
+
+ const val PlainText = "text/plain"
+
+ fun String?.normalizeMimeType() = if (this == BadJpg) Jpeg else this
+
+ fun String?.isMimeTypeImage() = this?.startsWith("image/").orFalse()
+ fun String?.isMimeTypeVideo() = this?.startsWith("video/").orFalse()
+ fun String?.isMimeTypeAudio() = this?.startsWith("audio/").orFalse()
+ fun String?.isMimeTypeApplication() = this?.startsWith("application/").orFalse()
+ fun String?.isMimeTypeFile() = this?.startsWith("file/").orFalse()
+ fun String?.isMimeTypeText() = this?.startsWith("text/").orFalse()
+ fun String?.isMimeTypeAny() = this?.startsWith("*/").orFalse()
+}
diff --git a/libraries/core/src/main/java/io/element/android/x/core/screenshot/Screenshot.kt b/libraries/core/src/main/java/io/element/android/x/core/screenshot/Screenshot.kt
new file mode 100644
index 0000000000..26a2e57c9e
--- /dev/null
+++ b/libraries/core/src/main/java/io/element/android/x/core/screenshot/Screenshot.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.core.screenshot
+
+import android.app.Activity
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.view.PixelCopy
+import android.view.View
+
+fun View.screenshot(bitmapCallback: (ImageResult) -> Unit) {
+ try {
+ val handler = Handler(Looper.getMainLooper())
+ val bitmap = Bitmap.createBitmap(
+ width,
+ height,
+ Bitmap.Config.ARGB_8888,
+ )
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ PixelCopy.request(
+ (this.context as Activity).window,
+ clipBounds,
+ bitmap,
+ {
+ when (it) {
+ PixelCopy.SUCCESS -> {
+ bitmapCallback.invoke(ImageResult.Success(bitmap))
+ }
+ else -> {
+ bitmapCallback.invoke(ImageResult.Error(Exception(it.toString())))
+ }
+ }
+ },
+ handler
+ )
+ } else {
+ handler.post {
+ val canvas = Canvas(bitmap)
+ .apply {
+ translate(-clipBounds.left.toFloat(), -clipBounds.top.toFloat())
+ }
+ this.draw(canvas)
+ canvas.setBitmap(null)
+ bitmapCallback.invoke(ImageResult.Success(bitmap))
+ }
+ }
+ } catch (e: Exception) {
+ bitmapCallback.invoke(ImageResult.Error(e))
+ }
+}
+
+sealed interface ImageResult {
+ data class Error(val exception: Exception) : ImageResult
+ data class Success(val data: Bitmap) : ImageResult
+}
diff --git a/libraries/core/src/main/java/io/element/android/x/core/ui/DimensionConverter.kt b/libraries/core/src/main/java/io/element/android/x/core/ui/DimensionConverter.kt
index bdca1eefd0..f0395b1d3d 100644
--- a/libraries/core/src/main/java/io/element/android/x/core/ui/DimensionConverter.kt
+++ b/libraries/core/src/main/java/io/element/android/x/core/ui/DimensionConverter.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/libraries/core/src/main/java/io/element/android/x/core/ui/View.kt b/libraries/core/src/main/java/io/element/android/x/core/ui/View.kt
index a240a4d10c..99147d76a6 100644
--- a/libraries/core/src/main/java/io/element/android/x/core/ui/View.kt
+++ b/libraries/core/src/main/java/io/element/android/x/core/ui/View.kt
@@ -1,11 +1,11 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
diff --git a/libraries/core/src/main/java/io/element/android/x/core/uri/UrlUtils.kt b/libraries/core/src/main/java/io/element/android/x/core/uri/UrlUtils.kt
index 7718cd604b..8574727e35 100644
--- a/libraries/core/src/main/java/io/element/android/x/core/uri/UrlUtils.kt
+++ b/libraries/core/src/main/java/io/element/android/x/core/uri/UrlUtils.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.core.uri
import java.net.URL
diff --git a/libraries/core/src/main/res/values-ldrtl/integers.xml b/libraries/core/src/main/res/values-ldrtl/integers.xml
index 88b587c96f..f563f32b51 100644
--- a/libraries/core/src/main/res/values-ldrtl/integers.xml
+++ b/libraries/core/src/main/res/values-ldrtl/integers.xml
@@ -1,7 +1,23 @@
+
+
-1180
-
\ No newline at end of file
+
diff --git a/libraries/core/src/main/res/values/integers.xml b/libraries/core/src/main/res/values/integers.xml
index 4b147b3a98..ecbfa4cdda 100644
--- a/libraries/core/src/main/res/values/integers.xml
+++ b/libraries/core/src/main/res/values/integers.xml
@@ -1,7 +1,23 @@
+
+
10
-
\ No newline at end of file
+
diff --git a/libraries/designsystem/build.gradle.kts b/libraries/designsystem/build.gradle.kts
index 2fb20b4f6c..9c70a68b4b 100644
--- a/libraries/designsystem/build.gradle.kts
+++ b/libraries/designsystem/build.gradle.kts
@@ -1,3 +1,21 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
+@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id("io.element.android-compose-library")
alias(libs.plugins.ksp)
@@ -10,6 +28,7 @@ android {
// Should not be there, but this is a POC
implementation(libs.coil.compose)
implementation(libs.accompanist.systemui)
+ implementation(project(":libraries:elementresources"))
ksp(libs.showkase.processor)
}
}
diff --git a/libraries/designsystem/src/main/AndroidManifest.xml b/libraries/designsystem/src/main/AndroidManifest.xml
index e100076157..19db0c3d57 100644
--- a/libraries/designsystem/src/main/AndroidManifest.xml
+++ b/libraries/designsystem/src/main/AndroidManifest.xml
@@ -1,4 +1,20 @@
+
+
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/Color.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/Color.kt
index 6bf9aaca92..c2ce45f879 100644
--- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/Color.kt
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/Color.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.designsystem
import androidx.compose.ui.graphics.Color
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/ColorUtil.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/ColorUtil.kt
new file mode 100644
index 0000000000..7ecf7c428e
--- /dev/null
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/ColorUtil.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.designsystem
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+
+@Composable
+fun Boolean.toEnabledColor(): Color {
+ return if (this) {
+ MaterialTheme.colorScheme.primary
+ } else {
+ MaterialTheme.colorScheme.secondary
+ }
+}
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/Theme.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/Theme.kt
index 2e72ebe4ee..34bda66a21 100644
--- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/Theme.kt
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/Theme.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.designsystem
import android.os.Build
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/Type.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/Type.kt
index da25eb21ef..df87fba257 100644
--- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/Type.kt
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/Type.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.designsystem
import androidx.compose.material3.Typography
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/VectorIcons.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/VectorIcons.kt
index 0f6be4989d..d29ff12b9e 100644
--- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/VectorIcons.kt
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/VectorIcons.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.designsystem
import io.element.android.x.libraries.designsystem.R
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/ClickableLinkText.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/ClickableLinkText.kt
index 0efdb52771..1166a20f92 100644
--- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/ClickableLinkText.kt
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/ClickableLinkText.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.designsystem.components
import androidx.compose.foundation.gestures.detectTapGestures
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/LabelledCheckbox.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/LabelledCheckbox.kt
new file mode 100644
index 0000000000..fcbc6f8d02
--- /dev/null
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/LabelledCheckbox.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.designsystem.components
+
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Checkbox
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+
+@Composable
+fun LabelledCheckbox(
+ checked: Boolean,
+ text: String,
+ modifier: Modifier = Modifier,
+ onCheckedChange: (Boolean) -> Unit = {},
+ enabled: Boolean = true,
+) {
+ Row(
+ modifier = modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Checkbox(
+ checked = checked,
+ onCheckedChange = onCheckedChange,
+ enabled = enabled,
+ )
+ Text(text = text)
+ }
+}
+
+@Preview
+@Composable
+fun LabelledCheckboxPreview() {
+ LabelledCheckbox(
+ checked = true,
+ text = "Some text",
+ )
+}
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/ProgressDialog.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/ProgressDialog.kt
index 885fff2a61..c374dec691 100644
--- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/ProgressDialog.kt
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/ProgressDialog.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.designsystem.components
import androidx.compose.foundation.background
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/VectorButton.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/VectorButton.kt
index df1939e9af..e728636d55 100644
--- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/VectorButton.kt
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/VectorButton.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.designsystem.components
import androidx.compose.material3.Button
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/VectorIcon.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/VectorIcon.kt
index 2726ba0fd1..f0e93aefd0 100644
--- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/VectorIcon.kt
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/VectorIcon.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.designsystem.components
import androidx.compose.material3.Icon
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/Avatar.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/Avatar.kt
index cdd1e4bb5b..73451441e6 100644
--- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/Avatar.kt
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/Avatar.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.designsystem.components.avatar
import androidx.compose.foundation.background
@@ -13,6 +29,7 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import io.element.android.x.designsystem.AvatarGradientEnd
@@ -26,13 +43,13 @@ fun Avatar(avatarData: AvatarData, modifier: Modifier = Modifier) {
.clip(CircleShape)
if (avatarData.model == null) {
InitialsAvatar(
+ avatarData = avatarData,
modifier = commonModifier,
- initials = avatarData.name.first().uppercase()
)
} else {
ImageAvatar(
+ avatarData = avatarData,
modifier = commonModifier,
- avatarData = avatarData
)
}
}
@@ -55,7 +72,7 @@ private fun ImageAvatar(
@Composable
private fun InitialsAvatar(
- initials: String,
+ avatarData: AvatarData,
modifier: Modifier = Modifier,
) {
val initialsGradient = Brush.linearGradient(
@@ -71,9 +88,15 @@ private fun InitialsAvatar(
) {
Text(
modifier = Modifier.align(Alignment.Center),
- text = initials,
- fontSize = 24.sp,
+ text = avatarData.name.first().uppercase(),
+ fontSize = (avatarData.size.value / 2).sp,
color = Color.White,
)
}
}
+
+@Preview
+@Composable
+fun InitialsAvatar() {
+ InitialsAvatar(AvatarData("A"))
+}
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/AvatarData.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/AvatarData.kt
index 34892867ce..346aeca336 100644
--- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/AvatarData.kt
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/AvatarData.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.designsystem.components.avatar
import androidx.compose.runtime.Stable
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/AvatarSize.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/AvatarSize.kt
index d7834fd352..a0b4643191 100644
--- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/AvatarSize.kt
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/AvatarSize.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.designsystem.components.avatar
import androidx.compose.ui.unit.dp
@@ -5,7 +21,8 @@ import androidx.compose.ui.unit.dp
enum class AvatarSize(val value: Int) {
SMALL(32),
MEDIUM(40),
- BIG(48);
+ BIG(48),
+ HUGE(96);
val dp = value.dp
}
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/dialogs/ConfirmationDialog.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/dialogs/ConfirmationDialog.kt
index fb43ccd3ce..349ec32f41 100644
--- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/dialogs/ConfirmationDialog.kt
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/dialogs/ConfirmationDialog.kt
@@ -1,6 +1,23 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.element.android.x.designsystem.components.dialogs
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -9,21 +26,24 @@ import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import io.element.android.x.element.resources.R as ElementR
@Composable
fun ConfirmationDialog(
- isDisplayed: Boolean,
title: String,
content: String,
modifier: Modifier = Modifier,
- submitText: String = "OK",
- cancelText: String = "Cancel",
+ submitText: String = stringResource(id = ElementR.string.ok),
+ cancelText: String = stringResource(id = ElementR.string.action_cancel),
+ thirdButtonText: String? = null,
onSubmitClicked: () -> Unit = {},
+ onCancelClicked: () -> Unit = {},
+ onThirdButtonClicked: () -> Unit = {},
onDismiss: () -> Unit = {},
) {
- if (!isDisplayed) return
AlertDialog(
modifier = modifier,
onDismissRequest = onDismiss,
@@ -33,6 +53,31 @@ fun ConfirmationDialog(
text = {
Text(content)
},
+ dismissButton = {
+ Row(
+ modifier = Modifier.padding(all = 8.dp),
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Column {
+ Button(
+ modifier = Modifier.fillMaxWidth(),
+ onClick = {
+ onCancelClicked()
+ }) {
+ Text(cancelText)
+ }
+ if (thirdButtonText != null) {
+ Button(
+ modifier = Modifier.fillMaxWidth(),
+ onClick = {
+ onThirdButtonClicked()
+ }) {
+ Text(thirdButtonText)
+ }
+ }
+ }
+ }
+ },
confirmButton = {
Row(
modifier = Modifier.padding(all = 8.dp),
@@ -41,7 +86,6 @@ fun ConfirmationDialog(
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
- onDismiss()
onSubmitClicked()
}
) {
@@ -49,20 +93,6 @@ fun ConfirmationDialog(
}
}
},
- dismissButton = {
- Row(
- modifier = Modifier.padding(all = 8.dp),
- horizontalArrangement = Arrangement.Center
- ) {
- Button(
- modifier = Modifier.fillMaxWidth(),
- onClick = {
- onDismiss()
- }) {
- Text(cancelText)
- }
- }
- }
)
}
@@ -70,8 +100,8 @@ fun ConfirmationDialog(
@Preview
fun ConfirmationDialogPreview() {
ConfirmationDialog(
- isDisplayed = true,
title = "Title",
content = "Content",
+ thirdButtonText = "Disable"
)
}
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/dialogs/ErrorDialog.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/dialogs/ErrorDialog.kt
new file mode 100644
index 0000000000..b46f3104a7
--- /dev/null
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/dialogs/ErrorDialog.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.designsystem.components.dialogs
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import io.element.android.x.element.resources.R as ElementR
+
+@Composable
+fun ErrorDialog(
+ content: String,
+ modifier: Modifier = Modifier,
+ title: String = stringResource(id = ElementR.string.dialog_title_error),
+ submitText: String = stringResource(id = ElementR.string.ok),
+ onDismiss: () -> Unit = {},
+) {
+ AlertDialog(
+ modifier = modifier,
+ onDismissRequest = onDismiss,
+ title = {
+ Text(text = title)
+ },
+ text = {
+ Text(content)
+ },
+ confirmButton = {
+ Row(
+ modifier = Modifier.padding(all = 8.dp),
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Button(
+ modifier = Modifier.fillMaxWidth(),
+ onClick = {
+ onDismiss()
+ }
+ ) {
+ Text(submitText)
+ }
+ }
+ },
+ )
+}
+
+@Composable
+@Preview
+fun ErrorDialogPreview() {
+ ErrorDialog(
+ content = "Content",
+ )
+}
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/Config.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/Config.kt
new file mode 100644
index 0000000000..ffce99028d
--- /dev/null
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/Config.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.designsystem.components.preferences
+
+import androidx.compose.ui.unit.dp
+
+internal val preferenceMinHeight = 80.dp
+internal val preferencePaddingEnd = 16.dp
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceCategory.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceCategory.kt
new file mode 100644
index 0000000000..74b13ffa6b
--- /dev/null
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceCategory.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.designsystem.components.preferences
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Divider
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun PreferenceCategory(
+ title: String,
+ modifier: Modifier = Modifier,
+ content: @Composable ColumnScope.() -> Unit,
+) {
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ ) {
+ Divider(
+ modifier = Modifier.padding(horizontal = 16.dp),
+ color = MaterialTheme.colorScheme.secondary,
+ thickness = 1.dp
+ )
+ Text(
+ modifier = Modifier.padding(top = 8.dp, start = 56.dp),
+ style = MaterialTheme.typography.titleSmall,
+ text = title
+ )
+ content()
+ }
+}
+
+@Composable
+@Preview(showBackground = false)
+fun PreferenceCategoryPreview() {
+ PreferenceCategory(
+ title = "Category title",
+ ) {
+ PreferenceTextPreview()
+ PreferenceSwitchPreview()
+ PreferenceSlidePreview()
+ }
+}
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceScreen.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceScreen.kt
new file mode 100644
index 0000000000..6fca5f5504
--- /dev/null
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceScreen.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.designsystem.components.preferences
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.imePadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.statusBars
+import androidx.compose.foundation.layout.systemBarsPadding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.sp
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun PreferenceScreen(
+ title: String,
+ modifier: Modifier = Modifier,
+ onBackPressed: () -> Unit = {},
+ content: @Composable ColumnScope.() -> Unit,
+) {
+ Scaffold(
+ modifier = modifier
+ .fillMaxSize()
+ .systemBarsPadding()
+ .imePadding(),
+ contentWindowInsets = WindowInsets.statusBars,
+ topBar = {
+ PreferenceTopAppBar(
+ title = title,
+ onBackPressed = onBackPressed,
+ )
+ },
+ content = {
+ val scrollState = rememberScrollState()
+ Column(
+ modifier = Modifier
+ .padding(it)
+ .verticalScroll(
+ state = scrollState,
+ )
+ ) {
+ content()
+ }
+ }
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun PreferenceTopAppBar(
+ title: String,
+ modifier: Modifier = Modifier,
+ onBackPressed: () -> Unit = {},
+) {
+ TopAppBar(
+ modifier = modifier,
+ navigationIcon = {
+ IconButton(onClick = onBackPressed) {
+ Icon(
+ imageVector = Icons.Filled.ArrowBack,
+ contentDescription = "Back"
+ )
+ }
+ },
+ title = {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text(
+ fontSize = 16.sp,
+ fontWeight = FontWeight.SemiBold,
+ text = title,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ }
+ }
+
+ )
+}
+
+@Composable
+@Preview(showBackground = false)
+fun PreferenceScreenPreview() {
+ PreferenceScreen(
+ title = "Preference screen"
+ ) {
+ PreferenceCategoryPreview()
+ }
+}
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceSlide.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceSlide.kt
new file mode 100644
index 0000000000..672f94fbcb
--- /dev/null
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceSlide.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.designsystem.components.preferences
+
+import androidx.annotation.FloatRange
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Slider
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.tooling.preview.Preview
+import io.element.android.x.designsystem.components.preferences.components.PreferenceIcon
+import io.element.android.x.designsystem.toEnabledColor
+
+@Composable
+fun PreferenceSlide(
+ title: String,
+ @FloatRange(0.0, 1.0)
+ value: Float,
+ modifier: Modifier = Modifier,
+ icon: ImageVector? = null,
+ enabled: Boolean = true,
+ summary: String? = null,
+ steps: Int = 0,
+ onValueChange: (Float) -> Unit = {},
+) {
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .defaultMinSize(minHeight = preferenceMinHeight),
+ contentAlignment = Alignment.CenterStart
+ ) {
+ Row(modifier = Modifier.fillMaxWidth()) {
+ PreferenceIcon(icon = icon)
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .padding(end = preferencePaddingEnd),
+ ) {
+ Text(
+ modifier = Modifier.fillMaxWidth(),
+ style = MaterialTheme.typography.bodyLarge,
+ color = enabled.toEnabledColor(),
+ text = title
+ )
+ summary?.let {
+ Text(
+ modifier = Modifier.fillMaxWidth(),
+ style = MaterialTheme.typography.bodyMedium,
+ color = enabled.toEnabledColor(),
+ text = summary
+ )
+ }
+ Slider(
+ value = value,
+ steps = steps,
+ onValueChange = onValueChange,
+ enabled = enabled,
+ )
+ }
+ }
+ }
+}
+
+@Composable
+@Preview(showBackground = false)
+fun PreferenceSlidePreview() {
+ PreferenceSlide(
+ title = "Slide",
+ summary = "Summary",
+ value = 0.75F
+ )
+}
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceSwitch.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceSwitch.kt
new file mode 100644
index 0000000000..ab2757936d
--- /dev/null
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceSwitch.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.designsystem.components.preferences
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Announcement
+import androidx.compose.material3.Checkbox
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.tooling.preview.Preview
+import io.element.android.x.designsystem.components.preferences.components.PreferenceIcon
+import io.element.android.x.designsystem.toEnabledColor
+
+@Composable
+fun PreferenceSwitch(
+ title: String,
+ isChecked: Boolean,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ icon: ImageVector? = null,
+ onCheckedChange: (Boolean) -> Unit = {},
+) {
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .defaultMinSize(minHeight = preferenceMinHeight)
+ .clickable { onCheckedChange(!isChecked) },
+ contentAlignment = Alignment.CenterStart
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ PreferenceIcon(
+ icon = icon,
+ enabled = enabled
+ )
+ Text(
+ modifier = Modifier.weight(1f),
+ style = MaterialTheme.typography.bodyLarge,
+ color = enabled.toEnabledColor(),
+ text = title
+ )
+ Checkbox(
+ modifier = Modifier.padding(end = preferencePaddingEnd),
+ checked = isChecked,
+ enabled = enabled,
+ onCheckedChange = onCheckedChange
+ )
+ }
+ }
+}
+
+@Composable
+@Preview(showBackground = false)
+fun PreferenceSwitchPreview() {
+ PreferenceSwitch(
+ title = "Switch",
+ icon = Icons.Default.Announcement,
+ isChecked = true
+ )
+}
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceText.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceText.kt
new file mode 100644
index 0000000000..70c3419bc0
--- /dev/null
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceText.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.designsystem.components.preferences
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.BugReport
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.tooling.preview.Preview
+import io.element.android.x.designsystem.components.preferences.components.PreferenceIcon
+
+@Composable
+fun PreferenceText(
+ title: String,
+ // TODO subtitle
+ modifier: Modifier = Modifier,
+ icon: ImageVector? = null,
+ onClick: () -> Unit = {},
+) {
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .defaultMinSize(minHeight = preferenceMinHeight)
+ .clickable { onClick() },
+ contentAlignment = Alignment.Center
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ PreferenceIcon(icon = icon)
+ Text(
+ modifier = Modifier
+ .weight(1f)
+ .padding(end = preferencePaddingEnd),
+ style = MaterialTheme.typography.bodyLarge,
+ text = title
+ )
+ }
+ }
+}
+
+@Composable
+@Preview(showBackground = false)
+fun PreferenceTextPreview() {
+ PreferenceText(
+ title = "Title",
+ icon = Icons.Default.BugReport,
+ )
+}
diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/components/PreferenceIcon.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/components/PreferenceIcon.kt
new file mode 100644
index 0000000000..9dc5fe0e8f
--- /dev/null
+++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/components/PreferenceIcon.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.x.designsystem.components.preferences.components
+
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.unit.dp
+import io.element.android.x.designsystem.toEnabledColor
+
+@Composable
+fun PreferenceIcon(
+ icon: ImageVector?,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true
+) {
+ if (icon != null) {
+ Icon(
+ imageVector = icon,
+ contentDescription = "",
+ tint = enabled.toEnabledColor(),
+ modifier = modifier
+ .padding(start = 8.dp)
+ .width(48.dp),
+ )
+ } else {
+ Spacer(modifier = modifier.width(56.dp))
+ }
+}
diff --git a/libraries/designsystem/src/main/res/drawable/ic_baseline_delete_outline_24.xml b/libraries/designsystem/src/main/res/drawable/ic_baseline_delete_outline_24.xml
index 33b26af516..479bafb78b 100644
--- a/libraries/designsystem/src/main/res/drawable/ic_baseline_delete_outline_24.xml
+++ b/libraries/designsystem/src/main/res/drawable/ic_baseline_delete_outline_24.xml
@@ -1,3 +1,19 @@
+
+
+
+
+
+
)
\ No newline at end of file
+annotation class SingleIn(val clazz: KClass<*>)
diff --git a/libraries/elementresources/build.gradle.kts b/libraries/elementresources/build.gradle.kts
index aea3268bf4..3202b47a8d 100644
--- a/libraries/elementresources/build.gradle.kts
+++ b/libraries/elementresources/build.gradle.kts
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
plugins {
id("io.element.android-library")
}
diff --git a/libraries/elementresources/src/main/AndroidManifest.xml b/libraries/elementresources/src/main/AndroidManifest.xml
index e100076157..19db0c3d57 100644
--- a/libraries/elementresources/src/main/AndroidManifest.xml
+++ b/libraries/elementresources/src/main/AndroidManifest.xml
@@ -1,4 +1,20 @@
+
+
diff --git a/libraries/elementresources/src/main/res/values/colors.xml b/libraries/elementresources/src/main/res/values/colors.xml
index 9d8645a707..e25bea60b0 100644
--- a/libraries/elementresources/src/main/res/values/colors.xml
+++ b/libraries/elementresources/src/main/res/values/colors.xml
@@ -1,4 +1,20 @@
+
+
diff --git a/libraries/elementresources/src/main/res/values/dimens.xml b/libraries/elementresources/src/main/res/values/dimens.xml
index 1450693f76..274b9336fc 100644
--- a/libraries/elementresources/src/main/res/values/dimens.xml
+++ b/libraries/elementresources/src/main/res/values/dimens.xml
@@ -1,4 +1,20 @@
+
+
diff --git a/libraries/elementresources/src/main/res/values/dimens_font.xml b/libraries/elementresources/src/main/res/values/dimens_font.xml
index ad8f012a16..4d7c1772f2 100644
--- a/libraries/elementresources/src/main/res/values/dimens_font.xml
+++ b/libraries/elementresources/src/main/res/values/dimens_font.xml
@@ -1,4 +1,20 @@
+
+
24sp
diff --git a/libraries/elementresources/src/main/res/values/palette.xml b/libraries/elementresources/src/main/res/values/palette.xml
index 73ac768919..4652df3f31 100644
--- a/libraries/elementresources/src/main/res/values/palette.xml
+++ b/libraries/elementresources/src/main/res/values/palette.xml
@@ -1,4 +1,20 @@
+
+
+
+
diff --git a/libraries/elementresources/src/main/res/values/styles_bottom_sheet.xml b/libraries/elementresources/src/main/res/values/styles_bottom_sheet.xml
index a48071cb71..12ecc37b82 100644
--- a/libraries/elementresources/src/main/res/values/styles_bottom_sheet.xml
+++ b/libraries/elementresources/src/main/res/values/styles_bottom_sheet.xml
@@ -1,4 +1,20 @@
+
+