feature(security&privacy): support KnockRestricted join rule
This commit is contained in:
@@ -19,6 +19,7 @@ import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||
import com.bumble.appyx.navmodel.backstack.activeElement
|
||||
import com.bumble.appyx.navmodel.backstack.operation.pop
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
@@ -66,7 +67,7 @@ class SecurityAndPrivacyFlowNode(
|
||||
data object EditRoomAddress : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object ManageAuthorizedSpaces: NavTarget
|
||||
data class ManageAuthorizedSpaces(val forKnockRestricted: Boolean = false) : NavTarget
|
||||
}
|
||||
|
||||
private val callback: SecurityAndPrivacyEntryPoint.Callback = callback()
|
||||
@@ -94,9 +95,10 @@ class SecurityAndPrivacyFlowNode(
|
||||
commonLifecycle.coroutineScope.launch {
|
||||
val authorizedSpacesData = securityAndPrivacyNode.getAuthorizedSpacesData()
|
||||
val selectedSpaces = manageAuthorizedSpacesNode.waitForCompletion(authorizedSpacesData)
|
||||
val forKnock = (backstack.activeElement as? NavTarget.ManageAuthorizedSpaces)?.forKnockRestricted ?: false
|
||||
withContext(NonCancellable) {
|
||||
navigator.closeManageAuthorizedSpaces()
|
||||
securityAndPrivacyNode.onAuthorizedSpacesSelected(selectedSpaces)
|
||||
securityAndPrivacyNode.onAuthorizedSpacesSelected(selectedSpaces, forKnock = forKnock)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,7 +112,7 @@ class SecurityAndPrivacyFlowNode(
|
||||
NavTarget.EditRoomAddress -> {
|
||||
createNode<EditRoomAddressNode>(buildContext, plugins = listOf(navigator))
|
||||
}
|
||||
NavTarget.ManageAuthorizedSpaces -> {
|
||||
is NavTarget.ManageAuthorizedSpaces -> {
|
||||
createNode<ManageAuthorizedSpacesNode>(buildContext, plugins = listOf(navigator))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ interface SecurityAndPrivacyNavigator : Plugin {
|
||||
fun onDone()
|
||||
fun openEditRoomAddress()
|
||||
fun closeEditRoomAddress()
|
||||
fun openManageAuthorizedSpaces()
|
||||
fun openManageAuthorizedSpaces(forKnockRestricted: Boolean = false)
|
||||
fun closeManageAuthorizedSpaces()
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ class BackstackSecurityAndPrivacyNavigator(
|
||||
backStack.pop()
|
||||
}
|
||||
|
||||
override fun openManageAuthorizedSpaces() {
|
||||
backStack.push(SecurityAndPrivacyFlowNode.NavTarget.ManageAuthorizedSpaces)
|
||||
override fun openManageAuthorizedSpaces(forKnockRestricted: Boolean) {
|
||||
backStack.push(SecurityAndPrivacyFlowNode.NavTarget.ManageAuthorizedSpaces(forKnockRestricted))
|
||||
}
|
||||
|
||||
override fun closeManageAuthorizedSpaces() {
|
||||
|
||||
@@ -17,6 +17,8 @@ sealed interface SecurityAndPrivacyEvent {
|
||||
data class ChangeRoomAccess(val roomAccess: SecurityAndPrivacyRoomAccess) : SecurityAndPrivacyEvent
|
||||
// Special case for "Space Members"
|
||||
data object SelectSpaceMemberAccess : SecurityAndPrivacyEvent
|
||||
// Special case for "Ask to join with Space Members"
|
||||
data object SelectAskToJoinWithSpaceMembersAccess : SecurityAndPrivacyEvent
|
||||
data object ToggleEncryptionState : SecurityAndPrivacyEvent
|
||||
data object CancelEnableEncryption : SecurityAndPrivacyEvent
|
||||
data object ConfirmEnableEncryption : SecurityAndPrivacyEvent
|
||||
|
||||
@@ -50,10 +50,13 @@ class SecurityAndPrivacyNode(
|
||||
return stateFlow.value.getAuthorizedSpacesSelection()
|
||||
}
|
||||
|
||||
fun onAuthorizedSpacesSelected(selectedSpaces: ImmutableList<RoomId>) {
|
||||
stateFlow.value.eventSink(
|
||||
SecurityAndPrivacyEvent.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.SpaceMember(selectedSpaces))
|
||||
)
|
||||
fun onAuthorizedSpacesSelected(selectedSpaces: ImmutableList<RoomId>, forKnock: Boolean) {
|
||||
val roomAccess = if (forKnock) {
|
||||
SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember(selectedSpaces)
|
||||
} else {
|
||||
SecurityAndPrivacyRoomAccess.SpaceMember(selectedSpaces)
|
||||
}
|
||||
stateFlow.value.eventSink(SecurityAndPrivacyEvent.ChangeRoomAccess(roomAccess))
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -200,6 +200,10 @@ class SecurityAndPrivacyPresenter(
|
||||
spaceIds = editedSettings.roomAccess.spaceIds(),
|
||||
editedAccess = editedRoomAccess,
|
||||
)
|
||||
SecurityAndPrivacyEvent.SelectAskToJoinWithSpaceMembersAccess -> handleAskToJoinWithSpaceMembersAccessSelection(
|
||||
spaceSelectionMode = spaceSelectionMode,
|
||||
editedAccess = editedRoomAccess,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,7 +258,7 @@ class SecurityAndPrivacyPresenter(
|
||||
}
|
||||
when (spaceSelectionMode) {
|
||||
is SpaceSelectionMode.None -> Unit
|
||||
is SpaceSelectionMode.Multiple -> navigator.openManageAuthorizedSpaces()
|
||||
is SpaceSelectionMode.Multiple -> navigator.openManageAuthorizedSpaces(forKnockRestricted = false)
|
||||
is SpaceSelectionMode.Single -> {
|
||||
val newRoomAccess = SecurityAndPrivacyRoomAccess.SpaceMember(
|
||||
spaceIds = persistentListOf(spaceSelectionMode.spaceId)
|
||||
@@ -264,6 +268,25 @@ class SecurityAndPrivacyPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAskToJoinWithSpaceMembersAccessSelection(
|
||||
spaceSelectionMode: SpaceSelectionMode,
|
||||
editedAccess: MutableState<SecurityAndPrivacyRoomAccess>,
|
||||
) {
|
||||
if (editedAccess.value is SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember) {
|
||||
return
|
||||
}
|
||||
when (spaceSelectionMode) {
|
||||
is SpaceSelectionMode.None -> Unit
|
||||
is SpaceSelectionMode.Multiple -> navigator.openManageAuthorizedSpaces(forKnockRestricted = true)
|
||||
is SpaceSelectionMode.Single -> {
|
||||
val newRoomAccess = SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember(
|
||||
spaceIds = persistentListOf(spaceSelectionMode.spaceId)
|
||||
)
|
||||
editedAccess.value = newRoomAccess
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSpaceSelectionMode(
|
||||
selectableJoinedSpaces: Set<SpaceRoom>,
|
||||
savedAccess: SecurityAndPrivacyRoomAccess,
|
||||
@@ -328,6 +351,7 @@ class SecurityAndPrivacyPresenter(
|
||||
// the room should be automatically made invisible (private) in the room directory.
|
||||
val editedIsVisibleInRoomDirectory = when (editedSettings.roomAccess) {
|
||||
SecurityAndPrivacyRoomAccess.AskToJoin,
|
||||
is SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember,
|
||||
SecurityAndPrivacyRoomAccess.Anyone -> editedSettings.isVisibleInRoomDirectory.dataOrNull()
|
||||
else -> false
|
||||
}
|
||||
@@ -365,7 +389,13 @@ class SecurityAndPrivacyPresenter(
|
||||
private fun JoinRule?.map(): SecurityAndPrivacyRoomAccess {
|
||||
return when (this) {
|
||||
JoinRule.Public -> SecurityAndPrivacyRoomAccess.Anyone
|
||||
JoinRule.Knock, is JoinRule.KnockRestricted -> SecurityAndPrivacyRoomAccess.AskToJoin
|
||||
JoinRule.Knock -> SecurityAndPrivacyRoomAccess.AskToJoin
|
||||
is JoinRule.KnockRestricted -> SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember(
|
||||
spaceIds = this.rules
|
||||
.filterIsInstance<AllowRule.RoomMembership>()
|
||||
.map { it.roomId }
|
||||
.toImmutableList()
|
||||
)
|
||||
is JoinRule.Restricted -> SecurityAndPrivacyRoomAccess.SpaceMember(
|
||||
spaceIds = this.rules
|
||||
.filterIsInstance<AllowRule.RoomMembership>()
|
||||
@@ -388,6 +418,9 @@ private fun SecurityAndPrivacyRoomAccess.map(): JoinRule? {
|
||||
is SecurityAndPrivacyRoomAccess.SpaceMember -> JoinRule.Restricted(
|
||||
rules = this.spaceIds.map { AllowRule.RoomMembership(it) }.toImmutableList()
|
||||
)
|
||||
is SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember -> JoinRule.KnockRestricted(
|
||||
rules = this.spaceIds.map { AllowRule.RoomMembership(it) }.toImmutableList()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,14 +46,25 @@ data class SecurityAndPrivacyState(
|
||||
// - SpaceMember option is selectable (ie. the FF is enabled and there is at least one space to select)
|
||||
val showSpaceMemberOption = savedSettings.roomAccess is SecurityAndPrivacyRoomAccess.SpaceMember || isSpaceMemberSelectable
|
||||
|
||||
val showManageSpaceAction = spaceSelectionMode is SpaceSelectionMode.Multiple && editedSettings.roomAccess is SecurityAndPrivacyRoomAccess.SpaceMember
|
||||
val showManageSpaceFooter = spaceSelectionMode is SpaceSelectionMode.Multiple &&
|
||||
(editedSettings.roomAccess is SecurityAndPrivacyRoomAccess.SpaceMember ||
|
||||
editedSettings.roomAccess is SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember)
|
||||
|
||||
val isAskToJoinSelectable = isKnockEnabled
|
||||
|
||||
// Show Ask to join option in two cases:
|
||||
// - the Knock FF is enabled
|
||||
// - AskToJoin is the current saved value
|
||||
val showAskToJoinOption = savedSettings.roomAccess == SecurityAndPrivacyRoomAccess.AskToJoin || isAskToJoinSelectable
|
||||
val isAskToJoinWithSpaceMembersSelectable = isAskToJoinSelectable && isSpaceMemberSelectable
|
||||
|
||||
// Show Ask to join option only when:
|
||||
// - AskToJoin is the current saved value (legacy), OR
|
||||
// - Knock FF enabled BUT (SpaceSettings FF disabled OR no spaces available)
|
||||
val showAskToJoinOption = savedSettings.roomAccess == SecurityAndPrivacyRoomAccess.AskToJoin ||
|
||||
(isAskToJoinSelectable && !isAskToJoinWithSpaceMembersSelectable)
|
||||
|
||||
// Show AskToJoinWithSpaceMember option when:
|
||||
// - It's the current saved value, OR
|
||||
// - Both FFs enabled AND spaces available
|
||||
val showAskToJoinWithSpaceMemberOption = savedSettings.roomAccess is SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember ||
|
||||
isAskToJoinWithSpaceMembersSelectable
|
||||
|
||||
val canBeSaved = savedSettings != editedSettings
|
||||
|
||||
@@ -94,6 +105,22 @@ data class SecurityAndPrivacyState(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun askToJoinWithSpaceMembersDescription(): String {
|
||||
return if (isAskToJoinWithSpaceMembersSelectable) {
|
||||
when (spaceSelectionMode) {
|
||||
is SpaceSelectionMode.Single -> {
|
||||
val spaceName = spaceSelectionMode.spaceRoom?.displayName ?: spaceSelectionMode.spaceId.value
|
||||
stringResource(R.string.screen_security_and_privacy_ask_to_join_single_space_members_option_description, spaceName)
|
||||
}
|
||||
is SpaceSelectionMode.None,
|
||||
is SpaceSelectionMode.Multiple -> stringResource(R.string.screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description)
|
||||
}
|
||||
} else {
|
||||
stringResource(R.string.screen_security_and_privacy_ask_to_join_option_description)
|
||||
}
|
||||
}
|
||||
|
||||
fun getAuthorizedSpacesSelection(): AuthorizedSpacesSelection {
|
||||
return AuthorizedSpacesSelection(
|
||||
joinedSpaces = selectableJoinedSpaces.toImmutableList(),
|
||||
@@ -102,6 +129,7 @@ data class SecurityAndPrivacyState(
|
||||
}.toImmutableList(),
|
||||
initialSelectedIds = when (editedSettings.roomAccess) {
|
||||
is SecurityAndPrivacyRoomAccess.SpaceMember -> editedSettings.roomAccess.spaceIds
|
||||
is SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember -> editedSettings.roomAccess.spaceIds
|
||||
else -> savedSettings.roomAccess.spaceIds()
|
||||
}
|
||||
)
|
||||
@@ -145,17 +173,19 @@ sealed interface SecurityAndPrivacyRoomAccess {
|
||||
data object AskToJoin : SecurityAndPrivacyRoomAccess
|
||||
data object Anyone : SecurityAndPrivacyRoomAccess
|
||||
data class SpaceMember(val spaceIds: ImmutableList<RoomId>) : SecurityAndPrivacyRoomAccess
|
||||
data class AskToJoinWithSpaceMember(val spaceIds: ImmutableList<RoomId>) : SecurityAndPrivacyRoomAccess
|
||||
|
||||
fun canConfigureRoomVisibility(): Boolean {
|
||||
return when (this) {
|
||||
InviteOnly, is SpaceMember -> false
|
||||
AskToJoin, Anyone -> true
|
||||
AskToJoin, Anyone, is AskToJoinWithSpaceMember -> true
|
||||
}
|
||||
}
|
||||
|
||||
fun spaceIds(): ImmutableList<RoomId> {
|
||||
return when (this) {
|
||||
is SpaceMember -> spaceIds
|
||||
is AskToJoinWithSpaceMember -> spaceIds
|
||||
else -> persistentListOf()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,19 @@ private fun commonSecurityAndPrivacyStates(isSpace: Boolean): Sequence<SecurityA
|
||||
isSpace = isSpace,
|
||||
isKnockEnabled = false,
|
||||
),
|
||||
aSecurityAndPrivacyState(
|
||||
editedSettings = aSecurityAndPrivacySettings(
|
||||
roomAccess = SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember(persistentListOf()),
|
||||
),
|
||||
isSpace = isSpace,
|
||||
),
|
||||
aSecurityAndPrivacyState(
|
||||
savedSettings = aSecurityAndPrivacySettings(
|
||||
roomAccess = SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember(persistentListOf())
|
||||
),
|
||||
isSpace = isSpace,
|
||||
isKnockEnabled = false,
|
||||
),
|
||||
aSecurityAndPrivacyState(
|
||||
editedSettings = aSecurityAndPrivacySettings(
|
||||
roomAccess = SecurityAndPrivacyRoomAccess.Anyone,
|
||||
|
||||
@@ -220,6 +220,10 @@ private fun RoomAccessSection(
|
||||
state.eventSink(SecurityAndPrivacyEvent.SelectSpaceMemberAccess)
|
||||
}
|
||||
|
||||
fun onAskToJoinWithSpaceMembersClick() {
|
||||
state.eventSink(SecurityAndPrivacyEvent.SelectAskToJoinWithSpaceMembersAccess)
|
||||
}
|
||||
|
||||
fun onManageSpacesClick() {
|
||||
state.eventSink(SecurityAndPrivacyEvent.ManageAuthorizedSpaces)
|
||||
}
|
||||
@@ -235,7 +239,7 @@ private fun RoomAccessSection(
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Public())),
|
||||
onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.Anyone) },
|
||||
)
|
||||
if (state.showSpaceMemberOption)
|
||||
if (state.showSpaceMemberOption) {
|
||||
ListItem(
|
||||
headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_space_members_option_title)) },
|
||||
supportingContent = {
|
||||
@@ -246,6 +250,7 @@ private fun RoomAccessSection(
|
||||
onClick = ::onSpaceMemberAccessClick,
|
||||
enabled = state.isSpaceMemberSelectable,
|
||||
)
|
||||
}
|
||||
if (state.showAskToJoinOption) {
|
||||
ListItem(
|
||||
headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_ask_to_join_option_title)) },
|
||||
@@ -256,6 +261,16 @@ private fun RoomAccessSection(
|
||||
enabled = state.isAskToJoinSelectable,
|
||||
)
|
||||
}
|
||||
if (state.showAskToJoinWithSpaceMemberOption) {
|
||||
ListItem(
|
||||
headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_ask_to_join_option_title)) },
|
||||
supportingContent = { Text(text = state.askToJoinWithSpaceMembersDescription()) },
|
||||
trailingContent = ListItemContent.RadioButton(selected = edited is SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember),
|
||||
onClick = ::onAskToJoinWithSpaceMembersClick,
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.UserAdd())),
|
||||
enabled = state.isAskToJoinWithSpaceMembersSelectable,
|
||||
)
|
||||
}
|
||||
ListItem(
|
||||
headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_invite_only_option_title)) },
|
||||
supportingContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_invite_only_option_description)) },
|
||||
@@ -263,7 +278,7 @@ private fun RoomAccessSection(
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Lock())),
|
||||
onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.InviteOnly) },
|
||||
)
|
||||
if (state.showManageSpaceAction) {
|
||||
if (state.showManageSpaceFooter) {
|
||||
val footerText = stringWithLink(
|
||||
textRes = R.string.screen_security_and_privacy_room_access_footer,
|
||||
url = stringResource(R.string.screen_security_and_privacy_room_access_footer_manage_spaces_action),
|
||||
|
||||
@@ -14,6 +14,8 @@ class FakeSecurityAndPrivacyNavigator(
|
||||
private val onDoneLambda: () -> Unit = { lambdaError() },
|
||||
private val openEditRoomAddressLambda: () -> Unit = { lambdaError() },
|
||||
private val closeEditRoomAddressLambda: () -> Unit = { lambdaError() },
|
||||
private val openManageAuthorizedSpacesLambda: (Boolean) -> Unit = { lambdaError() },
|
||||
private val closeManageAuthorizedSpacesLambda: () -> Unit = { lambdaError() },
|
||||
) : SecurityAndPrivacyNavigator {
|
||||
override fun onDone() {
|
||||
onDoneLambda()
|
||||
@@ -26,4 +28,12 @@ class FakeSecurityAndPrivacyNavigator(
|
||||
override fun closeEditRoomAddress() {
|
||||
closeEditRoomAddressLambda()
|
||||
}
|
||||
|
||||
override fun openManageAuthorizedSpaces(forKnockRestricted: Boolean) {
|
||||
openManageAuthorizedSpacesLambda(forKnockRestricted)
|
||||
}
|
||||
|
||||
override fun closeManageAuthorizedSpaces() {
|
||||
closeManageAuthorizedSpacesLambda()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user