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 78ae309341..115d6fb4de 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 @@ -25,8 +25,8 @@ 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.compose.OnLifecycleEvent 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.features.rageshake.bugreport.BugReportNode import io.element.android.x.matrix.Matrix @@ -77,22 +77,6 @@ class RootFlowNode( 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") } - ) - } - init { matrix.isLoggedIn() .distinctUntilChanged() @@ -114,47 +98,21 @@ 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) { 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) } } - private fun onScreenshotTaken(imageResult: ImageResult) { - presenterConnector.emitEvent(RootEvents.ProcessScreenshot(imageResult)) - } - private val bugReportNodeCallback = object : BugReportNode.Callback { override fun onBugReportSent() { backstack.pop() 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 index 7a88722ce6..174852ad2f 100644 --- a/app/src/main/java/io/element/android/x/root/RootEvents.kt +++ b/app/src/main/java/io/element/android/x/root/RootEvents.kt @@ -1,14 +1,5 @@ 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 + object HideShowkaseButton : 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 index 45a28f2ba4..3563fafb4f 100644 --- a/app/src/main/java/io/element/android/x/root/RootPresenter.kt +++ b/app/src/main/java/io/element/android/x/root/RootPresenter.kt @@ -1,62 +1,45 @@ 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() +) : Presenter { @Composable - override fun present(events: Flow): RootState { + override fun present(): 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()) + val rageshakeDetectionState = rageshakeDetectionPresenter.present() + val crashDetectionState = crashDetectionPresenter.present() + val bugReportState = bugReportPresenter.present() - 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)) - } + fun handleEvent(event: RootEvents) { + when (event) { + RootEvents.HideShowkaseButton -> isShowkaseButtonVisible.value = false } } + return RootState( isBugReportVisible = isBugReportVisible.value, isShowkaseButtonVisible = isShowkaseButtonVisible.value, rageshakeDetectionState = rageshakeDetectionState, crashDetectionState = crashDetectionState, - bugReportState = bugReportState + bugReportState = bugReportState, + eventSink = ::handleEvent ) } } 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 index a146b93322..cfcbb29499 100644 --- a/app/src/main/java/io/element/android/x/root/RootState.kt +++ b/app/src/main/java/io/element/android/x/root/RootState.kt @@ -11,5 +11,6 @@ data class RootState( val isShowkaseButtonVisible: Boolean, val rageshakeDetectionState: RageshakeDetectionState, val crashDetectionState: CrashDetectionState, - val bugReportState: BugReportState + val bugReportState: BugReportState, + val eventSink: (RootEvents) -> Unit ) 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 index ff0effef57..fb0ac5c296 100644 --- a/app/src/main/java/io/element/android/x/root/RootView.kt +++ b/app/src/main/java/io/element/android/x/root/RootView.kt @@ -10,8 +10,9 @@ 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.CrashDetectionEvents import io.element.android.x.features.rageshake.crash.ui.CrashDetectionView +import io.element.android.x.features.rageshake.detection.RageshakeDetectionEvents import io.element.android.x.features.rageshake.detection.RageshakeDetectionView import io.element.android.x.getBrowserIntent @@ -19,12 +20,7 @@ import io.element.android.x.getBrowserIntent 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( @@ -33,23 +29,27 @@ fun RootView( contentAlignment = Alignment.TopCenter, ) { children() + val eventSink = state.eventSink val context = LocalContext.current + + fun onOpenBugReport() { + state.crashDetectionState.eventSink(CrashDetectionEvents.ResetAppHasCrashed) + state.rageshakeDetectionState.eventSink(RageshakeDetectionEvents.Dismiss) + onOpenBugReport.invoke() + } + ShowkaseButton( isVisible = state.isShowkaseButtonVisible, - onCloseClicked = onHideShowkaseClicked, + onCloseClicked = { eventSink(RootEvents.HideShowkaseButton) }, onClick = { ContextCompat.startActivity(context, Showkase.getBrowserIntent(context), null) } ) RageshakeDetectionView( state = state.rageshakeDetectionState, - onOpenBugReport = onOpenBugReport, - onDisableClicked = onDisableRageshake, - onNoClicked = onDismissRageshake, - onScreenshotTaken = onScreenshotTaken + onOpenBugReport = ::onOpenBugReport, ) CrashDetectionView( state = state.crashDetectionState, - onOpenBugReport = onOpenBugReport, - onPopupDismissed = onCrashDetectedDismissed + onOpenBugReport = ::onOpenBugReport, ) } } diff --git a/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerNode.kt b/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerNode.kt index f9f0bc4bbd..a6dc59a703 100644 --- a/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerNode.kt +++ b/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerNode.kt @@ -22,14 +22,6 @@ class ChangeServerNode @AssistedInject constructor( private val presenterConnector = presenterConnector(presenter) - private fun onChangeServer(server: String) { - presenterConnector.emitEvent(ChangeServerEvents.SetServer(server)) - } - - private fun onSubmit() { - presenterConnector.emitEvent(ChangeServerEvents.Submit) - } - private fun onSuccess() { navigateUp() } @@ -39,8 +31,6 @@ class ChangeServerNode @AssistedInject constructor( val state by presenterConnector.stateFlow.collectAsState() ChangeServerView( state = state, - onChangeServer = this::onChangeServer, - onChangeServerSubmit = this::onSubmit, onChangeServerSuccess = this::onSuccess, ) } diff --git a/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerPresenter.kt b/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerPresenter.kt index c5d9891c7c..84bb601259 100644 --- a/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerPresenter.kt +++ b/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerPresenter.kt @@ -1,41 +1,42 @@ package io.element.android.x.features.login.changeserver import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState 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.Async import io.element.android.x.architecture.Presenter import io.element.android.x.architecture.execute import io.element.android.x.matrix.Matrix import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch import javax.inject.Inject -class ChangeServerPresenter @Inject constructor(private val matrix: Matrix) : Presenter { +class ChangeServerPresenter @Inject constructor(private val matrix: Matrix) : Presenter { @Composable - override fun present(events: Flow): ChangeServerState { + override fun present(): ChangeServerState { + val localCoroutineScope = rememberCoroutineScope() val homeserver = rememberSaveable { mutableStateOf(matrix.getHomeserverOrDefault()) } val changeServerAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } - LaunchedEffect(Unit) { - events.collect { event -> - when (event) { - is ChangeServerEvents.SetServer -> homeserver.value = event.server - ChangeServerEvents.Submit -> submit(homeserver.value, changeServerAction) - } + + fun handleEvents(event: ChangeServerEvents) { + when (event) { + is ChangeServerEvents.SetServer -> homeserver.value = event.server + ChangeServerEvents.Submit -> localCoroutineScope.submit(homeserver.value, changeServerAction) } } + return ChangeServerState( homeserver = homeserver.value, - changeServerAction = changeServerAction.value + changeServerAction = changeServerAction.value, + eventSink = ::handleEvents ) } diff --git a/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerState.kt b/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerState.kt index dabd7a09bf..1cf98d788f 100644 --- a/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerState.kt +++ b/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerState.kt @@ -5,6 +5,7 @@ import io.element.android.x.architecture.Async data class ChangeServerState( val homeserver: String = "", val changeServerAction: Async = Async.Uninitialized, + val eventSink: (ChangeServerEvents) -> Unit = {}, ) { val submitEnabled = homeserver.isNotEmpty() && changeServerAction !is Async.Loading } diff --git a/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerView.kt b/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerView.kt index 3bcf580c28..e08fb73e52 100644 --- a/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerView.kt +++ b/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerView.kt @@ -64,14 +64,13 @@ import io.element.android.x.features.login.error.changeServerError fun ChangeServerView( state: ChangeServerState, modifier: Modifier = Modifier, - onChangeServer: (String) -> Unit = {}, - onChangeServerSubmit: () -> Unit = {}, onChangeServerSuccess: () -> Unit = {}, ) { Surface( modifier = modifier, color = MaterialTheme.colorScheme.background, ) { + val eventSink = state.eventSink val scrollState = rememberScrollState() Box( modifier = Modifier @@ -135,7 +134,7 @@ fun ChangeServerView( .padding(top = 200.dp), onValueChange = { homeserverFieldState = it - onChangeServer(it) + eventSink(ChangeServerEvents.SetServer(it)) }, label = { Text(text = "Server") @@ -146,7 +145,7 @@ fun ChangeServerView( imeAction = ImeAction.Done, ), keyboardActions = KeyboardActions( - onDone = { onChangeServerSubmit() } + onDone = { eventSink(ChangeServerEvents.Submit) } ) ) if (state.changeServerAction is Async.Failure) { @@ -161,7 +160,7 @@ fun ChangeServerView( ) } Button( - onClick = onChangeServerSubmit, + onClick = { eventSink(ChangeServerEvents.Submit) }, enabled = state.submitEnabled, modifier = Modifier .fillMaxWidth() diff --git a/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootNode.kt b/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootNode.kt index 096fb3939a..a0d809b9ad 100644 --- a/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootNode.kt +++ b/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootNode.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.lifecycle.Lifecycle import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node @@ -13,6 +14,7 @@ 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.core.compose.OnLifecycleEvent import io.element.android.x.di.AppScope @ContributesNode(AppScope::class) @@ -24,12 +26,6 @@ class LoginRootNode @AssistedInject constructor( private val presenterConnector = presenterConnector(presenter) - init { - lifecycle.subscribe( - onResume = { presenterConnector.emitEvent(LoginRootEvents.RefreshHomeServer) } - ) - } - interface Callback : Plugin { fun onChangeHomeServer() } @@ -38,27 +34,18 @@ class LoginRootNode @AssistedInject constructor( plugins().forEach { it.onChangeHomeServer() } } - private fun onLoginChanged(login: String) { - presenterConnector.emitEvent(LoginRootEvents.SetLogin(login)) - } - - private fun onPasswordChanged(password: String) { - presenterConnector.emitEvent(LoginRootEvents.SetPassword(password)) - } - - private fun onSubmit() { - presenterConnector.emitEvent(LoginRootEvents.Submit) - } - @Composable override fun View(modifier: Modifier) { val state by presenterConnector.stateFlow.collectAsState() + OnLifecycleEvent { _, event -> + when (event) { + Lifecycle.Event.ON_RESUME -> state.eventSink(LoginRootEvents.RefreshHomeServer) + else -> Unit + } + } LoginRootScreen( state = state, onChangeServer = this::onChangeHomeServer, - onLoginChanged = this::onLoginChanged, - onPasswordChanged = this::onPasswordChanged, - onSubmitClicked = this::onSubmit ) } } diff --git a/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootPresenter.kt b/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootPresenter.kt index 1d254c47d3..79ac8a3d95 100644 --- a/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootPresenter.kt +++ b/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootPresenter.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState 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.matrix.Matrix @@ -13,10 +14,11 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch import javax.inject.Inject -class LoginRootPresenter @Inject constructor(private val matrix: Matrix) : Presenter { +class LoginRootPresenter @Inject constructor(private val matrix: Matrix) : Presenter { @Composable - override fun present(events: Flow): LoginRootState { + override fun present(): LoginRootState { + val localCoroutineScope = rememberCoroutineScope() val homeserver = rememberSaveable { mutableStateOf(matrix.getHomeserverOrDefault()) } @@ -27,24 +29,24 @@ class LoginRootPresenter @Inject constructor(private val matrix: Matrix) : Prese mutableStateOf(LoginFormState.Default) } - LaunchedEffect(Unit) { - events.collect { event -> - when (event) { - LoginRootEvents.RefreshHomeServer -> refreshHomeServer(homeserver) - is LoginRootEvents.SetLogin -> updateFormState(formState) { - copy(login = event.login) - } - is LoginRootEvents.SetPassword -> updateFormState(formState) { - copy(password = event.password) - } - LoginRootEvents.Submit -> submit(homeserver.value, formState.value, loggedInState) + fun handleEvents(event: LoginRootEvents){ + when (event) { + LoginRootEvents.RefreshHomeServer -> refreshHomeServer(homeserver) + is LoginRootEvents.SetLogin -> updateFormState(formState) { + copy(login = event.login) } + is LoginRootEvents.SetPassword -> updateFormState(formState) { + copy(password = event.password) + } + LoginRootEvents.Submit -> localCoroutineScope.submit(homeserver.value, formState.value, loggedInState) } } + return LoginRootState( homeserver = homeserver.value, loggedInState = loggedInState.value, - formState = formState.value + formState = formState.value, + eventSink = ::handleEvents ) } diff --git a/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootScreen.kt b/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootScreen.kt index f746965ddb..d71cb909c3 100644 --- a/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootScreen.kt +++ b/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootScreen.kt @@ -68,11 +68,9 @@ fun LoginRootScreen( state: LoginRootState, modifier: Modifier = Modifier, onChangeServer: () -> Unit = {}, - onLoginChanged: (String) -> Unit = {}, - onPasswordChanged: (String) -> Unit = {}, - onSubmitClicked: () -> Unit = {}, onLoginWithSuccess: (SessionId) -> Unit = {}, ) { + val eventSink = state.eventSink Surface( modifier = modifier, color = MaterialTheme.colorScheme.background, @@ -144,7 +142,7 @@ fun LoginRootScreen( }, onValueChange = { loginFieldState = it - onLoginChanged(it) + eventSink(LoginRootEvents.SetLogin(it)) }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Email, @@ -163,7 +161,7 @@ fun LoginRootScreen( .padding(top = 24.dp), onValueChange = { passwordFieldState = it - onPasswordChanged(it) + eventSink(LoginRootEvents.SetPassword(it)) }, label = { Text(text = "Password") @@ -185,7 +183,7 @@ fun LoginRootScreen( imeAction = ImeAction.Done, ), keyboardActions = KeyboardActions( - onDone = { onSubmitClicked() } + onDone = { eventSink(LoginRootEvents.Submit) } ), ) if (state.loggedInState is LoggedInState.ErrorLoggingIn) { @@ -199,7 +197,7 @@ fun LoginRootScreen( } // Submit Button( - onClick = onSubmitClicked, + onClick = { eventSink(LoginRootEvents.Submit) }, enabled = state.submitEnabled, modifier = Modifier .fillMaxWidth() diff --git a/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootState.kt b/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootState.kt index 38b8f67af2..53fc6262f1 100644 --- a/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootState.kt +++ b/features/login/src/main/java/io/element/android/x/features/login/root/LoginRootState.kt @@ -8,6 +8,7 @@ data class LoginRootState( val homeserver: String = "", val loggedInState: LoggedInState = LoggedInState.NotLoggedIn, val formState: LoginFormState = LoginFormState.Default, + val eventSink: (LoginRootEvents) -> Unit = {} ) { val submitEnabled = formState.login.isNotEmpty() && formState.password.isNotEmpty() && loggedInState != LoggedInState.LoggingIn diff --git a/features/logout/src/main/java/io/element/android/x/features/logout/LogoutPreferencePresenter.kt b/features/logout/src/main/java/io/element/android/x/features/logout/LogoutPreferencePresenter.kt index 800aebf077..82a56c6bf8 100644 --- a/features/logout/src/main/java/io/element/android/x/features/logout/LogoutPreferencePresenter.kt +++ b/features/logout/src/main/java/io/element/android/x/features/logout/LogoutPreferencePresenter.kt @@ -1,35 +1,36 @@ package io.element.android.x.features.logout import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import io.element.android.x.architecture.Async import io.element.android.x.architecture.Presenter import io.element.android.x.architecture.execute import io.element.android.x.matrix.MatrixClient import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch import javax.inject.Inject -class LogoutPreferencePresenter @Inject constructor(private val matrixClient: MatrixClient) : Presenter { +class LogoutPreferencePresenter @Inject constructor(private val matrixClient: MatrixClient) : Presenter { @Composable - override fun present(events: Flow): LogoutPreferenceState { + override fun present(): LogoutPreferenceState { + val localCoroutineScope = rememberCoroutineScope() val logoutAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } - LaunchedEffect(Unit) { - events.collect { event -> - when (event) { - LogoutPreferenceEvents.Logout -> logout(logoutAction) - } + + fun handleEvents(event: LogoutPreferenceEvents) { + when (event) { + LogoutPreferenceEvents.Logout -> localCoroutineScope.logout(logoutAction) } } + return LogoutPreferenceState( - logoutAction = logoutAction.value + logoutAction = logoutAction.value, + eventSink = ::handleEvents ) } diff --git a/features/logout/src/main/java/io/element/android/x/features/logout/LogoutPreferenceScreen.kt b/features/logout/src/main/java/io/element/android/x/features/logout/LogoutPreferenceScreen.kt index dc0a8acea0..75f29319dd 100644 --- a/features/logout/src/main/java/io/element/android/x/features/logout/LogoutPreferenceScreen.kt +++ b/features/logout/src/main/java/io/element/android/x/features/logout/LogoutPreferenceScreen.kt @@ -19,6 +19,7 @@ package io.element.android.x.features.logout import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Logout import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.res.stringResource @@ -34,14 +35,15 @@ import io.element.android.x.element.resources.R as ElementR @Composable fun LogoutPreferenceView( state: LogoutPreferenceState, - onLogoutClicked: () -> Unit = {}, - onSuccessLogout: () -> Unit = {}, + onSuccessLogout: () -> Unit = {} ) { + val eventSink = state.eventSink if (state.logoutAction is Async.Success) { - onSuccessLogout() + LaunchedEffect(state.logoutAction) { + onSuccessLogout() + } return } - val openDialog = remember { mutableStateOf(false) } LogoutPreferenceContent( @@ -61,7 +63,7 @@ fun LogoutPreferenceView( }, onSubmitClicked = { openDialog.value = false - onLogoutClicked() + eventSink(LogoutPreferenceEvents.Logout) }, onDismiss = { openDialog.value = false diff --git a/features/logout/src/main/java/io/element/android/x/features/logout/LogoutPreferenceState.kt b/features/logout/src/main/java/io/element/android/x/features/logout/LogoutPreferenceState.kt index b7b08578bd..70e637899b 100644 --- a/features/logout/src/main/java/io/element/android/x/features/logout/LogoutPreferenceState.kt +++ b/features/logout/src/main/java/io/element/android/x/features/logout/LogoutPreferenceState.kt @@ -20,4 +20,5 @@ import io.element.android.x.architecture.Async data class LogoutPreferenceState( val logoutAction: Async = Async.Uninitialized, + val eventSink: (LogoutPreferenceEvents) -> Unit = {}, ) diff --git a/features/onboarding/src/main/java/io/element/android/x/features/onboarding/OnBoardingViewModel.kt b/features/onboarding/src/main/java/io/element/android/x/features/onboarding/OnBoardingViewModel.kt deleted file mode 100644 index a4a170515c..0000000000 --- a/features/onboarding/src/main/java/io/element/android/x/features/onboarding/OnBoardingViewModel.kt +++ /dev/null @@ -1,31 +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.onboarding - -import com.airbnb.mvrx.MavericksViewModel - -class OnBoardingViewModel(initialState: OnBoardingViewState) : - MavericksViewModel(initialState) { - - fun onPageChanged(page: Int) { - setState { - copy( - currentPage = page, - ) - } - } -} diff --git a/features/onboarding/src/main/java/io/element/android/x/features/onboarding/OnBoardingViewState.kt b/features/onboarding/src/main/java/io/element/android/x/features/onboarding/OnBoardingViewState.kt deleted file mode 100644 index e728e8345c..0000000000 --- a/features/onboarding/src/main/java/io/element/android/x/features/onboarding/OnBoardingViewState.kt +++ /dev/null @@ -1,23 +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.onboarding - -import com.airbnb.mvrx.MavericksState - -data class OnBoardingViewState( - val currentPage: Int = 0, -) : MavericksState diff --git a/features/preferences/src/main/java/io/element/android/x/features/preferences/root/PreferencesRootEvents.kt b/features/preferences/src/main/java/io/element/android/x/features/preferences/root/PreferencesRootEvents.kt deleted file mode 100644 index 80496c695b..0000000000 --- a/features/preferences/src/main/java/io/element/android/x/features/preferences/root/PreferencesRootEvents.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.element.android.x.features.preferences.root - -sealed interface PreferencesRootEvents { - object Logout : PreferencesRootEvents - data class SetRageshakeSensitivity(val sensitivity: Float) : PreferencesRootEvents - data class SetRageshakeEnabled(val enabled: Boolean) : PreferencesRootEvents -} 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 78db0459a2..f8afbd9461 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 @@ -21,24 +21,12 @@ class PreferencesRootNode @AssistedInject constructor( private val presenter: PreferencesRootPresenter, ) : Node(buildContext, plugins = plugins) { - public interface Callback : Plugin { + interface Callback : Plugin { fun onOpenBugReport() } private val presenterConnector = presenterConnector(presenter) - private fun onLogoutClicked() { - presenterConnector.emitEvent(PreferencesRootEvents.Logout) - } - - private fun onRageshakeEnabledChanged(isEnabled: Boolean) { - presenterConnector.emitEvent(PreferencesRootEvents.SetRageshakeEnabled(isEnabled)) - } - - private fun onRageshakeSensitivityChanged(sensitivity: Float) { - presenterConnector.emitEvent(PreferencesRootEvents.SetRageshakeSensitivity(sensitivity)) - } - private fun onOpenBugReport() { plugins().forEach { it.onOpenBugReport() } } @@ -48,10 +36,7 @@ class PreferencesRootNode @AssistedInject constructor( val state by presenterConnector.stateFlow.collectAsState() PreferencesRootView( state = state, - onLogoutClicked = this::onLogoutClicked, onBackPressed = this::navigateUp, - onRageshakeEnabledChanged = this::onRageshakeEnabledChanged, - onRageshakeSensitivityChanged = this::onRageshakeSensitivityChanged, onOpenRageShake = this::onOpenBugReport ) } diff --git a/features/preferences/src/main/java/io/element/android/x/features/preferences/root/PreferencesRootPresenter.kt b/features/preferences/src/main/java/io/element/android/x/features/preferences/root/PreferencesRootPresenter.kt index 8927b220ed..fd0fe26297 100644 --- a/features/preferences/src/main/java/io/element/android/x/features/preferences/root/PreferencesRootPresenter.kt +++ b/features/preferences/src/main/java/io/element/android/x/features/preferences/root/PreferencesRootPresenter.kt @@ -1,42 +1,26 @@ package io.element.android.x.features.preferences.root import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import io.element.android.x.architecture.Async import io.element.android.x.architecture.Presenter -import io.element.android.x.architecture.SharedFlowHolder -import io.element.android.x.features.logout.LogoutPreferenceEvents import io.element.android.x.features.logout.LogoutPreferencePresenter -import io.element.android.x.features.rageshake.preferences.RageshakePreferencesEvents import io.element.android.x.features.rageshake.preferences.RageshakePreferencesPresenter -import kotlinx.coroutines.flow.Flow import javax.inject.Inject class PreferencesRootPresenter @Inject constructor( private val logoutPresenter: LogoutPreferencePresenter, private val rageshakePresenter: RageshakePreferencesPresenter, -) : Presenter { - - private val logoutEventsFlow = SharedFlowHolder() - private val rageshakeEventsFlow = SharedFlowHolder() +) : Presenter { @Composable - override fun present(events: Flow): PreferencesRootState { - val logoutState = logoutPresenter.present(events = logoutEventsFlow.asSharedFlow()) - val rageshakeState = rageshakePresenter.present(events = rageshakeEventsFlow.asSharedFlow()) - LaunchedEffect(Unit) { - events.collect { event -> - when (event) { - PreferencesRootEvents.Logout -> logoutEventsFlow.emit(LogoutPreferenceEvents.Logout) - is PreferencesRootEvents.SetRageshakeEnabled -> rageshakeEventsFlow.emit(RageshakePreferencesEvents.SetIsEnabled(event.enabled)) - is PreferencesRootEvents.SetRageshakeSensitivity -> rageshakeEventsFlow.emit(RageshakePreferencesEvents.SetSensitivity(event.sensitivity)) - } - } - } + override fun present(): PreferencesRootState { + val logoutState = logoutPresenter.present() + val rageshakeState = rageshakePresenter.present() + return PreferencesRootState( logoutState = logoutState, rageshakeState = rageshakeState, - myUser = Async.Uninitialized + myUser = Async.Uninitialized, ) } } diff --git a/features/preferences/src/main/java/io/element/android/x/features/preferences/root/PreferencesRootView.kt b/features/preferences/src/main/java/io/element/android/x/features/preferences/root/PreferencesRootView.kt index 30a116ce8d..fc0cbd3331 100644 --- a/features/preferences/src/main/java/io/element/android/x/features/preferences/root/PreferencesRootView.kt +++ b/features/preferences/src/main/java/io/element/android/x/features/preferences/root/PreferencesRootView.kt @@ -10,6 +10,7 @@ import io.element.android.x.element.resources.R import io.element.android.x.features.logout.LogoutPreferenceState import io.element.android.x.features.logout.LogoutPreferenceView import io.element.android.x.features.preferences.user.UserPreferences +import io.element.android.x.features.rageshake.preferences.RageshakePreferencesEvents import io.element.android.x.features.rageshake.preferences.RageshakePreferencesState import io.element.android.x.features.rageshake.preferences.RageshakePreferencesView @@ -18,10 +19,7 @@ fun PreferencesRootView( state: PreferencesRootState, modifier: Modifier = Modifier, onBackPressed: () -> Unit = {}, - onLogoutClicked: () -> Unit = {}, onOpenRageShake: () -> Unit = {}, - onRageshakeEnabledChanged: (Boolean) -> Unit = {}, - onRageshakeSensitivityChanged: (Float) -> Unit = {}, ) { // TODO Hierarchy! // Include pref from other modules @@ -34,12 +32,9 @@ fun PreferencesRootView( RageshakePreferencesView( state = state.rageshakeState, onOpenRageshake = onOpenRageShake, - onSensitivityChanged = onRageshakeSensitivityChanged, - onIsEnabledChanged = onRageshakeEnabledChanged, ) LogoutPreferenceView( state = state.logoutState, - onLogoutClicked = onLogoutClicked, ) } } 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 index 088179f939..c7bf7114c3 100644 --- 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 @@ -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().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 4127395ce5..16a95ad6f0 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 @@ -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 { +) : Presenter { private class BugReporterUploadListener( private val sendingProgress: MutableState, @@ -56,7 +53,7 @@ class BugReportPresenter @Inject constructor( } @Composable - override fun present(events: Flow): 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, 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 ) 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 cc7fad74ca..80912a7923 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 @@ -26,6 +26,7 @@ data class BugReportState( val screenshotUri: String? = null, val sendingProgress: Float = 0F, val sending: Async = Async.Uninitialized, + val eventSink: (BugReportEvents) -> Unit = {} ) { val submitEnabled = formState.description.length > 10 && sending !is Async.Loading diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportView.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportView.kt index 0b5ab39365..06fa9dd539 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportView.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportView.kt @@ -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 } } } 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 716bc19bf5..0153227c5e 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 @@ -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 { +class CrashDetectionPresenter @Inject constructor(private val crashDataStore: CrashDataStore) : Presenter { @Composable - override fun present(events: Flow): 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 ) } diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionScreen.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionScreen.kt index e7da8ba4ab..a0d764a286 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionScreen.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionScreen.kt @@ -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, ) } } diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionState.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionState.kt index 1ce7142735..52774c4cc9 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionState.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/ui/CrashDetectionState.kt @@ -18,4 +18,5 @@ package io.element.android.x.features.rageshake.crash.ui data class CrashDetectionState( val crashDetected: Boolean = false, + val eventSink: (CrashDetectionEvents) -> Unit = {} ) 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 c3bd6b9ca7..49d6097ed8 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 @@ -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 { - - private val preferencesEventsFlow = SharedFlowHolder() +) : Presenter { @Composable - override fun present(events: Flow): 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) 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 f1a3f656cd..d32b326dd3 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 @@ -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 = {} ) 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 7de08d158c..61e9f32150 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 @@ -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() } } 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 3012a6c32f..6a0ed8416a 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 @@ -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 { +) : Presenter { @Composable - override fun present(events: Flow): RageshakePreferencesState { + override fun present(): RageshakePreferencesState { + val localCoroutineScope = rememberCoroutineScope() val isSupported: MutableState = 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 ) } diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferencesState.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferencesState.kt index d58457f620..216a386d5f 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferencesState.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferencesState.kt @@ -4,4 +4,5 @@ data class RageshakePreferencesState( val isEnabled: Boolean = false, val isSupported: Boolean = true, val sensitivity: Float = 0.3f, + val eventSink: (RageshakePreferencesEvents) -> Unit = {}, ) diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferencesView.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferencesView.kt index 7bd29c7580..ac7b2478fc 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferencesView.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferencesView.kt @@ -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") diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListNode.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListNode.kt index 41bccdcb7b..a35acb7345 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListNode.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListNode.kt @@ -30,18 +30,6 @@ class RoomListNode @AssistedInject constructor( private val connector = presenterConnector(presenter) - private fun updateFilter(filter: String) { - connector.emitEvent(RoomListEvents.UpdateFilter(filter)) - } - - private fun updateVisibleRange(range: IntRange) { - connector.emitEvent((RoomListEvents.UpdateVisibleRange(range))) - } - - private fun logout() { - connector.emitEvent(RoomListEvents.Logout) - } - private fun onRoomClicked(roomId: RoomId) { plugins().forEach { it.onRoomClicked(roomId) } } @@ -56,8 +44,6 @@ class RoomListNode @AssistedInject constructor( RoomListView( state = state, onRoomClicked = this::onRoomClicked, - onFilterChanged = this::updateFilter, - onScrollOver = this::updateVisibleRange, onOpenSettings = this::onOpenSettings ) } diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListPresenter.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListPresenter.kt index ee3aa7b6fb..7727adf348 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListPresenter.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListPresenter.kt @@ -7,8 +7,10 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import io.element.android.x.architecture.Presenter import io.element.android.x.core.coroutine.parallelMap import io.element.android.x.designsystem.components.avatar.AvatarData import io.element.android.x.designsystem.components.avatar.AvatarSize @@ -19,13 +21,12 @@ import io.element.android.x.features.roomlist.model.RoomListState import io.element.android.x.matrix.MatrixClient import io.element.android.x.matrix.media.MediaResolver import io.element.android.x.matrix.room.RoomSummary -import io.element.android.x.architecture.Presenter import io.element.android.x.matrix.ui.model.MatrixUser import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import javax.inject.Inject private const val extendedRangeSize = 40 @@ -33,10 +34,10 @@ private const val extendedRangeSize = 40 class RoomListPresenter @Inject constructor( private val client: MatrixClient, private val lastMessageFormatter: LastMessageFormatter, -) : Presenter { +) : Presenter { @Composable - override fun present(events: Flow): RoomListState { + override fun present(): RoomListState { val matrixUser: MutableState = remember { mutableStateOf(null) } @@ -52,14 +53,15 @@ class RoomListPresenter @Inject constructor( } LaunchedEffect(Unit) { initialLoad(matrixUser) - events.collect { event -> - when (event) { - RoomListEvents.Logout -> logout(isLoginOut) - is RoomListEvents.UpdateFilter -> filter = event.newFilter - is RoomListEvents.UpdateVisibleRange -> updateVisibleRange(event.range) - } + } + + fun handleEvents(event: RoomListEvents) { + when (event) { + is RoomListEvents.UpdateFilter -> filter = event.newFilter + is RoomListEvents.UpdateVisibleRange -> updateVisibleRange(event.range) } } + LaunchedEffect(roomSummaries, filter) { filteredRoomSummaries.value = updateFilteredRoomSummaries(roomSummaries, filter) } @@ -67,7 +69,8 @@ class RoomListPresenter @Inject constructor( matrixUser = matrixUser.value, roomList = filteredRoomSummaries.value, filter = filter, - isLoginOut = isLoginOut.value + isLoginOut = isLoginOut.value, + eventSink = ::handleEvents ) } @@ -83,7 +86,7 @@ class RoomListPresenter @Inject constructor( }.toImmutableList() } - private suspend fun initialLoad(matrixUser: MutableState) { + private fun CoroutineScope.initialLoad(matrixUser: MutableState) = launch { val userAvatarUrl = client.loadUserAvatarURLString().getOrNull() val userDisplayName = client.loadUserDisplayName().getOrNull() val avatarData = @@ -100,13 +103,6 @@ class RoomListPresenter @Inject constructor( ) } - private suspend fun logout(isLoginOut: MutableState) { - isLoginOut.value = true - delay(2000) - client.logout() - isLoginOut.value = false - } - private fun updateVisibleRange(range: IntRange) { if (range.isEmpty()) return val midExtendedRangeSize = extendedRangeSize / 2 diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListView.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListView.kt index 97c42f7eeb..4eb09e52b2 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListView.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListView.kt @@ -41,6 +41,7 @@ import io.element.android.x.designsystem.ElementXTheme import io.element.android.x.designsystem.components.avatar.AvatarData import io.element.android.x.features.roomlist.components.RoomListTopBar import io.element.android.x.features.roomlist.components.RoomSummaryRow +import io.element.android.x.features.roomlist.model.RoomListEvents import io.element.android.x.features.roomlist.model.RoomListRoomSummary import io.element.android.x.features.roomlist.model.RoomListState import io.element.android.x.features.roomlist.model.stubbedRoomSummaries @@ -54,19 +55,26 @@ fun RoomListView( state: RoomListState, modifier: Modifier = Modifier, onRoomClicked: (RoomId) -> Unit = {}, - onFilterChanged: (String) -> Unit = {}, onOpenSettings: () -> Unit = {}, - onScrollOver: (IntRange) -> Unit = {}, ) { + + fun onFilterChanged(filter: String){ + state.eventSink(RoomListEvents.UpdateFilter(filter)) + } + + fun onVisibleRangedChanged(range: IntRange){ + state.eventSink(RoomListEvents.UpdateVisibleRange(range)) + } + RoomListView( roomSummaries = state.roomList, matrixUser = state.matrixUser, filter = state.filter, modifier = modifier, onRoomClicked = onRoomClicked, - onFilterChanged = onFilterChanged, + onFilterChanged = ::onFilterChanged, onOpenSettings = onOpenSettings, - onScrollOver = onScrollOver, + onScrollOver = ::onVisibleRangedChanged, ) } diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListEvents.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListEvents.kt index ff8b80decf..c89a87280d 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListEvents.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListEvents.kt @@ -1,7 +1,6 @@ package io.element.android.x.features.roomlist.model sealed interface RoomListEvents { - object Logout : RoomListEvents data class UpdateFilter(val newFilter: String) : RoomListEvents data class UpdateVisibleRange(val range: IntRange): RoomListEvents } diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListState.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListState.kt index 8e5b159676..97ce9b4023 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListState.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListState.kt @@ -10,4 +10,5 @@ data class RoomListState( val roomList: ImmutableList, val filter: String, val isLoginOut: Boolean, + val eventSink: (RoomListEvents) -> Unit = {} ) diff --git a/libraries/architecture/src/main/java/io/element/android/x/architecture/Presenter.kt b/libraries/architecture/src/main/java/io/element/android/x/architecture/Presenter.kt index a0addbfe1a..6926a5f475 100644 --- a/libraries/architecture/src/main/java/io/element/android/x/architecture/Presenter.kt +++ b/libraries/architecture/src/main/java/io/element/android/x/architecture/Presenter.kt @@ -3,7 +3,7 @@ package io.element.android.x.architecture import androidx.compose.runtime.Composable import kotlinx.coroutines.flow.Flow -interface Presenter { +interface Presenter { @Composable - fun present(events: Flow): State + fun present(): State } diff --git a/libraries/architecture/src/main/java/io/element/android/x/architecture/PresenterConnector.kt b/libraries/architecture/src/main/java/io/element/android/x/architecture/PresenterConnector.kt index d30b5047f0..b183c324e8 100644 --- a/libraries/architecture/src/main/java/io/element/android/x/architecture/PresenterConnector.kt +++ b/libraries/architecture/src/main/java/io/element/android/x/architecture/PresenterConnector.kt @@ -8,19 +8,14 @@ import app.cash.molecule.launchMolecule import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow -inline fun LifecycleOwner.presenterConnector(presenter: Presenter): LifecyclePresenterConnector = +inline fun LifecycleOwner.presenterConnector(presenter: Presenter): LifecyclePresenterConnector = LifecyclePresenterConnector(lifecycleOwner = this, presenter = presenter) -class LifecyclePresenterConnector(lifecycleOwner: LifecycleOwner, presenter: Presenter) { +class LifecyclePresenterConnector(lifecycleOwner: LifecycleOwner, presenter: Presenter) { private val moleculeScope = CoroutineScope(lifecycleOwner.lifecycleScope.coroutineContext + AndroidUiDispatcher.Main) - private val eventFlow = SharedFlowHolder() val stateFlow: StateFlow = moleculeScope.launchMolecule(RecompositionClock.Immediate) { - presenter.present(events = eventFlow.asSharedFlow()) - } - - fun emitEvent(event: Event) { - eventFlow.emit(event) + presenter.present() } } diff --git a/libraries/architecture/src/main/java/io/element/android/x/architecture/SharedFlowHolder.kt b/libraries/architecture/src/main/java/io/element/android/x/architecture/SharedFlowHolder.kt index 7d41e261a5..c0933b933e 100644 --- a/libraries/architecture/src/main/java/io/element/android/x/architecture/SharedFlowHolder.kt +++ b/libraries/architecture/src/main/java/io/element/android/x/architecture/SharedFlowHolder.kt @@ -9,4 +9,6 @@ class SharedFlowHolder(capacity: Int = 64) { fun asSharedFlow() = mutableFlow.asSharedFlow() fun emit(data: Data) = mutableFlow.tryEmit(data) + + suspend fun awaitEmit(data: Data) = mutableFlow.emit(data) }