Browse Source

Migrate BugReport and CrashDetection to new architecture

feature/bma/flipper
ganfra 2 years ago
parent
commit
877ffd0d14
  1. 5
      app/src/main/java/io/element/android/x/node/RootFlowNode.kt
  2. 1
      features/rageshake/build.gradle.kts
  3. 11
      features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportEvents.kt
  4. 127
      features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportPresenter.kt
  5. 35
      features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportState.kt
  6. 79
      features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportView.kt
  7. 175
      features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportViewModel.kt
  8. 6
      features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionEvents.kt
  9. 38
      features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionPresenter.kt
  10. 23
      features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionScreen.kt
  11. 6
      features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionState.kt
  12. 65
      features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionViewModel.kt

5
app/src/main/java/io/element/android/x/node/RootFlowNode.kt

@ -8,7 +8,6 @@ import androidx.compose.runtime.Composable @@ -8,7 +8,6 @@ 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
@ -27,14 +26,10 @@ import com.bumble.appyx.core.node.ParentNode @@ -27,14 +26,10 @@ 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 com.bumble.appyx.navmodel.backstack.operation.replace
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

1
features/rageshake/build.gradle.kts

@ -20,6 +20,7 @@ plugins { @@ -20,6 +20,7 @@ plugins {
id("io.element.android-compose-library")
alias(libs.plugins.ksp)
alias(libs.plugins.anvil)
id("kotlin-parcelize")
}
android {

11
features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportEvents.kt

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
package io.element.android.x.features.rageshake.bugreport
sealed interface BugReportEvents {
object SendBugReport : BugReportEvents
object ResetAll: BugReportEvents
data class SetDescription(val description: String): BugReportEvents
data class SetSendLog(val sendLog: Boolean): BugReportEvents
data class SetSendCrashLog(val sendCrashlog: Boolean): BugReportEvents
data class SetCanContact(val canContact: Boolean): BugReportEvents
data class SetSendScreenshot(val sendScreenshot: Boolean) : BugReportEvents
}

127
features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportPresenter.kt

@ -0,0 +1,127 @@ @@ -0,0 +1,127 @@
package io.element.android.x.features.rageshake.bugreport
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import io.element.android.x.architecture.Async
import io.element.android.x.architecture.Presenter
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.flow.Flow
import kotlinx.coroutines.launch
import javax.inject.Inject
class BugReportPresenter @Inject constructor(
private val bugReporter: BugReporter,
private val crashDataStore: CrashDataStore,
private val screenshotHolder: ScreenshotHolder,
private val appCoroutineScope: CoroutineScope,
) : Presenter<BugReportState, BugReportEvents> {
private class BugReporterUploadListener(
private val sendingProgress: MutableState<Float>,
private val sendingAction: MutableState<Async<Unit>>
) : BugReporter.IMXBugReportListener {
override fun onUploadCancelled() {
sendingProgress.value = 0f
sendingAction.value = Async.Uninitialized
}
override fun onUploadFailed(reason: String?) {
sendingProgress.value = 0f
sendingAction.value = Async.Failure(Exception(reason))
}
override fun onProgress(progress: Int) {
sendingProgress.value = progress.toFloat() / 100
sendingAction.value = Async.Loading()
}
override fun onUploadSucceed(reportUrl: String?) {
sendingProgress.value = 0f
sendingAction.value = Async.Success(Unit)
}
}
@Composable
override fun present(events: Flow<BugReportEvents>): BugReportState {
val crashInfo: String by crashDataStore
.crashInfo()
.collectAsState(initial = "")
val sendingProgress = remember {
mutableStateOf(0f)
}
val sendingAction: MutableState<Async<Unit>> = remember {
mutableStateOf(Async.Uninitialized)
}
val formState: MutableState<BugReportFormState> = rememberSaveable {
mutableStateOf(BugReportFormState.Default)
}
val uploadListener = BugReporterUploadListener(sendingProgress, sendingAction)
val state = BugReportState(
hasCrashLogs = crashInfo.isNotEmpty(),
sendingProgress = sendingProgress.value,
sending = sendingAction.value
)
LaunchedEffect(Unit) {
events.collect { event ->
when (event) {
BugReportEvents.SendBugReport -> appCoroutineScope.sendBugReport(state, uploadListener)
BugReportEvents.ResetAll -> appCoroutineScope.resetAll()
is BugReportEvents.SetDescription -> updateFormState(formState) {
copy(description = event.description)
}
is BugReportEvents.SetCanContact -> updateFormState(formState) {
copy(canContact = event.canContact)
}
is BugReportEvents.SetSendCrashLog -> updateFormState(formState) {
copy(sendCrashLogs = event.sendCrashlog)
}
is BugReportEvents.SetSendLog -> updateFormState(formState) {
copy(sendLogs = event.sendLog)
}
is BugReportEvents.SetSendScreenshot -> updateFormState(formState) {
copy(sendScreenshot = event.sendScreenshot)
}
}
}
}
return state
}
private fun updateFormState(formState: MutableState<BugReportFormState>, operation: BugReportFormState.() -> BugReportFormState) {
formState.value = operation(formState.value)
}
private fun CoroutineScope.sendBugReport(state: BugReportState, listener: BugReporter.IMXBugReportListener) = launch {
bugReporter.sendBugReport(
coroutineScope = this,
reportType = ReportType.BUG_REPORT,
withDevicesLogs = state.formState.sendLogs,
withCrashLogs = state.hasCrashLogs && state.formState.sendCrashLogs,
withKeyRequestHistory = false,
withScreenshot = state.formState.sendScreenshot,
theBugDescription = state.formState.description,
serverVersion = "",
canContact = state.formState.canContact,
customFields = emptyMap(),
listener = listener
)
}
private fun CoroutineScope.resetAll() = launch {
screenshotHolder.reset()
crashDataStore.reset()
VectorFileLogger.getFromTimber().reset()
}
}

35
features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportViewState.kt → features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportState.kt

@ -16,30 +16,37 @@ @@ -16,30 +16,37 @@
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
import android.os.Parcelable
import io.element.android.x.architecture.Async
import kotlinx.parcelize.Parcelize
data class BugReportViewState(
data class BugReportState(
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<Unit> = Uninitialized,
) : MavericksState {
val sending: Async<Unit> = Async.Uninitialized,
) {
val submitEnabled =
formState.description.length > 10 && sending !is Loading
formState.description.length > 10 && sending !is Async.Loading
}
@Parcelize
data class BugReportFormState(
val description: String,
) {
val sendLogs: Boolean,
val sendCrashLogs: Boolean,
val canContact: Boolean,
val sendScreenshot: Boolean
): Parcelable {
companion object {
val Default = BugReportFormState("")
val Default = BugReportFormState(
description = "",
sendLogs = true,
sendCrashLogs = true,
canContact = false,
sendScreenshot = false
)
}
}

79
features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportScreen.kt → features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportView.kt

@ -37,6 +37,7 @@ import androidx.compose.material3.Surface @@ -37,6 +37,7 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@ -50,48 +51,17 @@ import androidx.compose.ui.unit.dp @@ -50,48 +51,17 @@ 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.architecture.Async
import io.element.android.x.core.compose.LogCompositions
import io.element.android.x.core.compose.textFieldState
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,
fun BugReportView(
state: BugReportState,
modifier: Modifier = Modifier,
onDescriptionChanged: (String) -> Unit = {},
onSetSendLog: (Boolean) -> Unit = {},
@ -102,6 +72,10 @@ fun BugReportContent( @@ -102,6 +72,10 @@ fun BugReportContent(
onFailureDialogClosed: () -> Unit = { },
onDone: () -> Unit = { },
) {
LogCompositions(tag = "Rageshake", msg = "Root")
if (state.sending is Async.Success) {
onDone()
}
Surface(
modifier = modifier,
color = MaterialTheme.colorScheme.background,
@ -120,8 +94,8 @@ fun BugReportContent( @@ -120,8 +94,8 @@ fun BugReportContent(
)
.padding(horizontal = 16.dp),
) {
val isError = state.sending is Fail
val isFormEnabled = state.sending !is Loading
val isError = state.sending is Async.Failure
val isFormEnabled = state.sending !is Async.Loading
// Title
Text(
text = stringResource(id = ElementR.string.send_bug_report),
@ -140,11 +114,12 @@ fun BugReportContent( @@ -140,11 +114,12 @@ fun BugReportContent(
.padding(horizontal = 16.dp, vertical = 16.dp),
fontSize = 16.sp,
)
var descriptionFieldState by textFieldState(stateValue = state.formState.description)
Column(
// modifier = Modifier.weight(1f),
) {
OutlinedTextField(
value = formState.description,
value = descriptionFieldState,
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
@ -155,7 +130,10 @@ fun BugReportContent( @@ -155,7 +130,10 @@ fun BugReportContent(
supportingText = {
Text(text = stringResource(id = ElementR.string.send_bug_report_description_in_english))
},
onValueChange = onDescriptionChanged,
onValueChange = {
descriptionFieldState = it
onDescriptionChanged(it)
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Next
@ -164,33 +142,33 @@ fun BugReportContent( @@ -164,33 +142,33 @@ fun BugReportContent(
)
}
LabelledCheckbox(
checked = state.sendLogs,
checked = state.formState.sendLogs,
onCheckedChange = onSetSendLog,
enabled = isFormEnabled,
text = stringResource(id = ElementR.string.send_bug_report_include_logs)
)
if (state.hasCrashLogs) {
LabelledCheckbox(
checked = state.sendCrashLogs,
checked = state.formState.sendCrashLogs,
onCheckedChange = onSetSendCrashLog,
enabled = isFormEnabled,
text = stringResource(id = ElementR.string.send_bug_report_include_crash_logs)
)
}
LabelledCheckbox(
checked = state.canContact,
checked = state.formState.canContact,
onCheckedChange = onSetCanContact,
enabled = isFormEnabled,
text = stringResource(id = ElementR.string.you_may_contact_me)
)
if (state.screenshotUri != null) {
LabelledCheckbox(
checked = state.sendScreenshot,
checked = state.formState.sendScreenshot,
onCheckedChange = onSetSendScreenshot,
enabled = isFormEnabled,
text = stringResource(id = ElementR.string.send_bug_report_include_screenshot)
)
if (state.sendScreenshot) {
if (state.formState.sendScreenshot) {
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center
@ -219,18 +197,18 @@ fun BugReportContent( @@ -219,18 +197,18 @@ fun BugReportContent(
}
}
when (state.sending) {
Uninitialized -> Unit
is Loading -> {
Async.Uninitialized -> Unit
is Async.Loading -> {
CircularProgressIndicator(
progress = state.sendingProgress,
modifier = Modifier.align(Alignment.Center)
)
}
is Fail -> ErrorDialog(
is Async.Failure -> ErrorDialog(
content = state.sending.error.toString(),
onDismiss = onFailureDialogClosed,
)
is Success -> onDone()
is Async.Success -> onDone()
}
}
}
@ -240,9 +218,8 @@ fun BugReportContent( @@ -240,9 +218,8 @@ fun BugReportContent(
@Preview
fun BugReportContentPreview() {
ElementXTheme(darkTheme = false) {
BugReportContent(
state = BugReportViewState(),
formState = BugReportFormState.Default
BugReportView(
state = BugReportState(),
)
}
}

175
features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportViewModel.kt

@ -1,175 +0,0 @@ @@ -1,175 +0,0 @@
/*
* 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<BugReportViewState>(initialState) {
companion object :
MavericksViewModelFactory<BugReportViewModel, BugReportViewState> 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) }
}

6
features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionEvents.kt

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
package io.element.android.x.features.rageshake.crash.ui
sealed interface CrashDetectionEvents {
object ResetAll : CrashDetectionEvents
object ResetAppHasCrashed : CrashDetectionEvents
}

38
features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionPresenter.kt

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
package io.element.android.x.features.rageshake.crash.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import io.element.android.x.architecture.Presenter
import io.element.android.x.features.rageshake.crash.CrashDataStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import javax.inject.Inject
class CrashDetectionPresenter @Inject constructor(private val crashDataStore: CrashDataStore) : Presenter<CrashDetectionState, CrashDetectionEvents> {
@Composable
override fun present(events: Flow<CrashDetectionEvents>): CrashDetectionState {
val crashDetected = crashDataStore.appHasCrashed().collectAsState(initial = false)
LaunchedEffect(Unit) {
events.collect { event ->
when (event) {
CrashDetectionEvents.ResetAll -> resetAll()
CrashDetectionEvents.ResetAppHasCrashed -> resetAppHasCrashed()
}
}
}
return CrashDetectionState(
crashDetected = crashDetected.value
)
}
private fun CoroutineScope.resetAppHasCrashed() = launch {
crashDataStore.resetAppHasCrashed()
}
fun CoroutineScope.resetAll() = launch {
crashDataStore.reset()
}
}

23
features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionScreen.kt

@ -17,40 +17,33 @@ @@ -17,40 +17,33 @@
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(),
fun CrashDetectionView(
state: CrashDetectionState,
onOpenBugReport: () -> Unit = { },
onPopupDismissed: () -> 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,
onYesClicked = onOpenBugReport,
onNoClicked = onPopupDismissed,
onDismiss = onPopupDismissed,
)
}
}
@Composable
fun CrashDetectionContent(
state: CrashDetectionViewState,
state: CrashDetectionState,
onNoClicked: () -> Unit = { },
onYesClicked: () -> Unit = { },
onDismiss: () -> Unit = { },
@ -71,7 +64,7 @@ fun CrashDetectionContent( @@ -71,7 +64,7 @@ fun CrashDetectionContent(
fun CrashDetectionContentPreview() {
ElementXTheme {
CrashDetectionContent(
state = CrashDetectionViewState()
state = CrashDetectionState()
)
}
}

6
features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionViewState.kt → features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionState.kt

@ -16,8 +16,6 @@ @@ -16,8 +16,6 @@
package io.element.android.x.features.rageshake.crash.ui
import com.airbnb.mvrx.MavericksState
data class CrashDetectionViewState(
data class CrashDetectionState(
val crashDetected: Boolean = false,
) : MavericksState
)

65
features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionViewModel.kt

@ -1,65 +0,0 @@ @@ -1,65 +0,0 @@
/*
* 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<CrashDetectionViewState>(initialState) {
companion object :
MavericksViewModelFactory<CrashDetectionViewModel, CrashDetectionViewState> 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()
}
}
}
Loading…
Cancel
Save