Use EventSink lambda in state instead of Flow in Presenter
This commit is contained in:
parent
56e54bb172
commit
1a0c9df1da
43 changed files with 277 additions and 490 deletions
|
|
@ -33,43 +33,12 @@ class BugReportNode @AssistedInject constructor(
|
|||
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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
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.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -18,7 +16,6 @@ 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
|
||||
|
||||
|
|
@ -27,7 +24,7 @@ class BugReportPresenter @Inject constructor(
|
|||
private val crashDataStore: CrashDataStore,
|
||||
private val screenshotHolder: ScreenshotHolder,
|
||||
private val appCoroutineScope: CoroutineScope,
|
||||
) : Presenter<BugReportState, BugReportEvents> {
|
||||
) : Presenter<BugReportState> {
|
||||
|
||||
private class BugReporterUploadListener(
|
||||
private val sendingProgress: MutableState<Float>,
|
||||
|
|
@ -56,7 +53,7 @@ class BugReportPresenter @Inject constructor(
|
|||
}
|
||||
|
||||
@Composable
|
||||
override fun present(events: Flow<BugReportEvents>): BugReportState {
|
||||
override fun present(): BugReportState {
|
||||
val screenshotUri = rememberSaveable {
|
||||
mutableStateOf(
|
||||
screenshotHolder.getFile()?.toUri()?.toString()
|
||||
|
|
@ -76,58 +73,54 @@ class BugReportPresenter @Inject constructor(
|
|||
mutableStateOf(BugReportFormState.Default)
|
||||
}
|
||||
val uploadListener = BugReporterUploadListener(sendingProgress, sendingAction)
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
|
||||
fun handleEvents(event: BugReportEvents) {
|
||||
when (event) {
|
||||
BugReportEvents.SendBugReport -> appCoroutineScope.sendBugReport(formState.value, crashInfo.isNotEmpty(), 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
|
||||
|
||||
return BugReportState(
|
||||
hasCrashLogs = crashInfo.isNotEmpty(),
|
||||
sendingProgress = sendingProgress.value,
|
||||
sending = sendingAction.value,
|
||||
formState = formState.value,
|
||||
screenshotUri = screenshotUri.value,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateFormState(formState: MutableState<BugReportFormState>, operation: BugReportFormState.() -> BugReportFormState) {
|
||||
formState.value = operation(formState.value)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.sendBugReport(state: BugReportState, listener: BugReporter.IMXBugReportListener) = launch {
|
||||
private fun CoroutineScope.sendBugReport(formState: BugReportFormState, hasCrashLogs: Boolean, listener: BugReporter.IMXBugReportListener) = launch {
|
||||
bugReporter.sendBugReport(
|
||||
coroutineScope = this,
|
||||
reportType = ReportType.BUG_REPORT,
|
||||
withDevicesLogs = state.formState.sendLogs,
|
||||
withCrashLogs = state.hasCrashLogs && state.formState.sendCrashLogs,
|
||||
withDevicesLogs = formState.sendLogs,
|
||||
withCrashLogs = hasCrashLogs && formState.sendCrashLogs,
|
||||
withKeyRequestHistory = false,
|
||||
withScreenshot = state.formState.sendScreenshot,
|
||||
theBugDescription = state.formState.description,
|
||||
withScreenshot = formState.sendScreenshot,
|
||||
theBugDescription = formState.description,
|
||||
serverVersion = "",
|
||||
canContact = state.formState.canContact,
|
||||
canContact = formState.canContact,
|
||||
customFields = emptyMap(),
|
||||
listener = listener
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ data class BugReportState(
|
|||
val screenshotUri: String? = null,
|
||||
val sendingProgress: Float = 0F,
|
||||
val sending: Async<Unit> = Async.Uninitialized,
|
||||
val eventSink: (BugReportEvents) -> Unit = {}
|
||||
) {
|
||||
val submitEnabled =
|
||||
formState.description.length > 10 && sending !is Async.Loading
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
package io.element.android.x.features.rageshake.bugreport
|
||||
|
||||
|
|
@ -36,6 +35,7 @@ import androidx.compose.material3.OutlinedTextField
|
|||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -59,22 +59,21 @@ 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
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun BugReportView(
|
||||
state: BugReportState,
|
||||
modifier: Modifier = Modifier,
|
||||
onDescriptionChanged: (String) -> Unit = {},
|
||||
onSetSendLog: (Boolean) -> Unit = {},
|
||||
onSetSendCrashLog: (Boolean) -> Unit = {},
|
||||
onSetCanContact: (Boolean) -> Unit = {},
|
||||
onSetSendScreenshot: (Boolean) -> Unit = {},
|
||||
onSubmit: () -> Unit = {},
|
||||
onFailureDialogClosed: () -> Unit = { },
|
||||
onDone: () -> Unit = { },
|
||||
) {
|
||||
LogCompositions(tag = "Rageshake", msg = "Root")
|
||||
val eventSink = state.eventSink
|
||||
if (state.sending is Async.Success) {
|
||||
onDone()
|
||||
LaunchedEffect(state.sending) {
|
||||
eventSink(BugReportEvents.ResetAll)
|
||||
onDone()
|
||||
}
|
||||
return
|
||||
}
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
|
|
@ -132,7 +131,7 @@ fun BugReportView(
|
|||
},
|
||||
onValueChange = {
|
||||
descriptionFieldState = it
|
||||
onDescriptionChanged(it)
|
||||
eventSink(BugReportEvents.SetDescription(it))
|
||||
},
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Text,
|
||||
|
|
@ -143,28 +142,28 @@ fun BugReportView(
|
|||
}
|
||||
LabelledCheckbox(
|
||||
checked = state.formState.sendLogs,
|
||||
onCheckedChange = onSetSendLog,
|
||||
onCheckedChange = { eventSink(BugReportEvents.SetSendLog(it)) },
|
||||
enabled = isFormEnabled,
|
||||
text = stringResource(id = ElementR.string.send_bug_report_include_logs)
|
||||
)
|
||||
if (state.hasCrashLogs) {
|
||||
LabelledCheckbox(
|
||||
checked = state.formState.sendCrashLogs,
|
||||
onCheckedChange = onSetSendCrashLog,
|
||||
onCheckedChange = { eventSink(BugReportEvents.SetSendCrashLog(it)) },
|
||||
enabled = isFormEnabled,
|
||||
text = stringResource(id = ElementR.string.send_bug_report_include_crash_logs)
|
||||
)
|
||||
}
|
||||
LabelledCheckbox(
|
||||
checked = state.formState.canContact,
|
||||
onCheckedChange = onSetCanContact,
|
||||
onCheckedChange = { eventSink(BugReportEvents.SetCanContact(it)) },
|
||||
enabled = isFormEnabled,
|
||||
text = stringResource(id = ElementR.string.you_may_contact_me)
|
||||
)
|
||||
if (state.screenshotUri != null) {
|
||||
LabelledCheckbox(
|
||||
checked = state.formState.sendScreenshot,
|
||||
onCheckedChange = onSetSendScreenshot,
|
||||
onCheckedChange = { eventSink(BugReportEvents.SetSendScreenshot(it)) },
|
||||
enabled = isFormEnabled,
|
||||
text = stringResource(id = ElementR.string.send_bug_report_include_screenshot)
|
||||
)
|
||||
|
|
@ -187,7 +186,7 @@ fun BugReportView(
|
|||
}
|
||||
// Submit
|
||||
Button(
|
||||
onClick = onSubmit,
|
||||
onClick = { eventSink(BugReportEvents.SendBugReport) },
|
||||
enabled = state.submitEnabled,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -197,7 +196,6 @@ fun BugReportView(
|
|||
}
|
||||
}
|
||||
when (state.sending) {
|
||||
Async.Uninitialized -> Unit
|
||||
is Async.Loading -> {
|
||||
CircularProgressIndicator(
|
||||
progress = state.sendingProgress,
|
||||
|
|
@ -206,9 +204,8 @@ fun BugReportView(
|
|||
}
|
||||
is Async.Failure -> ErrorDialog(
|
||||
content = state.sending.error.toString(),
|
||||
onDismiss = onFailureDialogClosed,
|
||||
)
|
||||
is Async.Success -> onDone()
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,31 @@
|
|||
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 androidx.compose.runtime.rememberCoroutineScope
|
||||
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> {
|
||||
class CrashDetectionPresenter @Inject constructor(private val crashDataStore: CrashDataStore) : Presenter<CrashDetectionState> {
|
||||
|
||||
@Composable
|
||||
override fun present(events: Flow<CrashDetectionEvents>): CrashDetectionState {
|
||||
override fun present(): CrashDetectionState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
val crashDetected = crashDataStore.appHasCrashed().collectAsState(initial = false)
|
||||
LaunchedEffect(Unit) {
|
||||
events.collect { event ->
|
||||
when (event) {
|
||||
CrashDetectionEvents.ResetAllCrashData -> resetAll()
|
||||
CrashDetectionEvents.ResetAppHasCrashed -> resetAppHasCrashed()
|
||||
}
|
||||
|
||||
fun handleEvents(event: CrashDetectionEvents) {
|
||||
when (event) {
|
||||
CrashDetectionEvents.ResetAllCrashData -> localCoroutineScope.resetAll()
|
||||
CrashDetectionEvents.ResetAppHasCrashed -> localCoroutineScope.resetAppHasCrashed()
|
||||
}
|
||||
}
|
||||
|
||||
return CrashDetectionState(
|
||||
crashDetected = crashDetected.value
|
||||
crashDetected = crashDetected.value,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,15 +28,19 @@ import io.element.android.x.element.resources.R as ElementR
|
|||
fun CrashDetectionView(
|
||||
state: CrashDetectionState,
|
||||
onOpenBugReport: () -> Unit = { },
|
||||
onPopupDismissed: () -> Unit = {}
|
||||
) {
|
||||
LogCompositions(tag = "Crash", msg = "CrashDetectionScreen")
|
||||
|
||||
fun onPopupDismissed(){
|
||||
state.eventSink(CrashDetectionEvents.ResetAllCrashData)
|
||||
}
|
||||
|
||||
if (state.crashDetected) {
|
||||
CrashDetectionContent(
|
||||
state,
|
||||
onYesClicked = onOpenBugReport,
|
||||
onNoClicked = onPopupDismissed,
|
||||
onDismiss = onPopupDismissed,
|
||||
onNoClicked = ::onPopupDismissed,
|
||||
onDismiss = ::onPopupDismissed,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,4 +18,5 @@ package io.element.android.x.features.rageshake.crash.ui
|
|||
|
||||
data class CrashDetectionState(
|
||||
val crashDetected: Boolean = false,
|
||||
val eventSink: (CrashDetectionEvents) -> Unit = {}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,19 +3,17 @@ package io.element.android.x.features.rageshake.detection
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
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.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.screenshot.ScreenshotHolder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
|
@ -24,13 +22,12 @@ class RageshakeDetectionPresenter @Inject constructor(
|
|||
private val screenshotHolder: ScreenshotHolder,
|
||||
private val rageShake: RageShake,
|
||||
private val preferencesPresenter: RageshakePreferencesPresenter,
|
||||
) : Presenter<RageshakeDetectionState, RageshakeDetectionEvents> {
|
||||
|
||||
private val preferencesEventsFlow = SharedFlowHolder<RageshakePreferencesEvents>()
|
||||
) : Presenter<RageshakeDetectionState> {
|
||||
|
||||
@Composable
|
||||
override fun present(events: Flow<RageshakeDetectionEvents>): RageshakeDetectionState {
|
||||
val preferencesState = preferencesPresenter.present(events = preferencesEventsFlow.asSharedFlow())
|
||||
override fun present(): RageshakeDetectionState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
val preferencesState = preferencesPresenter.present()
|
||||
val isStarted = rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
|
@ -40,33 +37,38 @@ class RageshakeDetectionPresenter @Inject constructor(
|
|||
val showDialog = rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
fun handleEvents(event: RageshakeDetectionEvents) {
|
||||
when (event) {
|
||||
RageshakeDetectionEvents.Disable -> {
|
||||
preferencesState.eventSink(RageshakePreferencesEvents.SetIsEnabled(false))
|
||||
showDialog.value = false
|
||||
}
|
||||
RageshakeDetectionEvents.StartDetection -> isStarted.value = true
|
||||
RageshakeDetectionEvents.StopDetection -> isStarted.value = false
|
||||
is RageshakeDetectionEvents.ProcessScreenshot -> localCoroutineScope.processScreenshot(takeScreenshot, showDialog, event.imageResult)
|
||||
RageshakeDetectionEvents.Dismiss -> showDialog.value = false
|
||||
}
|
||||
}
|
||||
|
||||
val state = remember(preferencesState, isStarted.value, takeScreenshot.value, showDialog.value) {
|
||||
RageshakeDetectionState(
|
||||
isStarted = isStarted.value,
|
||||
takeScreenshot = takeScreenshot.value,
|
||||
showDialog = showDialog.value,
|
||||
preferenceState = preferencesState
|
||||
preferenceState = preferencesState,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
events.collect { event ->
|
||||
when (event) {
|
||||
RageshakeDetectionEvents.Disable -> preferencesEventsFlow.emit(RageshakePreferencesEvents.SetIsEnabled(false))
|
||||
RageshakeDetectionEvents.StartDetection -> isStarted.value = true
|
||||
RageshakeDetectionEvents.StopDetection -> isStarted.value = false
|
||||
is RageshakeDetectionEvents.ProcessScreenshot -> processScreenshot(takeScreenshot, showDialog, event.imageResult)
|
||||
RageshakeDetectionEvents.Dismiss -> showDialog.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(preferencesState.sensitivity) {
|
||||
rageShake.setSensitivity(preferencesState.sensitivity)
|
||||
}
|
||||
val shouldStart = preferencesState.isEnabled &&
|
||||
preferencesState.isSupported &&
|
||||
isStarted.value &&
|
||||
!takeScreenshot.value &&
|
||||
!showDialog.value
|
||||
preferencesState.isSupported &&
|
||||
isStarted.value &&
|
||||
!takeScreenshot.value &&
|
||||
!showDialog.value
|
||||
|
||||
LaunchedEffect(shouldStart) {
|
||||
handleRageShake(shouldStart, state, takeScreenshot)
|
||||
|
|
|
|||
|
|
@ -24,5 +24,6 @@ data class RageshakeDetectionState(
|
|||
val takeScreenshot: Boolean = false,
|
||||
val showDialog: Boolean = false,
|
||||
val isStarted: Boolean = false,
|
||||
val preferenceState: RageshakePreferencesState = RageshakePreferencesState()
|
||||
val preferenceState: RageshakePreferencesState = RageshakePreferencesState(),
|
||||
val eventSink: (RageshakeDetectionEvents) -> Unit = {}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ 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 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
|
||||
|
|
@ -34,24 +36,28 @@ import io.element.android.x.element.resources.R as ElementR
|
|||
fun RageshakeDetectionView(
|
||||
state: RageshakeDetectionState,
|
||||
onOpenBugReport: () -> Unit = { },
|
||||
onScreenshotTaken: (ImageResult) -> Unit = {},
|
||||
onDisableClicked: () -> Unit = {},
|
||||
onNoClicked: () -> Unit = {}
|
||||
) {
|
||||
LogCompositions(tag = "Rageshake", msg = "RageshakeDetectionScreen")
|
||||
val eventSink = state.eventSink
|
||||
val context = LocalContext.current
|
||||
OnLifecycleEvent { _, event ->
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_RESUME -> eventSink(RageshakeDetectionEvents.StartDetection)
|
||||
Lifecycle.Event.ON_PAUSE -> eventSink(RageshakeDetectionEvents.StopDetection)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
when {
|
||||
state.takeScreenshot -> TakeScreenshot(
|
||||
onScreenshotTaken = onScreenshotTaken
|
||||
onScreenshotTaken = { eventSink(RageshakeDetectionEvents.ProcessScreenshot(it)) }
|
||||
)
|
||||
state.showDialog -> {
|
||||
LaunchedEffect(key1 = "RS_diag") {
|
||||
LaunchedEffect(Unit) {
|
||||
context.vibrate()
|
||||
}
|
||||
RageshakeDialogContent(
|
||||
state,
|
||||
onNoClicked = onNoClicked,
|
||||
onDisableClicked = onDisableClicked,
|
||||
onNoClicked = { eventSink(RageshakeDetectionEvents.Dismiss) },
|
||||
onDisableClicked = { eventSink(RageshakeDetectionEvents.Disable) },
|
||||
onYesClicked = onOpenBugReport
|
||||
)
|
||||
}
|
||||
|
|
@ -72,7 +78,6 @@ private fun TakeScreenshot(
|
|||
|
||||
@Composable
|
||||
fun RageshakeDialogContent(
|
||||
state: RageshakeDetectionState,
|
||||
onNoClicked: () -> Unit = { },
|
||||
onDisableClicked: () -> Unit = { },
|
||||
onYesClicked: () -> Unit = { },
|
||||
|
|
@ -83,6 +88,7 @@ fun RageshakeDialogContent(
|
|||
thirdButtonText = stringResource(id = ElementR.string.action_disable),
|
||||
submitText = stringResource(id = ElementR.string.yes),
|
||||
cancelText = stringResource(id = ElementR.string.no),
|
||||
onCancelClicked = onNoClicked,
|
||||
onThirdButtonClicked = onDisableClicked,
|
||||
onSubmitClicked = onYesClicked,
|
||||
onDismiss = onNoClicked,
|
||||
|
|
@ -93,8 +99,6 @@ fun RageshakeDialogContent(
|
|||
@Composable
|
||||
fun RageshakeDialogContentPreview() {
|
||||
ElementXTheme {
|
||||
RageshakeDialogContent(
|
||||
state = RageshakeDetectionState()
|
||||
)
|
||||
RageshakeDialogContent()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
package io.element.android.x.features.rageshake.preferences
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import io.element.android.x.architecture.Presenter
|
||||
import io.element.android.x.features.rageshake.rageshake.RageShake
|
||||
import io.element.android.x.features.rageshake.rageshake.RageshakeDataStore
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class RageshakePreferencesPresenter @Inject constructor(
|
||||
private val rageshake: RageShake,
|
||||
private val rageshakeDataStore: RageshakeDataStore,
|
||||
) : Presenter<RageshakePreferencesState, RageshakePreferencesEvents> {
|
||||
) : Presenter<RageshakePreferencesState> {
|
||||
|
||||
@Composable
|
||||
override fun present(events: Flow<RageshakePreferencesEvents>): RageshakePreferencesState {
|
||||
override fun present(): RageshakePreferencesState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
val isSupported: MutableState<Boolean> = rememberSaveable {
|
||||
mutableStateOf(rageshake.isAvailable())
|
||||
}
|
||||
|
|
@ -32,19 +32,18 @@ class RageshakePreferencesPresenter @Inject constructor(
|
|||
.sensitivity()
|
||||
.collectAsState(initial = 0f)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
events.collect { event ->
|
||||
when (event) {
|
||||
is RageshakePreferencesEvents.SetIsEnabled -> setIsEnabled(event.isEnabled)
|
||||
is RageshakePreferencesEvents.SetSensitivity -> setSensitivity(event.sensitivity)
|
||||
}
|
||||
fun handleEvents(event: RageshakePreferencesEvents) {
|
||||
when (event) {
|
||||
is RageshakePreferencesEvents.SetIsEnabled -> localCoroutineScope.setIsEnabled(event.isEnabled)
|
||||
is RageshakePreferencesEvents.SetSensitivity -> localCoroutineScope.setSensitivity(event.sensitivity)
|
||||
}
|
||||
}
|
||||
|
||||
return RageshakePreferencesState(
|
||||
isEnabled = isEnabled.value,
|
||||
isSupported = isSupported.value,
|
||||
sensitivity = sensitivity.value
|
||||
sensitivity = sensitivity.value,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@ data class RageshakePreferencesState(
|
|||
val isEnabled: Boolean = false,
|
||||
val isSupported: Boolean = true,
|
||||
val sensitivity: Float = 0.3f,
|
||||
val eventSink: (RageshakePreferencesEvents) -> Unit = {},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,9 +34,16 @@ fun RageshakePreferencesView(
|
|||
state: RageshakePreferencesState,
|
||||
modifier: Modifier = Modifier,
|
||||
onOpenRageshake: () -> Unit = {},
|
||||
onIsEnabledChanged: (Boolean) -> Unit = {},
|
||||
onSensitivityChanged: (Float) -> Unit = {}
|
||||
) {
|
||||
|
||||
fun onSensitivityChanged(sensitivity: Float){
|
||||
state.eventSink(RageshakePreferencesEvents.SetSensitivity(sensitivity = sensitivity))
|
||||
}
|
||||
|
||||
fun onEnabledChanged(isEnabled: Boolean){
|
||||
state.eventSink(RageshakePreferencesEvents.SetIsEnabled(isEnabled = isEnabled))
|
||||
}
|
||||
|
||||
Column(modifier = modifier) {
|
||||
PreferenceCategory(title = stringResource(id = ElementR.string.send_bug_report)) {
|
||||
PreferenceText(
|
||||
|
|
@ -45,12 +52,13 @@ fun RageshakePreferencesView(
|
|||
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 = onIsEnabledChanged
|
||||
onCheckedChange = ::onEnabledChanged
|
||||
)
|
||||
PreferenceSlide(
|
||||
title = stringResource(id = ElementR.string.settings_rageshake_detection_threshold),
|
||||
|
|
@ -58,7 +66,7 @@ fun RageshakePreferencesView(
|
|||
value = state.sensitivity,
|
||||
enabled = state.isEnabled,
|
||||
steps = 3 /* 5 possible values - steps are in ]0, 1[ */,
|
||||
onValueChange = onSensitivityChanged
|
||||
onValueChange = ::onSensitivityChanged
|
||||
)
|
||||
} else {
|
||||
PreferenceText(title = "Rageshaking is not supported by your device")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue