Continue migrating BugReport/Rageshake/Crash screens

This commit is contained in:
ganfra 2023-01-10 21:18:16 +01:00
parent c299ab4031
commit e56ba5e315
21 changed files with 366 additions and 80 deletions

View file

@ -14,6 +14,7 @@ import kotlinx.parcelize.Parcelize
class PreferencesFlowNode(
buildContext: BuildContext,
private val onOpenBugReport: () -> Unit,
private val backstack: BackStack<NavTarget> = 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<PreferencesRootNode>(buildContext)
NavTarget.Root -> createNode<PreferencesRootNode>(buildContext, plugins = listOf(preferencesRootNodeCallback))
}
}

View file

@ -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<Callback>().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
)
}
}

View file

@ -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<Plugin>,
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<Callback>().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))
}
}

View file

@ -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<Float>,
private val sendingAction: MutableState<Async<Unit>>
) : 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<BugReportEvents>): 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<Async<Unit>> = remember {
mutableStateOf(Async.Uninitialized)
}
val formState: MutableState<BugReportFormState> = rememberSaveable {
val formState: MutableState<BugReportFormState> = 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) {

View file

@ -38,8 +38,7 @@ data class BugReportFormState(
val sendCrashLogs: Boolean,
val canContact: Boolean,
val sendScreenshot: Boolean
): Parcelable {
) : Parcelable {
companion object {
val Default = BugReportFormState(
description = "",

View file

@ -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
}

View file

@ -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()
}
}

View file

@ -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<Boolean>) {
if (start) {
rageShake.start(state.preferenceState.sensitivity)

View file

@ -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,

View file

@ -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)
}
}
}

View file

@ -17,8 +17,7 @@ import javax.inject.Inject
class RageshakePreferencesPresenter @Inject constructor(
private val rageshake: RageShake,
private val rageshakeDataStore: RageshakeDataStore,
) : Presenter<RageshakePreferencesState, RageshakePreferencesEvents> {
) : Presenter<RageshakePreferencesState, RageshakePreferencesEvents> {
@Composable
override fun present(events: Flow<RageshakePreferencesEvents>): RageshakePreferencesState {

View file

@ -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,
) {