From 56e54bb172112f485ea42370fa72cb3252c29352 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 10 Jan 2023 21:18:16 +0100 Subject: [PATCH] Continue migrating BugReport/Rageshake/Crash screens --- .../java/io/element/android/x/MainActivity.kt | 3 +- .../io/element/android/x/di/AppBindings.kt | 2 + .../android/x/node/LoggedInFlowNode.kt | 3 +- .../io/element/android/x/node/RootFlowNode.kt | 113 +++++++++++------- .../io/element/android/x/root/RootEvents.kt | 14 +++ .../element/android/x/root/RootPresenter.kt | 62 ++++++++++ .../io/element/android/x/root/RootState.kt | 15 +++ .../io/element/android/x/root/RootView.kt | 55 +++++++++ .../preferences/PreferencesFlowNode.kt | 9 +- .../preferences/root/PreferencesRootNode.kt | 12 +- .../rageshake/bugreport/BugReportNode.kt | 75 ++++++++++++ .../rageshake/bugreport/BugReportPresenter.kt | 26 +++- .../rageshake/bugreport/BugReportState.kt | 3 +- .../crash/ui/CrashDetectionEvents.kt | 2 +- .../crash/ui/CrashDetectionPresenter.kt | 4 +- .../detection/RageshakeDetectionPresenter.kt | 26 ++-- .../detection/RageshakeDetectionState.kt | 2 + .../detection/RageshakeDetectionView.kt | 12 +- .../RageshakePreferencesPresenter.kt | 3 +- .../rageshake/screenshot/ScreenshotHolder.kt | 3 + .../element/android/x/architecture/Async.kt | 2 + 21 files changed, 366 insertions(+), 80 deletions(-) create mode 100644 app/src/main/java/io/element/android/x/root/RootEvents.kt create mode 100644 app/src/main/java/io/element/android/x/root/RootPresenter.kt create mode 100644 app/src/main/java/io/element/android/x/root/RootState.kt create mode 100644 app/src/main/java/io/element/android/x/root/RootView.kt create mode 100644 features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportNode.kt 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 33eac75262..1fd827be85 100644 --- a/app/src/main/java/io/element/android/x/MainActivity.kt +++ b/app/src/main/java/io/element/android/x/MainActivity.kt @@ -49,7 +49,8 @@ class MainActivity : NodeComponentActivity() { buildContext = it, appComponentOwner = applicationContext as DaggerComponentOwner, matrix = appBindings.matrix(), - sessionComponentsOwner = appBindings.sessionComponentsOwner() + sessionComponentsOwner = appBindings.sessionComponentsOwner(), + rootPresenter = appBindings.rootPresenter() ) } } 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 f330346e80..28efc08ba8 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 @@ -19,11 +19,13 @@ package io.element.android.x.di import com.squareup.anvil.annotations.ContributesTo import io.element.android.x.matrix.Matrix import io.element.android.x.matrix.ui.MatrixUi +import io.element.android.x.root.RootPresenter import kotlinx.coroutines.CoroutineScope @ContributesTo(AppScope::class) interface AppBindings { fun coroutineScope(): CoroutineScope + fun rootPresenter(): RootPresenter fun matrix(): Matrix fun matrixUi(): MatrixUi fun sessionComponentsOwner(): SessionComponentsOwner diff --git a/app/src/main/java/io/element/android/x/node/LoggedInFlowNode.kt b/app/src/main/java/io/element/android/x/node/LoggedInFlowNode.kt index 92fe956141..94fd29807b 100644 --- a/app/src/main/java/io/element/android/x/node/LoggedInFlowNode.kt +++ b/app/src/main/java/io/element/android/x/node/LoggedInFlowNode.kt @@ -22,6 +22,7 @@ import kotlinx.parcelize.Parcelize class LoggedInFlowNode( buildContext: BuildContext, val sessionId: SessionId, + private val onOpenBugReport: () -> Unit, private val backstack: BackStack = BackStack( initialElement = NavTarget.RoomList, savedStateMap = buildContext.savedStateMap, @@ -64,7 +65,7 @@ class LoggedInFlowNode( ) } NavTarget.Settings -> { - PreferencesFlowNode(buildContext) + PreferencesFlowNode(buildContext, onOpenBugReport) } } } 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 aea7eb1a68..78ae309341 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 @@ -5,17 +5,12 @@ 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.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.core.content.ContextCompat.startActivity import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import com.airbnb.android.showkase.models.Showkase import com.bumble.appyx.core.children.whenChildAttached import com.bumble.appyx.core.clienthelper.interactor.Interactor import com.bumble.appyx.core.composable.Children @@ -26,13 +21,19 @@ import com.bumble.appyx.core.node.ParentNode import com.bumble.appyx.core.node.node import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.newRoot -import io.element.android.x.BuildConfig -import io.element.android.x.component.ShowkaseButton +import com.bumble.appyx.navmodel.backstack.operation.pop +import com.bumble.appyx.navmodel.backstack.operation.push +import io.element.android.x.architecture.createNode +import io.element.android.x.architecture.presenterConnector import io.element.android.x.core.di.DaggerComponentOwner +import io.element.android.x.core.screenshot.ImageResult import io.element.android.x.di.SessionComponentsOwner -import io.element.android.x.getBrowserIntent +import io.element.android.x.features.rageshake.bugreport.BugReportNode import io.element.android.x.matrix.Matrix import io.element.android.x.matrix.core.SessionId +import io.element.android.x.root.RootEvents +import io.element.android.x.root.RootPresenter +import io.element.android.x.root.RootView import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -64,6 +65,7 @@ class RootFlowNode( private val appComponentOwner: DaggerComponentOwner, private val matrix: Matrix, private val sessionComponentsOwner: SessionComponentsOwner, + rootPresenter: RootPresenter ) : ParentNode( navModel = backstack, @@ -73,10 +75,20 @@ class RootFlowNode( DaggerComponentOwner by appComponentOwner { + private val presenterConnector = presenterConnector(rootPresenter) + init { Timber.v("Init") lifecycle.subscribe( onCreate = { Timber.v("OnCreate") }, + onResume = { + Timber.v("OnResume") + presenterConnector.emitEvent(RootEvents.StartRageshakeDetection) + }, + onPause = { + Timber.v("OnPause") + presenterConnector.emitEvent(RootEvents.StopRageshakeDetection) + }, onDestroy = { Timber.v("OnDestroy") } ) } @@ -85,7 +97,7 @@ class RootFlowNode( matrix.isLoggedIn() .distinctUntilChanged() .onEach { isLoggedIn -> - Timber.v("IsLoggedIn") + Timber.v("isLoggedIn=$isLoggedIn") if (isLoggedIn) { val matrixClient = matrix.restoreSession() if (matrixClient == null) { @@ -102,43 +114,50 @@ class RootFlowNode( .launchIn(lifecycleScope) } + private fun hideShowkaseButton() { + presenterConnector.emitEvent(RootEvents.HideShowkaseButton) + } + + private fun onOpenBugReport() { + presenterConnector.emitEvent(RootEvents.ResetAppHasCrashed) + backstack.push(NavTarget.BugReport) + } + + private fun onCrashDetectedDismissed() { + presenterConnector.emitEvent(RootEvents.ResetAllCrashData) + } + + private fun onDismissRageshake() { + presenterConnector.emitEvent(RootEvents.DismissRageshake) + } + + private fun onDisableRageshake() { + presenterConnector.emitEvent(RootEvents.DisableRageshake) + } + @Composable override fun View(modifier: Modifier) { - var isShowkaseButtonVisible by remember { mutableStateOf(BuildConfig.DEBUG) } - Box( - modifier = modifier - .fillMaxSize(), - contentAlignment = Alignment.TopCenter, + val state by presenterConnector.stateFlow.collectAsState() + RootView( + state = state, + onHideShowkaseClicked = this::hideShowkaseButton, + onOpenBugReport = this::onOpenBugReport, + onCrashDetectedDismissed = this::onCrashDetectedDismissed, + onDisableRageshake = this::onDisableRageshake, + onDismissRageshake = this::onDismissRageshake, + onScreenshotTaken = this::onScreenshotTaken ) { Children(navModel = backstack) - val context = LocalContext.current - ShowkaseButton( - isVisible = isShowkaseButtonVisible, - 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 } - ) - } - */ + private fun onScreenshotTaken(imageResult: ImageResult) { + presenterConnector.emitEvent(RootEvents.ProcessScreenshot(imageResult)) + } + + private val bugReportNodeCallback = object : BugReportNode.Callback { + override fun onBugReportSent() { + backstack.pop() } } @@ -151,12 +170,19 @@ class RootFlowNode( @Parcelize data class LoggedInFlow(val sessionId: SessionId) : NavTarget + + @Parcelize + object BugReport : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { is NavTarget.LoggedInFlow -> { - LoggedInFlowNode(buildContext, navTarget.sessionId) + LoggedInFlowNode( + buildContext = buildContext, + sessionId = navTarget.sessionId, + onOpenBugReport = this::onOpenBugReport + ) } NavTarget.NotLoggedInFlow -> NotLoggedInFlowNode(buildContext) NavTarget.SplashScreen -> node(buildContext) { @@ -164,6 +190,7 @@ class RootFlowNode( CircularProgressIndicator() } } + NavTarget.BugReport -> createNode(buildContext, plugins = listOf(bugReportNodeCallback)) } } } diff --git a/app/src/main/java/io/element/android/x/root/RootEvents.kt b/app/src/main/java/io/element/android/x/root/RootEvents.kt new file mode 100644 index 0000000000..7a88722ce6 --- /dev/null +++ b/app/src/main/java/io/element/android/x/root/RootEvents.kt @@ -0,0 +1,14 @@ +package io.element.android.x.root + +import io.element.android.x.core.screenshot.ImageResult + +sealed interface RootEvents { + data class ProcessScreenshot(val imageResult: ImageResult) : RootEvents + object HideShowkaseButton: RootEvents + object ResetAllCrashData : RootEvents + object ResetAppHasCrashed: RootEvents + object DisableRageshake: RootEvents + object DismissRageshake: RootEvents + object StartRageshakeDetection: RootEvents + object StopRageshakeDetection: RootEvents +} diff --git a/app/src/main/java/io/element/android/x/root/RootPresenter.kt b/app/src/main/java/io/element/android/x/root/RootPresenter.kt new file mode 100644 index 0000000000..45a28f2ba4 --- /dev/null +++ b/app/src/main/java/io/element/android/x/root/RootPresenter.kt @@ -0,0 +1,62 @@ +package io.element.android.x.root + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import io.element.android.x.architecture.Presenter +import io.element.android.x.architecture.SharedFlowHolder +import io.element.android.x.features.rageshake.bugreport.BugReportEvents +import io.element.android.x.features.rageshake.bugreport.BugReportPresenter +import io.element.android.x.features.rageshake.crash.ui.CrashDetectionEvents +import io.element.android.x.features.rageshake.crash.ui.CrashDetectionPresenter +import io.element.android.x.features.rageshake.detection.RageshakeDetectionEvents +import io.element.android.x.features.rageshake.detection.RageshakeDetectionPresenter +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class RootPresenter @Inject constructor( + private val bugReportPresenter: BugReportPresenter, + private val crashDetectionPresenter: CrashDetectionPresenter, + private val rageshakeDetectionPresenter: RageshakeDetectionPresenter, +) : Presenter { + + private val rageshakeDetectionEventsFlow = SharedFlowHolder() + private val bugReporterEventsFlow = SharedFlowHolder() + private val crashDetectionEventsFlow = SharedFlowHolder() + + @Composable + override fun present(events: Flow): RootState { + val isBugReportVisible = rememberSaveable { + mutableStateOf(false) + } + val isShowkaseButtonVisible = rememberSaveable { + mutableStateOf(true) + } + val rageshakeDetectionState = rageshakeDetectionPresenter.present(events = rageshakeDetectionEventsFlow.asSharedFlow()) + val crashDetectionState = crashDetectionPresenter.present(events = crashDetectionEventsFlow.asSharedFlow()) + val bugReportState = bugReportPresenter.present(events = bugReporterEventsFlow.asSharedFlow()) + + LaunchedEffect(Unit) { + events.collect { event -> + when (event) { + RootEvents.HideShowkaseButton -> isShowkaseButtonVisible.value = false + RootEvents.ResetAllCrashData -> crashDetectionEventsFlow.emit(CrashDetectionEvents.ResetAllCrashData) + RootEvents.ResetAppHasCrashed -> crashDetectionEventsFlow.emit(CrashDetectionEvents.ResetAppHasCrashed) + RootEvents.DisableRageshake -> rageshakeDetectionEventsFlow.emit(RageshakeDetectionEvents.Disable) + RootEvents.DismissRageshake -> rageshakeDetectionEventsFlow.emit(RageshakeDetectionEvents.Dismiss) + RootEvents.StartRageshakeDetection -> rageshakeDetectionEventsFlow.emit(RageshakeDetectionEvents.StartDetection) + RootEvents.StopRageshakeDetection -> rageshakeDetectionEventsFlow.emit(RageshakeDetectionEvents.StopDetection) + is RootEvents.ProcessScreenshot -> rageshakeDetectionEventsFlow.emit(RageshakeDetectionEvents.ProcessScreenshot(event.imageResult)) + } + } + } + return RootState( + isBugReportVisible = isBugReportVisible.value, + isShowkaseButtonVisible = isShowkaseButtonVisible.value, + rageshakeDetectionState = rageshakeDetectionState, + crashDetectionState = crashDetectionState, + bugReportState = bugReportState + ) + } +} diff --git a/app/src/main/java/io/element/android/x/root/RootState.kt b/app/src/main/java/io/element/android/x/root/RootState.kt new file mode 100644 index 0000000000..a146b93322 --- /dev/null +++ b/app/src/main/java/io/element/android/x/root/RootState.kt @@ -0,0 +1,15 @@ +package io.element.android.x.root + +import androidx.compose.runtime.Stable +import io.element.android.x.features.rageshake.bugreport.BugReportState +import io.element.android.x.features.rageshake.crash.ui.CrashDetectionState +import io.element.android.x.features.rageshake.detection.RageshakeDetectionState + +@Stable +data class RootState( + val isBugReportVisible: Boolean, + val isShowkaseButtonVisible: Boolean, + val rageshakeDetectionState: RageshakeDetectionState, + val crashDetectionState: CrashDetectionState, + val bugReportState: BugReportState +) diff --git a/app/src/main/java/io/element/android/x/root/RootView.kt b/app/src/main/java/io/element/android/x/root/RootView.kt new file mode 100644 index 0000000000..ff0effef57 --- /dev/null +++ b/app/src/main/java/io/element/android/x/root/RootView.kt @@ -0,0 +1,55 @@ +package io.element.android.x.root + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.core.content.ContextCompat +import com.airbnb.android.showkase.models.Showkase +import io.element.android.x.component.ShowkaseButton +import io.element.android.x.core.screenshot.ImageResult +import io.element.android.x.features.rageshake.crash.ui.CrashDetectionView +import io.element.android.x.features.rageshake.detection.RageshakeDetectionView +import io.element.android.x.getBrowserIntent + +@Composable +fun RootView( + state: RootState, + modifier: Modifier = Modifier, + onHideShowkaseClicked: () -> Unit = { }, + onOpenBugReport: () -> Unit = {}, + onCrashDetectedDismissed: () -> Unit = {}, + onDisableRageshake: () -> Unit = {}, + onDismissRageshake: () -> Unit = {}, + onScreenshotTaken: (ImageResult) -> Unit = {}, + children: @Composable BoxScope.() -> Unit, +) { + Box( + modifier = modifier + .fillMaxSize(), + contentAlignment = Alignment.TopCenter, + ) { + children() + val context = LocalContext.current + ShowkaseButton( + isVisible = state.isShowkaseButtonVisible, + onCloseClicked = onHideShowkaseClicked, + onClick = { ContextCompat.startActivity(context, Showkase.getBrowserIntent(context), null) } + ) + RageshakeDetectionView( + state = state.rageshakeDetectionState, + onOpenBugReport = onOpenBugReport, + onDisableClicked = onDisableRageshake, + onNoClicked = onDismissRageshake, + onScreenshotTaken = onScreenshotTaken + ) + CrashDetectionView( + state = state.crashDetectionState, + onOpenBugReport = onOpenBugReport, + onPopupDismissed = onCrashDetectedDismissed + ) + } +} diff --git a/features/preferences/src/main/java/io/element/android/x/features/preferences/PreferencesFlowNode.kt b/features/preferences/src/main/java/io/element/android/x/features/preferences/PreferencesFlowNode.kt index 10b6c141c1..9a69294f3b 100644 --- a/features/preferences/src/main/java/io/element/android/x/features/preferences/PreferencesFlowNode.kt +++ b/features/preferences/src/main/java/io/element/android/x/features/preferences/PreferencesFlowNode.kt @@ -14,6 +14,7 @@ import kotlinx.parcelize.Parcelize class PreferencesFlowNode( buildContext: BuildContext, + private val onOpenBugReport: () -> Unit, private val backstack: BackStack = BackStack( initialElement = NavTarget.Root, savedStateMap = buildContext.savedStateMap, @@ -23,6 +24,12 @@ class PreferencesFlowNode( buildContext = buildContext ) { + private val preferencesRootNodeCallback = object : PreferencesRootNode.Callback { + override fun onOpenBugReport() { + onOpenBugReport.invoke() + } + } + sealed interface NavTarget : Parcelable { @Parcelize object Root : NavTarget @@ -30,7 +37,7 @@ class PreferencesFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { - NavTarget.Root -> createNode(buildContext) + NavTarget.Root -> createNode(buildContext, plugins = listOf(preferencesRootNodeCallback)) } } diff --git a/features/preferences/src/main/java/io/element/android/x/features/preferences/root/PreferencesRootNode.kt b/features/preferences/src/main/java/io/element/android/x/features/preferences/root/PreferencesRootNode.kt index 04dc1a6fb0..78db0459a2 100644 --- a/features/preferences/src/main/java/io/element/android/x/features/preferences/root/PreferencesRootNode.kt +++ b/features/preferences/src/main/java/io/element/android/x/features/preferences/root/PreferencesRootNode.kt @@ -7,6 +7,7 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.x.anvilannotations.ContributesNode @@ -20,6 +21,10 @@ class PreferencesRootNode @AssistedInject constructor( private val presenter: PreferencesRootPresenter, ) : Node(buildContext, plugins = plugins) { + public interface Callback : Plugin { + fun onOpenBugReport() + } + private val presenterConnector = presenterConnector(presenter) private fun onLogoutClicked() { @@ -34,6 +39,10 @@ class PreferencesRootNode @AssistedInject constructor( presenterConnector.emitEvent(PreferencesRootEvents.SetRageshakeSensitivity(sensitivity)) } + private fun onOpenBugReport() { + plugins().forEach { it.onOpenBugReport() } + } + @Composable override fun View(modifier: Modifier) { val state by presenterConnector.stateFlow.collectAsState() @@ -42,7 +51,8 @@ class PreferencesRootNode @AssistedInject constructor( onLogoutClicked = this::onLogoutClicked, onBackPressed = this::navigateUp, onRageshakeEnabledChanged = this::onRageshakeEnabledChanged, - onRageshakeSensitivityChanged = this::onRageshakeSensitivityChanged + onRageshakeSensitivityChanged = this::onRageshakeSensitivityChanged, + onOpenRageShake = this::onOpenBugReport ) } } diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportNode.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportNode.kt new file mode 100644 index 0000000000..088179f939 --- /dev/null +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportNode.kt @@ -0,0 +1,75 @@ +package io.element.android.x.features.rageshake.bugreport + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.x.anvilannotations.ContributesNode +import io.element.android.x.architecture.presenterConnector +import io.element.android.x.di.AppScope + +@ContributesNode(AppScope::class) +class BugReportNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + presenter: BugReportPresenter, +) : Node(buildContext, plugins = plugins) { + + private val presenterConnector = presenterConnector(presenter) + + interface Callback : Plugin { + fun onBugReportSent() + } + + @Composable + override fun View(modifier: Modifier) { + val state by presenterConnector.stateFlow.collectAsState() + BugReportView( + state = state, + modifier = modifier, + onDescriptionChanged = this::onDescriptionChanged, + onSetSendLog = this::onSetSendLog, + onSetSendCrashLog = this::onSetSendCrashLog, + onSetCanContact = this::onSetCanContact, + onSetSendScreenshot = this::onSetSendScreenshot, + onSubmit = this::onSubmit, + onDone = this::onDone + ) + } + + private fun onDone() { + presenterConnector.emitEvent(BugReportEvents.ResetAll) + plugins().forEach { it.onBugReportSent() } + } + + private fun onSubmit() { + presenterConnector.emitEvent(BugReportEvents.SendBugReport) + } + + private fun onSetSendLog(sendLog: Boolean) { + presenterConnector.emitEvent(BugReportEvents.SetSendLog(sendLog)) + } + + private fun onSetSendCrashLog(sendCrashLog: Boolean) { + presenterConnector.emitEvent(BugReportEvents.SetSendCrashLog(sendCrashLog)) + } + + private fun onSetSendScreenshot(sendScreenshot: Boolean) { + presenterConnector.emitEvent(BugReportEvents.SetSendScreenshot(sendScreenshot)) + } + + private fun onSetCanContact(canContact: Boolean) { + presenterConnector.emitEvent(BugReportEvents.SetCanContact(canContact)) + } + + private fun onDescriptionChanged(description: String) { + presenterConnector.emitEvent(BugReportEvents.SetDescription(description)) + } +} + diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportPresenter.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportPresenter.kt index a4cbf027d8..4127395ce5 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportPresenter.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportPresenter.kt @@ -4,10 +4,12 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable +import androidx.core.net.toUri import io.element.android.x.architecture.Async import io.element.android.x.architecture.Presenter import io.element.android.x.features.rageshake.crash.CrashDataStore @@ -31,6 +33,7 @@ class BugReportPresenter @Inject constructor( private val sendingProgress: MutableState, private val sendingAction: MutableState> ) : BugReporter.IMXBugReportListener { + override fun onUploadCancelled() { sendingProgress.value = 0f sendingAction.value = Async.Uninitialized @@ -54,6 +57,11 @@ class BugReportPresenter @Inject constructor( @Composable override fun present(events: Flow): BugReportState { + val screenshotUri = rememberSaveable { + mutableStateOf( + screenshotHolder.getFile()?.toUri()?.toString() + ) + } val crashInfo: String by crashDataStore .crashInfo() .collectAsState(initial = "") @@ -64,15 +72,21 @@ class BugReportPresenter @Inject constructor( val sendingAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } - val formState: MutableState = rememberSaveable { + val formState: MutableState = remember { mutableStateOf(BugReportFormState.Default) } val uploadListener = BugReporterUploadListener(sendingProgress, sendingAction) - val state = BugReportState( - hasCrashLogs = crashInfo.isNotEmpty(), - sendingProgress = sendingProgress.value, - sending = sendingAction.value - ) + val state by remember { + derivedStateOf { + BugReportState( + hasCrashLogs = crashInfo.isNotEmpty(), + sendingProgress = sendingProgress.value, + sending = sendingAction.value, + formState = formState.value, + screenshotUri = screenshotUri.value + ) + } + } LaunchedEffect(Unit) { events.collect { event -> when (event) { diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportState.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportState.kt index e32f0c3abc..cc7fad74ca 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportState.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportState.kt @@ -38,8 +38,7 @@ data class BugReportFormState( val sendCrashLogs: Boolean, val canContact: Boolean, val sendScreenshot: Boolean - -): Parcelable { +) : Parcelable { companion object { val Default = BugReportFormState( description = "", diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionEvents.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionEvents.kt index e57a6de9bc..0017421c7f 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionEvents.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionEvents.kt @@ -1,6 +1,6 @@ package io.element.android.x.features.rageshake.crash.ui sealed interface CrashDetectionEvents { - object ResetAll : CrashDetectionEvents + object ResetAllCrashData : CrashDetectionEvents object ResetAppHasCrashed : CrashDetectionEvents } diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionPresenter.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionPresenter.kt index ae22cec08c..716bc19bf5 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionPresenter.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionPresenter.kt @@ -18,7 +18,7 @@ class CrashDetectionPresenter @Inject constructor(private val crashDataStore: Cr LaunchedEffect(Unit) { events.collect { event -> when (event) { - CrashDetectionEvents.ResetAll -> resetAll() + CrashDetectionEvents.ResetAllCrashData -> resetAll() CrashDetectionEvents.ResetAppHasCrashed -> resetAppHasCrashed() } } @@ -32,7 +32,7 @@ class CrashDetectionPresenter @Inject constructor(private val crashDataStore: Cr crashDataStore.resetAppHasCrashed() } - fun CoroutineScope.resetAll() = launch { + private fun CoroutineScope.resetAll() = launch { crashDataStore.reset() } } diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionPresenter.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionPresenter.kt index a9e14a476b..c3bd6b9ca7 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionPresenter.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionPresenter.kt @@ -13,7 +13,6 @@ import io.element.android.x.core.screenshot.ImageResult import io.element.android.x.features.rageshake.preferences.RageshakePreferencesEvents import io.element.android.x.features.rageshake.preferences.RageshakePreferencesPresenter 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.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -22,7 +21,6 @@ import timber.log.Timber import javax.inject.Inject class RageshakeDetectionPresenter @Inject constructor( - private val rageshakeDataStore: RageshakeDataStore, private val screenshotHolder: ScreenshotHolder, private val rageShake: RageShake, private val preferencesPresenter: RageshakePreferencesPresenter, @@ -42,12 +40,14 @@ class RageshakeDetectionPresenter @Inject constructor( val showDialog = rememberSaveable { mutableStateOf(false) } - val state = RageshakeDetectionState( - isStarted = isStarted.value, - takeScreenshot = takeScreenshot.value, - showDialog = showDialog.value, - preferenceState = preferencesState - ) + val state = remember(preferencesState, isStarted.value, takeScreenshot.value, showDialog.value) { + RageshakeDetectionState( + isStarted = isStarted.value, + takeScreenshot = takeScreenshot.value, + showDialog = showDialog.value, + preferenceState = preferencesState + ) + } LaunchedEffect(Unit) { events.collect { event -> when (event) { @@ -62,22 +62,18 @@ class RageshakeDetectionPresenter @Inject constructor( LaunchedEffect(preferencesState.sensitivity) { rageShake.setSensitivity(preferencesState.sensitivity) } - val shouldStart = remember { - derivedStateOf { - preferencesState.isEnabled && + val shouldStart = preferencesState.isEnabled && preferencesState.isSupported && isStarted.value && !takeScreenshot.value && !showDialog.value - } - } + LaunchedEffect(shouldStart) { - handleRageShake(shouldStart.value, state, takeScreenshot) + handleRageShake(shouldStart, state, takeScreenshot) } return state } - private fun handleRageShake(start: Boolean, state: RageshakeDetectionState, takeScreenshot: MutableState) { if (start) { rageShake.start(state.preferenceState.sensitivity) diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionState.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionState.kt index 247c81a2dc..f1a3f656cd 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionState.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionState.kt @@ -16,8 +16,10 @@ package io.element.android.x.features.rageshake.detection +import androidx.compose.runtime.Stable import io.element.android.x.features.rageshake.preferences.RageshakePreferencesState +@Stable data class RageshakeDetectionState( val takeScreenshot: Boolean = false, val showDialog: Boolean = false, diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionView.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionView.kt index 02273df57e..7de08d158c 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionView.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/detection/RageshakeDetectionView.kt @@ -34,9 +34,9 @@ import io.element.android.x.element.resources.R as ElementR fun RageshakeDetectionView( state: RageshakeDetectionState, onOpenBugReport: () -> Unit = { }, - onScreenshotTaken: (ImageResult) -> Unit, - onDisableClicked: () -> Unit, - onNoClicked: () -> Unit + onScreenshotTaken: (ImageResult) -> Unit = {}, + onDisableClicked: () -> Unit = {}, + onNoClicked: () -> Unit = {} ) { LogCompositions(tag = "Rageshake", msg = "RageshakeDetectionScreen") val context = LocalContext.current @@ -63,8 +63,10 @@ private fun TakeScreenshot( onScreenshotTaken: (ImageResult) -> Unit = {} ) { val view = LocalView.current - view.screenshot { - onScreenshotTaken(it) + LaunchedEffect(Unit) { + view.screenshot { + onScreenshotTaken(it) + } } } diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferencesPresenter.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferencesPresenter.kt index 1c56b42198..3012a6c32f 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferencesPresenter.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferencesPresenter.kt @@ -17,8 +17,7 @@ import javax.inject.Inject class RageshakePreferencesPresenter @Inject constructor( private val rageshake: RageShake, private val rageshakeDataStore: RageshakeDataStore, - - ) : Presenter { +) : Presenter { @Composable override fun present(events: Flow): RageshakePreferencesState { 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 index 1b028ffb2b..00f9d74e96 100644 --- 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 @@ -19,10 +19,13 @@ 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.AppScope import io.element.android.x.di.ApplicationContext +import io.element.android.x.di.SingleIn import java.io.File import javax.inject.Inject +@SingleIn(AppScope::class) class ScreenshotHolder @Inject constructor( @ApplicationContext private val context: Context, ) { diff --git a/libraries/architecture/src/main/java/io/element/android/x/architecture/Async.kt b/libraries/architecture/src/main/java/io/element/android/x/architecture/Async.kt index 73c0a81e3a..1e63d127fb 100644 --- a/libraries/architecture/src/main/java/io/element/android/x/architecture/Async.kt +++ b/libraries/architecture/src/main/java/io/element/android/x/architecture/Async.kt @@ -1,7 +1,9 @@ package io.element.android.x.architecture import androidx.compose.runtime.MutableState +import androidx.compose.runtime.Stable +@Stable sealed interface Async { object Uninitialized : Async data class Loading(val prevState: T? = null) : Async