Merge branch 'develop' into feature/fga/update_rust_sdk

This commit is contained in:
ganfra 2023-02-14 21:15:28 +01:00
commit 8bca19a3fa
341 changed files with 9655 additions and 2495 deletions

View file

@ -18,16 +18,15 @@ package io.element.android.x
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import com.bumble.appyx.core.integration.NodeHost
import com.bumble.appyx.core.integrationpoint.NodeComponentActivity
import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.designsystem.ElementXTheme
import io.element.android.libraries.designsystem.theme.ElementTheme
import io.element.android.libraries.di.DaggerComponentOwner
import io.element.android.x.di.AppBindings
import io.element.android.x.node.RootFlowNode
@ -41,10 +40,9 @@ class MainActivity : NodeComponentActivity() {
appBindings.matrixClientsHolder().restore(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
ElementXTheme {
Surface(
ElementTheme {
Box(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
NodeHost(integrationPoint = appyxIntegrationPoint) {
RootFlowNode(

View file

@ -20,13 +20,13 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.Text
@Composable
internal fun ShowkaseButton(

View file

@ -20,7 +20,6 @@ import com.squareup.anvil.annotations.ContributesTo
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.auth.MatrixAuthenticationService
import io.element.android.x.root.RootPresenter
import kotlinx.coroutines.CoroutineScope
@ContributesTo(AppScope::class)
interface AppBindings {

View file

@ -20,6 +20,8 @@ import android.content.Context
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.Provides
import io.element.android.features.rageshake.reporter.BugReporter
import io.element.android.features.rageshake.reporter.DefaultBugReporter
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
@ -58,4 +60,7 @@ object AppModule {
diffUpdateDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
)
}
@Provides
fun providesBugReporter(bugReporter: DefaultBugReporter): BugReporter = bugReporter
}

View file

@ -19,7 +19,6 @@ package io.element.android.x.node
import android.os.Parcelable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -37,6 +36,7 @@ import io.element.android.features.roomlist.RoomListNode
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.di.DaggerComponentOwner
import io.element.android.libraries.matrix.MatrixClient
import io.element.android.libraries.matrix.core.RoomId

View file

@ -19,7 +19,6 @@ package io.element.android.x.node
import android.os.Parcelable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -36,6 +35,7 @@ import com.bumble.appyx.navmodel.backstack.operation.push
import io.element.android.features.rageshake.bugreport.BugReportNode
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.di.DaggerComponentOwner
import io.element.android.libraries.matrix.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.core.SessionId

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.x.root
import android.graphics.Bitmap
import io.element.android.features.rageshake.screenshot.ScreenshotHolder
const val A_SCREENSHOT_URI = "file://content/uri"
// TODO Remove this duplicated class when we will rework modules.
class FakeScreenshotHolder(private val screenshotUri: String? = null) : ScreenshotHolder {
override fun writeBitmap(data: Bitmap) = Unit
override fun getFileUri() = screenshotUri
override fun reset() = Unit
}

View file

@ -0,0 +1,89 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.x.root
import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.rageshake.bugreport.BugReportPresenter
import io.element.android.features.rageshake.crash.ui.CrashDetectionPresenter
import io.element.android.features.rageshake.detection.RageshakeDetectionPresenter
import io.element.android.features.rageshake.preferences.RageshakePreferencesPresenter
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
class RootPresenterTest {
@Test
fun `present - initial state`() = runTest {
val presenter = createPresenter()
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.isShowkaseButtonVisible).isTrue()
}
}
@Test
fun `present - hide showkase button`() = runTest {
val presenter = createPresenter()
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.isShowkaseButtonVisible).isTrue()
initialState.eventSink.invoke(RootEvents.HideShowkaseButton)
assertThat(awaitItem().isShowkaseButtonVisible).isFalse()
}
}
private fun TestScope.createPresenter(): RootPresenter {
val crashDataStore = FakeCrashDataStore()
val rageshakeDataStore = FakeRageshakeDataStore()
val rageshake = FakeRageShake()
val screenshotHolder = FakeScreenshotHolder()
val bugReportPresenter = BugReportPresenter(
bugReporter = FakeBugReporter(),
crashDataStore = crashDataStore,
screenshotHolder = screenshotHolder,
appCoroutineScope = this,
)
val crashDetectionPresenter = CrashDetectionPresenter(
crashDataStore = crashDataStore
)
val rageshakeDetectionPresenter = RageshakeDetectionPresenter(
screenshotHolder = screenshotHolder,
rageShake = rageshake,
preferencesPresenter = RageshakePreferencesPresenter(
rageshake = rageshake,
rageshakeDataStore = rageshakeDataStore,
)
)
return RootPresenter(
bugReportPresenter = bugReportPresenter,
crashDetectionPresenter = crashDetectionPresenter,
rageshakeDetectionPresenter = rageshakeDetectionPresenter,
)
}
}