Migrate Preferences to new architecture

This commit is contained in:
ganfra 2023-01-09 19:27:28 +01:00
parent 9e211b5e04
commit ae273bd4ea
26 changed files with 399 additions and 174 deletions

View file

@ -0,0 +1,41 @@
package io.element.android.x.features.preferences
import android.os.Parcelable
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.bumble.appyx.core.composable.Children
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.ParentNode
import com.bumble.appyx.navmodel.backstack.BackStack
import io.element.android.x.architecture.createNode
import io.element.android.x.features.preferences.root.PreferencesRootNode
import kotlinx.parcelize.Parcelize
class PreferencesFlowNode(
buildContext: BuildContext,
private val backstack: BackStack<NavTarget> = BackStack(
initialElement = NavTarget.Root,
savedStateMap = buildContext.savedStateMap,
),
) : ParentNode<PreferencesFlowNode.NavTarget>(
navModel = backstack,
buildContext = buildContext
) {
sealed interface NavTarget : Parcelable {
@Parcelize
object Root : NavTarget
}
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) {
NavTarget.Root -> createNode<PreferencesRootNode>(buildContext)
}
}
@Composable
override fun View(modifier: Modifier) {
Children(navModel = backstack)
}
}

View file

@ -1,66 +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.preferences
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.x.designsystem.components.preferences.PreferenceScreen
import io.element.android.x.element.resources.R as ElementR
import io.element.android.x.features.logout.LogoutPreference
import io.element.android.x.features.preferences.user.UserPreferences
import io.element.android.x.features.rageshake.preferences.RageshakePreferences
@Composable
fun PreferencesScreen(
onBackPressed: () -> Unit = {},
onOpenRageShake: () -> Unit = {},
onSuccessLogout: () -> Unit = {},
) {
// TODO Hierarchy!
// Include pref from other modules
PreferencesContent(
onBackPressed = onBackPressed,
onOpenRageShake = onOpenRageShake,
onSuccessLogout = onSuccessLogout,
)
}
@Composable
fun PreferencesContent(
modifier: Modifier = Modifier,
onBackPressed: () -> Unit = {},
onOpenRageShake: () -> Unit = {},
onSuccessLogout: () -> Unit = {},
) {
PreferenceScreen(
modifier = modifier,
onBackPressed = onBackPressed,
title = stringResource(id = ElementR.string.settings)
) {
UserPreferences()
RageshakePreferences(onOpenRageShake = onOpenRageShake)
LogoutPreference(onSuccessLogout = onSuccessLogout)
}
}
@Preview
@Composable
fun PreferencesContentPreview() {
PreferencesContent()
}

View file

@ -0,0 +1,7 @@
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
}

View file

@ -0,0 +1,48 @@
package io.element.android.x.features.preferences.root
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.x.anvilannotations.ContributesNode
import io.element.android.x.architecture.presenterConnector
import io.element.android.x.di.SessionScope
@ContributesNode(SessionScope::class)
class PreferencesRootNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val presenter: PreferencesRootPresenter,
) : Node(buildContext, plugins = plugins) {
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))
}
@Composable
override fun View(modifier: Modifier) {
val state by presenterConnector.stateFlow.collectAsState()
PreferencesRootView(
state = state,
onLogoutClicked = this::onLogoutClicked,
onBackPressed = this::navigateUp,
onRageshakeEnabledChanged = this::onRageshakeEnabledChanged,
onRageshakeSensitivityChanged = this::onRageshakeSensitivityChanged
)
}
}

View file

@ -0,0 +1,42 @@
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<PreferencesRootState, PreferencesRootEvents> {
private val logoutEventsFlow = SharedFlowHolder<LogoutPreferenceEvents>()
private val rageshakeEventsFlow = SharedFlowHolder<RageshakePreferencesEvents>()
@Composable
override fun present(events: Flow<PreferencesRootEvents>): 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))
}
}
}
return PreferencesRootState(
logoutState = logoutState,
rageshakeState = rageshakeState,
myUser = Async.Uninitialized
)
}
}

View file

@ -0,0 +1,12 @@
package io.element.android.x.features.preferences.root
import io.element.android.x.architecture.Async
import io.element.android.x.features.logout.LogoutPreferenceState
import io.element.android.x.features.rageshake.preferences.RageshakePreferencesState
import io.element.android.x.matrix.ui.model.MatrixUser
data class PreferencesRootState(
val logoutState: LogoutPreferenceState,
val rageshakeState: RageshakePreferencesState,
val myUser: Async<MatrixUser>,
)

View file

@ -0,0 +1,56 @@
package io.element.android.x.features.preferences.root
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.x.architecture.Async
import io.element.android.x.designsystem.components.preferences.PreferenceView
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.RageshakePreferencesState
import io.element.android.x.features.rageshake.preferences.RageshakePreferencesView
@Composable
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
PreferenceView(
modifier = modifier,
onBackPressed = onBackPressed,
title = stringResource(id = R.string.settings)
) {
UserPreferences(state.myUser)
RageshakePreferencesView(
state = state.rageshakeState,
onOpenRageshake = onOpenRageShake,
onSensitivityChanged = onRageshakeSensitivityChanged,
onIsEnabledChanged = onRageshakeEnabledChanged,
)
LogoutPreferenceView(
state = state.logoutState,
onLogoutClicked = onLogoutClicked,
)
}
}
@Preview
@Composable
fun PreferencesContentPreview() {
val state = PreferencesRootState(
logoutState = LogoutPreferenceState(),
rageshakeState = RageshakePreferencesState(),
myUser = Async.Uninitialized
)
PreferencesRootView(state)
}

View file

@ -19,26 +19,22 @@ package io.element.android.x.features.preferences.user
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.airbnb.mvrx.compose.collectAsState
import com.airbnb.mvrx.compose.mavericksViewModel
import io.element.android.x.architecture.Async
import io.element.android.x.matrix.ui.components.MatrixUserHeader
import io.element.android.x.matrix.ui.viewmodels.user.UserViewModel
import io.element.android.x.matrix.ui.viewmodels.user.UserViewState
import io.element.android.x.matrix.ui.model.MatrixUser
@Composable
fun UserPreferences(
user: Async<MatrixUser>,
modifier: Modifier = Modifier,
viewModel: UserViewModel = mavericksViewModel(),
) {
val user by viewModel.collectAsState(UserViewState::user)
when (user()) {
when (val userData = user.dataOrNull()) {
null -> Spacer(modifier = modifier.height(1.dp))
else -> MatrixUserHeader(
modifier = modifier,
matrixUser = user.invoke()!!
matrixUser = userData
)
}
}