Migrate RageshakeDetectionView to new architecture
This commit is contained in:
parent
877ffd0d14
commit
c299ab4031
6 changed files with 133 additions and 224 deletions
|
|
@ -25,7 +25,7 @@ import io.element.android.x.architecture.viewmodel.DaggerMavericksBindings
|
|||
|
||||
@SingleIn(AppScope::class)
|
||||
@MergeComponent(AppScope::class)
|
||||
interface AppComponent : DaggerMavericksBindings, NodeFactoriesBindings {
|
||||
interface AppComponent : NodeFactoriesBindings {
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package io.element.android.x.features.rageshake.detection
|
||||
|
||||
import io.element.android.x.core.screenshot.ImageResult
|
||||
|
||||
sealed interface RageshakeDetectionEvents {
|
||||
object Dismiss: RageshakeDetectionEvents
|
||||
object Disable : RageshakeDetectionEvents
|
||||
object StartDetection : RageshakeDetectionEvents
|
||||
object StopDetection : RageshakeDetectionEvents
|
||||
data class ProcessScreenshot(val imageResult: ImageResult) : RageshakeDetectionEvents
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
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.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.rageshake.RageshakeDataStore
|
||||
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
|
||||
|
||||
class RageshakeDetectionPresenter @Inject constructor(
|
||||
private val rageshakeDataStore: RageshakeDataStore,
|
||||
private val screenshotHolder: ScreenshotHolder,
|
||||
private val rageShake: RageShake,
|
||||
private val preferencesPresenter: RageshakePreferencesPresenter,
|
||||
) : Presenter<RageshakeDetectionState, RageshakeDetectionEvents> {
|
||||
|
||||
private val preferencesEventsFlow = SharedFlowHolder<RageshakePreferencesEvents>()
|
||||
|
||||
@Composable
|
||||
override fun present(events: Flow<RageshakeDetectionEvents>): RageshakeDetectionState {
|
||||
val preferencesState = preferencesPresenter.present(events = preferencesEventsFlow.asSharedFlow())
|
||||
val isStarted = rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
val takeScreenshot = rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
val showDialog = rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
val state = RageshakeDetectionState(
|
||||
isStarted = isStarted.value,
|
||||
takeScreenshot = takeScreenshot.value,
|
||||
showDialog = showDialog.value,
|
||||
preferenceState = preferencesState
|
||||
)
|
||||
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 = remember {
|
||||
derivedStateOf {
|
||||
preferencesState.isEnabled &&
|
||||
preferencesState.isSupported &&
|
||||
isStarted.value &&
|
||||
!takeScreenshot.value &&
|
||||
!showDialog.value
|
||||
}
|
||||
}
|
||||
LaunchedEffect(shouldStart) {
|
||||
handleRageShake(shouldStart.value, state, takeScreenshot)
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
|
||||
private fun handleRageShake(start: Boolean, state: RageshakeDetectionState, takeScreenshot: MutableState<Boolean>) {
|
||||
if (start) {
|
||||
rageShake.start(state.preferenceState.sensitivity)
|
||||
rageShake.interceptor = {
|
||||
takeScreenshot.value = true
|
||||
}
|
||||
} else {
|
||||
rageShake.stop()
|
||||
rageShake.interceptor = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.processScreenshot(takeScreenshot: MutableState<Boolean>, showDialog: MutableState<Boolean>, imageResult: ImageResult) = launch {
|
||||
screenshotHolder.reset()
|
||||
when (imageResult) {
|
||||
is ImageResult.Error -> {
|
||||
Timber.e(imageResult.exception, "Unable to write screenshot")
|
||||
}
|
||||
is ImageResult.Success -> {
|
||||
screenshotHolder.writeBitmap(imageResult.data)
|
||||
}
|
||||
}
|
||||
takeScreenshot.value = false
|
||||
showDialog.value = true
|
||||
}
|
||||
}
|
||||
|
|
@ -16,13 +16,11 @@
|
|||
|
||||
package io.element.android.x.features.rageshake.detection
|
||||
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
import io.element.android.x.features.rageshake.preferences.RageshakePreferencesState
|
||||
|
||||
data class RageshakeDetectionViewState(
|
||||
data class RageshakeDetectionState(
|
||||
val takeScreenshot: Boolean = false,
|
||||
val showDialog: Boolean = false,
|
||||
val isEnabled: Boolean = true,
|
||||
val isStarted: Boolean = false,
|
||||
val isSupported: Boolean = false,
|
||||
val sensitivity: Float = 0.5f,
|
||||
) : MavericksState
|
||||
val preferenceState: RageshakePreferencesState = RageshakePreferencesState()
|
||||
)
|
||||
|
|
@ -18,16 +18,11 @@ package io.element.android.x.features.rageshake.detection
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
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 com.airbnb.mvrx.compose.collectAsState
|
||||
import com.airbnb.mvrx.compose.mavericksViewModel
|
||||
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
|
||||
|
|
@ -36,24 +31,18 @@ import io.element.android.x.designsystem.components.dialogs.ConfirmationDialog
|
|||
import io.element.android.x.element.resources.R as ElementR
|
||||
|
||||
@Composable
|
||||
fun RageshakeDetectionScreen(
|
||||
viewModel: RageshakeDetectionViewModel = mavericksViewModel(),
|
||||
fun RageshakeDetectionView(
|
||||
state: RageshakeDetectionState,
|
||||
onOpenBugReport: () -> Unit = { },
|
||||
onScreenshotTaken: (ImageResult) -> Unit,
|
||||
onDisableClicked: () -> Unit,
|
||||
onNoClicked: () -> Unit
|
||||
) {
|
||||
val state: RageshakeDetectionViewState by viewModel.collectAsState()
|
||||
LogCompositions(tag = "Rageshake", msg = "RageshakeDetectionScreen")
|
||||
val context = LocalContext.current
|
||||
OnLifecycleEvent { _, event ->
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_RESUME -> viewModel.start()
|
||||
Lifecycle.Event.ON_PAUSE -> viewModel.stop()
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
when {
|
||||
state.takeScreenshot -> TakeScreenshot(
|
||||
onScreenshotTaken = viewModel::onScreenshotTaken
|
||||
onScreenshotTaken = onScreenshotTaken
|
||||
)
|
||||
state.showDialog -> {
|
||||
LaunchedEffect(key1 = "RS_diag") {
|
||||
|
|
@ -61,14 +50,9 @@ fun RageshakeDetectionScreen(
|
|||
}
|
||||
RageshakeDialogContent(
|
||||
state,
|
||||
onNoClicked = viewModel::onNo,
|
||||
onDisableClicked = {
|
||||
viewModel.onEnableClicked(false)
|
||||
},
|
||||
onYesClicked = {
|
||||
onOpenBugReport()
|
||||
viewModel.onYes()
|
||||
}
|
||||
onNoClicked = onNoClicked,
|
||||
onDisableClicked = onDisableClicked,
|
||||
onYesClicked = onOpenBugReport
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -86,7 +70,7 @@ private fun TakeScreenshot(
|
|||
|
||||
@Composable
|
||||
fun RageshakeDialogContent(
|
||||
state: RageshakeDetectionViewState,
|
||||
state: RageshakeDetectionState,
|
||||
onNoClicked: () -> Unit = { },
|
||||
onDisableClicked: () -> Unit = { },
|
||||
onYesClicked: () -> Unit = { },
|
||||
|
|
@ -108,7 +92,7 @@ fun RageshakeDialogContent(
|
|||
fun RageshakeDialogContentPreview() {
|
||||
ElementXTheme {
|
||||
RageshakeDialogContent(
|
||||
state = RageshakeDetectionViewState()
|
||||
state = RageshakeDetectionState()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,190 +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.detection
|
||||
|
||||
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.core.screenshot.ImageResult
|
||||
import io.element.android.x.di.AppScope
|
||||
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.Dispatchers
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
@ContributesViewModel(AppScope::class)
|
||||
class RageshakeDetectionViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: RageshakeDetectionViewState,
|
||||
private val rageshakeDataStore: RageshakeDataStore,
|
||||
private val screenshotHolder: ScreenshotHolder,
|
||||
private val rageShake: RageShake,
|
||||
) : MavericksViewModel<RageshakeDetectionViewState>(initialState) {
|
||||
|
||||
companion object :
|
||||
MavericksViewModelFactory<RageshakeDetectionViewModel, RageshakeDetectionViewState> by daggerMavericksViewModelFactory()
|
||||
|
||||
init {
|
||||
setState {
|
||||
copy(
|
||||
isSupported = rageShake.isAvailable()
|
||||
)
|
||||
}
|
||||
observeDataStore()
|
||||
observeState()
|
||||
}
|
||||
|
||||
private fun observeDataStore() {
|
||||
viewModelScope.launch {
|
||||
rageshakeDataStore.isEnabled().collect { isEnabled ->
|
||||
setState {
|
||||
copy(
|
||||
isEnabled = isEnabled
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
rageshakeDataStore.sensitivity().collect { sensitivity ->
|
||||
setState {
|
||||
copy(
|
||||
sensitivity = sensitivity
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeState() {
|
||||
viewModelScope.launch {
|
||||
stateFlow
|
||||
.map {
|
||||
it.isSupported &&
|
||||
it.isEnabled &&
|
||||
it.isStarted &&
|
||||
!it.takeScreenshot &&
|
||||
!it.showDialog
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.collect(::handleRageShake)
|
||||
}
|
||||
viewModelScope.launch {
|
||||
stateFlow
|
||||
.map {
|
||||
it.sensitivity
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.collect {
|
||||
rageShake.setSensitivity(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRageShake(shouldStart: Boolean) {
|
||||
if (shouldStart) {
|
||||
withState {
|
||||
rageShake.start(it.sensitivity)
|
||||
}
|
||||
rageShake.interceptor = {
|
||||
setState {
|
||||
copy(
|
||||
takeScreenshot = true
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rageShake.stop()
|
||||
rageShake.interceptor = null
|
||||
}
|
||||
}
|
||||
|
||||
fun onScreenshotTaken(imageResult: ImageResult) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
screenshotHolder.reset()
|
||||
when (imageResult) {
|
||||
is ImageResult.Error -> {
|
||||
Timber.e(imageResult.exception, "Unable to write screenshot")
|
||||
}
|
||||
is ImageResult.Success -> {
|
||||
screenshotHolder.writeBitmap(imageResult.data)
|
||||
}
|
||||
}
|
||||
setState {
|
||||
copy(
|
||||
takeScreenshot = false,
|
||||
showDialog = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun start() {
|
||||
setState {
|
||||
copy(isStarted = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onPopupDismissed() {
|
||||
setState {
|
||||
copy(
|
||||
showDialog = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onNo() {
|
||||
onPopupDismissed()
|
||||
}
|
||||
|
||||
fun onYes() {
|
||||
onPopupDismissed()
|
||||
}
|
||||
|
||||
fun onEnableClicked(enabled: Boolean) {
|
||||
viewModelScope.launch {
|
||||
rageshakeDataStore.setIsEnabled(enabled)
|
||||
}
|
||||
if (!enabled) {
|
||||
onPopupDismissed()
|
||||
}
|
||||
}
|
||||
|
||||
fun onSensitivityChange(sensitivity: Float) {
|
||||
viewModelScope.launch {
|
||||
rageshakeDataStore.setSensitivity(sensitivity)
|
||||
}
|
||||
rageShake.setSensitivity(sensitivity)
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
setState {
|
||||
copy(isStarted = false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
stop()
|
||||
handleRageShake(false)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue