Merge branch 'develop' into kaylendog/history-sharing/alert
This commit is contained in:
commit
7abbb1bb19
271 changed files with 1991 additions and 1329 deletions
83
CHANGES.md
83
CHANGES.md
|
|
@ -1,3 +1,86 @@
|
||||||
|
Changes in Element X v25.11.2
|
||||||
|
=============================
|
||||||
|
|
||||||
|
<!-- Release notes generated using configuration in .github/release.yml at v25.11.2 -->
|
||||||
|
|
||||||
|
## What's Changed
|
||||||
|
### ✨ Features
|
||||||
|
* Enable access to security and privacy by @bmarty in https://github.com/element-hq/element-x-android/pull/5566
|
||||||
|
* Add ability to forward a media from the media viewer and the gallery by @bmarty in https://github.com/element-hq/element-x-android/pull/5622
|
||||||
|
* Split notifications for messages in threads by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5595
|
||||||
|
### 🙌 Improvements
|
||||||
|
* Enable `SyncNotificationsWithWorkManager` in nightly and debug builds by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5573
|
||||||
|
* Confirm exit without saving change in room details edit screen by @bmarty in https://github.com/element-hq/element-x-android/pull/5618
|
||||||
|
* Space : add view members entry by @ganfra in https://github.com/element-hq/element-x-android/pull/5619
|
||||||
|
* Update notification sound by @bmarty in https://github.com/element-hq/element-x-android/pull/5667
|
||||||
|
* Use the new notification sound only on debug and nightly build by @bmarty in https://github.com/element-hq/element-x-android/pull/5673
|
||||||
|
* Make sure we know the session verification state before showing the options to verify the session by @bmarty in https://github.com/element-hq/element-x-android/pull/5677
|
||||||
|
### 🐛 Bugfixes
|
||||||
|
* Improve how brand color is applied. by @bmarty in https://github.com/element-hq/element-x-android/pull/5584
|
||||||
|
* Improve wellknown retrieval API by @bmarty in https://github.com/element-hq/element-x-android/pull/5587
|
||||||
|
* Clearing the room list search clears the search term too by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5603
|
||||||
|
* Delete pin code only when the last session is deleted by @bmarty in https://github.com/element-hq/element-x-android/pull/5600
|
||||||
|
* Fix issues with WorkManager on Android 12 and below by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5606
|
||||||
|
* Fix marking a room as read re-instantiates its timeline by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5628
|
||||||
|
* Display only valid emojis in recent emoji list by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5612
|
||||||
|
* Fix navigation issue. by @bmarty in https://github.com/element-hq/element-x-android/pull/5666
|
||||||
|
* Fix forward events from media viewer from pinned media timeline by @bmarty in https://github.com/element-hq/element-x-android/pull/5669
|
||||||
|
* Try fixing 'Timeline Event object has already been destroyed' by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5675
|
||||||
|
* Use the SDK Client to check whether a homeserver is compatible by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5664
|
||||||
|
### 🗣 Translations
|
||||||
|
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/5610
|
||||||
|
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/5662
|
||||||
|
### 🧱 Build
|
||||||
|
* Remove `@Inject`, not necessary anymore when class is annotated with `@ContributesBinding` by @bmarty in https://github.com/element-hq/element-x-android/pull/5589
|
||||||
|
* Upgrade ktlint to 1.7.1 and ensure Renovate will upgrade the version by @bmarty in https://github.com/element-hq/element-x-android/pull/5638
|
||||||
|
* Improve architecture around Nodes by @bmarty in https://github.com/element-hq/element-x-android/pull/5641
|
||||||
|
* Move dependencies block out of the android block. by @bmarty in https://github.com/element-hq/element-x-android/pull/5674
|
||||||
|
* Always use the handleEvent(s) function the same way. by @bmarty in https://github.com/element-hq/element-x-android/pull/5672
|
||||||
|
### Dependency upgrades
|
||||||
|
* fix(deps): update metro to v0.7.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5576
|
||||||
|
* fix(deps): update dependencyanalysis to v3.2.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5577
|
||||||
|
* fix(deps): update dependency io.sentry:sentry-android to v8.24.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5586
|
||||||
|
* fix(deps): update dependency androidx.work:work-runtime-ktx to v2.11.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5590
|
||||||
|
* fix(deps): update dependency com.posthog:posthog-android to v3.25.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5594
|
||||||
|
* fix(deps): update dependency com.google.crypto.tink:tink-android to v1.19.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5572
|
||||||
|
* Update plugin sonarqube to v7.0.1.6134 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5605
|
||||||
|
* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.10.28 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5620
|
||||||
|
* fix(deps): update dependencyanalysis to v3.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5602
|
||||||
|
* fix(deps): update dependency com.github.matrix-org:matrix-analytics-events to v0.29.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5621
|
||||||
|
* fix(deps): update dependencyanalysis to v3.4.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5624
|
||||||
|
* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.10.29 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5625
|
||||||
|
* fix(deps): update dependency io.sentry:sentry-android to v8.25.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5629
|
||||||
|
* fix(deps): update dependencyanalysis to v3.4.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5642
|
||||||
|
* fix(deps): update dependency com.squareup.okhttp3:okhttp-bom to v5.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5644
|
||||||
|
* chore(deps): update danger/danger-js action to v13.0.5 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5652
|
||||||
|
* fix(deps): update dependency com.google.firebase:firebase-bom to v34.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5643
|
||||||
|
* fix(deps): update firebaseappdistribution to v5.2.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5640
|
||||||
|
* fix(deps): update metro to v0.7.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5663
|
||||||
|
* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.10.31 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5657
|
||||||
|
* Update GitHub Artifact Actions (major) by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5609
|
||||||
|
* Update dependency io.element.android:element-call-embedded to v0.16.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5598
|
||||||
|
* Update roborazzi to v1.51.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5676
|
||||||
|
* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.11.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5681
|
||||||
|
* fix(deps): update metro to v0.7.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5683
|
||||||
|
### Others
|
||||||
|
* Improve code around Element .well-known configuration by @bmarty in https://github.com/element-hq/element-x-android/pull/5565
|
||||||
|
* misc: display offline banner for all LoggedIn screens by @ganfra in https://github.com/element-hq/element-x-android/pull/5574
|
||||||
|
* Remove icon preview duplicate by @bmarty in https://github.com/element-hq/element-x-android/pull/5588
|
||||||
|
* Remove application navigation state usage in the push module by @bmarty in https://github.com/element-hq/element-x-android/pull/5596
|
||||||
|
* Design : update Home TopBar and RoomList Filters by @ganfra in https://github.com/element-hq/element-x-android/pull/5599
|
||||||
|
* Add missing tests on the analytic modules by @bmarty in https://github.com/element-hq/element-x-android/pull/5604
|
||||||
|
* design(space): let SpaceRoomItemView divider be full width by @ganfra in https://github.com/element-hq/element-x-android/pull/5597
|
||||||
|
* Update notification style by @bmarty in https://github.com/element-hq/element-x-android/pull/5607
|
||||||
|
* Improve how data is handled for the WorkManager. by @bmarty in https://github.com/element-hq/element-x-android/pull/5592
|
||||||
|
* Revert "Make sure declining a call stops observing the ringing call state" by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5615
|
||||||
|
* Misc : space flow inject room by @ganfra in https://github.com/element-hq/element-x-android/pull/5614
|
||||||
|
* Enable `SyncNotificationsWithWorkManager` by default in release mode apps too by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5646
|
||||||
|
* Revert "Update notification sound" by @bmarty in https://github.com/element-hq/element-x-android/pull/5671
|
||||||
|
* Introduce new query to count accounts by @bmarty in https://github.com/element-hq/element-x-android/pull/5678
|
||||||
|
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.11.0...v25.11.2
|
||||||
|
|
||||||
Changes in Element X v25.11.0
|
Changes in Element X v25.11.0
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ android {
|
||||||
value = if (isEnterpriseBuild) {
|
value = if (isEnterpriseBuild) {
|
||||||
BuildTimeConfig.BUG_REPORT_URL ?: ""
|
BuildTimeConfig.BUG_REPORT_URL ?: ""
|
||||||
} else {
|
} else {
|
||||||
"https://riot.im/bugreports/submit"
|
"https://rageshakes.element.io/api/submit"
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
buildConfigFieldStr(
|
buildConfigFieldStr(
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ import io.element.android.features.joinroom.api.JoinRoomEntryPoint
|
||||||
import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint
|
import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint
|
||||||
import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint.Params
|
import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint.Params
|
||||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||||
import io.element.android.features.space.api.SpaceEntryPoint
|
|
||||||
import io.element.android.libraries.architecture.BackstackView
|
import io.element.android.libraries.architecture.BackstackView
|
||||||
import io.element.android.libraries.architecture.BaseFlowNode
|
import io.element.android.libraries.architecture.BaseFlowNode
|
||||||
import io.element.android.libraries.architecture.NodeInputs
|
import io.element.android.libraries.architecture.NodeInputs
|
||||||
|
|
@ -70,7 +69,6 @@ class RoomFlowNode(
|
||||||
private val joinRoomEntryPoint: JoinRoomEntryPoint,
|
private val joinRoomEntryPoint: JoinRoomEntryPoint,
|
||||||
private val roomAliasResolverEntryPoint: RoomAliasResolverEntryPoint,
|
private val roomAliasResolverEntryPoint: RoomAliasResolverEntryPoint,
|
||||||
private val membershipObserver: RoomMembershipObserver,
|
private val membershipObserver: RoomMembershipObserver,
|
||||||
private val spaceEntryPoint: SpaceEntryPoint,
|
|
||||||
) : BaseFlowNode<RoomFlowNode.NavTarget>(
|
) : BaseFlowNode<RoomFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
backstack = BackStack(
|
||||||
initialElement = NavTarget.Loading,
|
initialElement = NavTarget.Loading,
|
||||||
|
|
@ -105,9 +103,6 @@ class RoomFlowNode(
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class JoinedRoom(val roomId: RoomId) : NavTarget
|
data class JoinedRoom(val roomId: RoomId) : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
data class JoinedSpace(val spaceId: RoomId) : NavTarget
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBuilt() {
|
override fun onBuilt() {
|
||||||
|
|
@ -209,15 +204,6 @@ class RoomFlowNode(
|
||||||
)
|
)
|
||||||
createNode<JoinedRoomFlowNode>(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback)
|
createNode<JoinedRoomFlowNode>(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback)
|
||||||
}
|
}
|
||||||
is NavTarget.JoinedSpace -> {
|
|
||||||
val spaceCallback = plugins<SpaceEntryPoint.Callback>().single()
|
|
||||||
spaceEntryPoint.createNode(
|
|
||||||
parentNode = this,
|
|
||||||
buildContext = buildContext,
|
|
||||||
inputs = SpaceEntryPoint.Inputs(roomId = navTarget.spaceId),
|
|
||||||
callback = spaceCallback,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -197,10 +197,6 @@ class JoinedRoomLoadedFlowNode(
|
||||||
callback.navigateToRoom(roomId, viaParameters)
|
callback.navigateToRoom(roomId, viaParameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun navigateToRoomDetails() {
|
|
||||||
backstack.push(NavTarget.RoomDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun navigateToRoomMemberList() {
|
override fun navigateToRoomMemberList() {
|
||||||
backstack.push(NavTarget.RoomMemberList)
|
backstack.push(NavTarget.RoomMemberList)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
fastlane/metadata/android/en-US/changelogs/202511020.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/202511020.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: bug fixes and improvements.
|
||||||
|
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||||
|
|
@ -93,6 +93,7 @@ dependencies {
|
||||||
testImplementation(projects.libraries.featureflag.test)
|
testImplementation(projects.libraries.featureflag.test)
|
||||||
testImplementation(projects.libraries.preferences.test)
|
testImplementation(projects.libraries.preferences.test)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
|
testImplementation(projects.libraries.matrixuiTest)
|
||||||
testImplementation(projects.libraries.push.test)
|
testImplementation(projects.libraries.push.test)
|
||||||
testImplementation(projects.services.analytics.test)
|
testImplementation(projects.services.analytics.test)
|
||||||
testImplementation(projects.services.appnavstate.test)
|
testImplementation(projects.services.appnavstate.test)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||||
import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder
|
import io.element.android.libraries.matrix.ui.test.media.FakeImageLoaderHolder
|
||||||
import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader
|
import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,9 @@ import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||||
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||||
|
import io.element.android.libraries.matrix.ui.test.media.FakeImageLoaderHolder
|
||||||
import io.element.android.libraries.push.api.notifications.ForegroundServiceType
|
import io.element.android.libraries.push.api.notifications.ForegroundServiceType
|
||||||
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
|
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
|
||||||
import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder
|
|
||||||
import io.element.android.libraries.push.test.notifications.FakeOnMissedCallNotificationHandler
|
import io.element.android.libraries.push.test.notifications.FakeOnMissedCallNotificationHandler
|
||||||
import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader
|
import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader
|
||||||
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
|
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
|
||||||
|
|
@ -415,6 +415,7 @@ class DefaultActiveCallManagerTest {
|
||||||
|
|
||||||
verify { notificationManagerCompat.cancel(any()) }
|
verify { notificationManagerCompat.cancel(any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@Test
|
@Test
|
||||||
fun `IncomingCall - ignore expired ring lifetime`() = runTest {
|
fun `IncomingCall - ignore expired ring lifetime`() = runTest {
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,12 @@ class FakeEnterpriseService(
|
||||||
private val defaultHomeserverListResult: () -> List<String> = { emptyList() },
|
private val defaultHomeserverListResult: () -> List<String> = { emptyList() },
|
||||||
private val isAllowedToConnectToHomeserverResult: (String) -> Boolean = { lambdaError() },
|
private val isAllowedToConnectToHomeserverResult: (String) -> Boolean = { lambdaError() },
|
||||||
initialSemanticColors: SemanticColorsLightDark = SemanticColorsLightDark.default,
|
initialSemanticColors: SemanticColorsLightDark = SemanticColorsLightDark.default,
|
||||||
|
initialBrandColor: Color? = null,
|
||||||
private val overrideBrandColorResult: (SessionId?, String?) -> Unit = { _, _ -> lambdaError() },
|
private val overrideBrandColorResult: (SessionId?, String?) -> Unit = { _, _ -> lambdaError() },
|
||||||
private val firebasePushGatewayResult: () -> String? = { lambdaError() },
|
private val firebasePushGatewayResult: () -> String? = { lambdaError() },
|
||||||
private val unifiedPushDefaultPushGatewayResult: () -> String? = { lambdaError() },
|
private val unifiedPushDefaultPushGatewayResult: () -> String? = { lambdaError() },
|
||||||
) : EnterpriseService {
|
) : EnterpriseService {
|
||||||
private val brandColorState = MutableStateFlow<Color?>(null)
|
private val brandColorState = MutableStateFlow(initialBrandColor)
|
||||||
private val semanticColorsState = MutableStateFlow(initialSemanticColors)
|
private val semanticColorsState = MutableStateFlow(initialSemanticColors)
|
||||||
|
|
||||||
override suspend fun isEnterpriseUser(sessionId: SessionId): Boolean = simulateLongTask {
|
override suspend fun isEnterpriseUser(sessionId: SessionId): Boolean = simulateLongTask {
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ dependencies {
|
||||||
implementation(libs.haze)
|
implementation(libs.haze)
|
||||||
implementation(libs.haze.materials)
|
implementation(libs.haze.materials)
|
||||||
implementation(projects.features.reportroom.api)
|
implementation(projects.features.reportroom.api)
|
||||||
implementation(projects.features.changeroommemberroles.api)
|
implementation(projects.features.rolesandpermissions.api)
|
||||||
implementation(projects.libraries.previewutils)
|
implementation(projects.libraries.previewutils)
|
||||||
api(projects.features.home.api)
|
api(projects.features.home.api)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,6 @@ import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import im.vector.app.features.analytics.plan.MobileScreen
|
import im.vector.app.features.analytics.plan.MobileScreen
|
||||||
import io.element.android.annotations.ContributesNode
|
import io.element.android.annotations.ContributesNode
|
||||||
import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesEntryPoint
|
|
||||||
import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesListType
|
|
||||||
import io.element.android.features.home.api.HomeEntryPoint
|
import io.element.android.features.home.api.HomeEntryPoint
|
||||||
import io.element.android.features.home.impl.components.RoomListMenuAction
|
import io.element.android.features.home.impl.components.RoomListMenuAction
|
||||||
import io.element.android.features.home.impl.model.RoomListRoomSummary
|
import io.element.android.features.home.impl.model.RoomListRoomSummary
|
||||||
|
|
@ -40,6 +38,8 @@ import io.element.android.features.invite.api.declineandblock.DeclineInviteAndBl
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomRenderer
|
import io.element.android.features.leaveroom.api.LeaveRoomRenderer
|
||||||
import io.element.android.features.logout.api.direct.DirectLogoutView
|
import io.element.android.features.logout.api.direct.DirectLogoutView
|
||||||
import io.element.android.features.reportroom.api.ReportRoomEntryPoint
|
import io.element.android.features.reportroom.api.ReportRoomEntryPoint
|
||||||
|
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint
|
||||||
|
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType
|
||||||
import io.element.android.libraries.architecture.BackstackView
|
import io.element.android.libraries.architecture.BackstackView
|
||||||
import io.element.android.libraries.architecture.BaseFlowNode
|
import io.element.android.libraries.architecture.BaseFlowNode
|
||||||
import io.element.android.libraries.architecture.appyx.launchMolecule
|
import io.element.android.libraries.architecture.appyx.launchMolecule
|
||||||
|
|
@ -93,10 +93,12 @@ class HomeFlowNode(
|
||||||
changeRoomMemberRolesNode: ChangeRoomMemberRolesEntryPoint.NodeProxy,
|
changeRoomMemberRolesNode: ChangeRoomMemberRolesEntryPoint.NodeProxy,
|
||||||
->
|
->
|
||||||
commonLifecycle.coroutineScope.launch {
|
commonLifecycle.coroutineScope.launch {
|
||||||
changeRoomMemberRolesNode.waitForRoleChanged()
|
val isNewOwnerSelected = changeRoomMemberRolesNode.waitForCompletion()
|
||||||
withContext(NonCancellable) {
|
withContext(NonCancellable) {
|
||||||
backstack.pop()
|
backstack.pop()
|
||||||
onNewOwnersSelected(changeRoomMemberRolesNode.roomId)
|
if (isNewOwnerSelected) {
|
||||||
|
onNewOwnersSelected(changeRoomMemberRolesNode.roomId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.poll.impl.R
|
import io.element.android.features.poll.impl.R
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||||
|
import io.element.android.libraries.designsystem.components.dialogs.SaveChangesDialog
|
||||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
|
@ -63,8 +64,7 @@ fun CreatePollView(
|
||||||
val navBack = { state.eventSink(CreatePollEvents.ConfirmNavBack) }
|
val navBack = { state.eventSink(CreatePollEvents.ConfirmNavBack) }
|
||||||
BackHandler(onBack = navBack)
|
BackHandler(onBack = navBack)
|
||||||
if (state.showBackConfirmation) {
|
if (state.showBackConfirmation) {
|
||||||
ConfirmationDialog(
|
SaveChangesDialog(
|
||||||
content = stringResource(id = R.string.screen_create_poll_cancel_confirmation_content_android),
|
|
||||||
onSubmitClick = { state.eventSink(CreatePollEvents.NavBack) },
|
onSubmitClick = { state.eventSink(CreatePollEvents.NavBack) },
|
||||||
onDismiss = { state.eventSink(CreatePollEvents.HideConfirmation) }
|
onDismiss = { state.eventSink(CreatePollEvents.HideConfirmation) }
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ class BugReportPresenter(
|
||||||
val sendingAction: MutableState<AsyncAction<Unit>> = remember {
|
val sendingAction: MutableState<AsyncAction<Unit>> = remember {
|
||||||
mutableStateOf(AsyncAction.Uninitialized)
|
mutableStateOf(AsyncAction.Uninitialized)
|
||||||
}
|
}
|
||||||
val formState: MutableState<BugReportFormState> = remember {
|
val formState: MutableState<BugReportFormState> = rememberSaveable {
|
||||||
mutableStateOf(BugReportFormState.Default)
|
mutableStateOf(BugReportFormState.Default)
|
||||||
}
|
}
|
||||||
val uploadListener = BugReporterUploadListener(sendingProgress, sendingAction)
|
val uploadListener = BugReporterUploadListener(sendingProgress, sendingAction)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.features.changeroommemberroles.api"
|
namespace = "io.element.android.features.rolesandpermissions.api"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
@ -5,12 +5,11 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.changeroommemberroles.api
|
package io.element.android.features.rolesandpermissions.api
|
||||||
|
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||||
import io.element.android.libraries.architecture.NodeInputs
|
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
|
|
||||||
|
|
@ -24,11 +23,11 @@ fun interface ChangeRoomMemberRolesEntryPoint : FeatureEntryPoint {
|
||||||
|
|
||||||
interface NodeProxy {
|
interface NodeProxy {
|
||||||
val roomId: RoomId
|
val roomId: RoomId
|
||||||
suspend fun waitForRoleChanged()
|
suspend fun waitForCompletion(): Boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class ChangeRoomMemberRolesListType : NodeInputs {
|
enum class ChangeRoomMemberRolesListType {
|
||||||
SelectNewOwnersWhenLeaving,
|
SelectNewOwnersWhenLeaving,
|
||||||
Admins,
|
Admins,
|
||||||
Moderators
|
Moderators
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.rolesandpermissions.api
|
||||||
|
|
||||||
|
import io.element.android.libraries.architecture.SimpleFeatureEntryPoint
|
||||||
|
|
||||||
|
fun interface RolesAndPermissionsEntryPoint : SimpleFeatureEntryPoint
|
||||||
|
|
@ -14,7 +14,7 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.features.changeroommemberroles.impl"
|
namespace = "io.element.android.features.rolesandpermissions.impl"
|
||||||
|
|
||||||
testOptions {
|
testOptions {
|
||||||
unitTests {
|
unitTests {
|
||||||
|
|
@ -26,7 +26,7 @@ android {
|
||||||
setupDependencyInjection()
|
setupDependencyInjection()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(projects.features.changeroommemberroles.api)
|
api(projects.features.rolesandpermissions.api)
|
||||||
implementation(projects.appnav)
|
implementation(projects.appnav)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.rolesandpermissions.impl
|
||||||
|
|
||||||
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
|
import com.bumble.appyx.core.node.Node
|
||||||
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
|
import io.element.android.features.rolesandpermissions.api.RolesAndPermissionsEntryPoint
|
||||||
|
import io.element.android.libraries.architecture.createNode
|
||||||
|
import io.element.android.libraries.di.RoomScope
|
||||||
|
|
||||||
|
@ContributesBinding(RoomScope::class)
|
||||||
|
class DefaultRolesAndPermissionsEntryPoint : RolesAndPermissionsEntryPoint {
|
||||||
|
override fun createNode(parentNode: Node, buildContext: BuildContext): Node {
|
||||||
|
return parentNode.createNode<RolesAndPermissionsFlowNode>(buildContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.rolesandpermissions.impl
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.statusBarsPadding
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.lifecycle.coroutineScope
|
||||||
|
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.operation.pop
|
||||||
|
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||||
|
import dev.zacsweers.metro.Assisted
|
||||||
|
import dev.zacsweers.metro.AssistedInject
|
||||||
|
import io.element.android.annotations.ContributesNode
|
||||||
|
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType
|
||||||
|
import io.element.android.features.rolesandpermissions.impl.permissions.ChangeRoomPermissionsNode
|
||||||
|
import io.element.android.features.rolesandpermissions.impl.roles.ChangeRolesNode
|
||||||
|
import io.element.android.features.rolesandpermissions.impl.root.RolesAndPermissionsNode
|
||||||
|
import io.element.android.libraries.architecture.BackstackView
|
||||||
|
import io.element.android.libraries.architecture.BaseFlowNode
|
||||||
|
import io.element.android.libraries.architecture.createNode
|
||||||
|
import io.element.android.libraries.designsystem.components.async.AsyncIndicator
|
||||||
|
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost
|
||||||
|
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorState
|
||||||
|
import io.element.android.libraries.di.RoomScope
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@ContributesNode(RoomScope::class)
|
||||||
|
@AssistedInject
|
||||||
|
class RolesAndPermissionsFlowNode(
|
||||||
|
@Assisted buildContext: BuildContext,
|
||||||
|
@Assisted plugins: List<Plugin>,
|
||||||
|
) : BaseFlowNode<RolesAndPermissionsFlowNode.NavTarget>(
|
||||||
|
backstack = BackStack(
|
||||||
|
initialElement = NavTarget.Root,
|
||||||
|
savedStateMap = buildContext.savedStateMap,
|
||||||
|
),
|
||||||
|
buildContext = buildContext,
|
||||||
|
plugins = plugins,
|
||||||
|
) {
|
||||||
|
sealed interface NavTarget : Parcelable {
|
||||||
|
@Parcelize
|
||||||
|
data object Root : NavTarget
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data object ChangeAdmins : NavTarget
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data object ChangeModerators : NavTarget
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data object ChangeRoomPermissions : NavTarget
|
||||||
|
}
|
||||||
|
|
||||||
|
private val asyncIndicatorState = AsyncIndicatorState()
|
||||||
|
|
||||||
|
override fun onBuilt() {
|
||||||
|
super.onBuilt()
|
||||||
|
whenChildAttached { lifecycle, node: ChangeRolesNode ->
|
||||||
|
lifecycle.coroutineScope.launch {
|
||||||
|
val changesSaved = node.waitForCompletion()
|
||||||
|
onChangeComplete(changesSaved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onChangeComplete(changesSaved: Boolean) {
|
||||||
|
backstack.pop()
|
||||||
|
if (changesSaved) {
|
||||||
|
asyncIndicatorState.enqueue(durationMs = AsyncIndicator.DURATION_SHORT) {
|
||||||
|
AsyncIndicator.Custom(text = stringResource(CommonStrings.common_saved_changes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||||
|
return when (navTarget) {
|
||||||
|
is NavTarget.Root -> {
|
||||||
|
val callback = object : RolesAndPermissionsNode.Callback {
|
||||||
|
override fun openAdminList() {
|
||||||
|
backstack.push(NavTarget.ChangeAdmins)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun openModeratorList() {
|
||||||
|
backstack.push(NavTarget.ChangeModerators)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun openEditPermissions() {
|
||||||
|
backstack.push(NavTarget.ChangeRoomPermissions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createNode<RolesAndPermissionsNode>(
|
||||||
|
buildContext = buildContext,
|
||||||
|
plugins = listOf(callback),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is NavTarget.ChangeAdmins -> {
|
||||||
|
val inputs = ChangeRolesNode.Inputs(ChangeRoomMemberRolesListType.Admins)
|
||||||
|
createNode<ChangeRolesNode>(buildContext = buildContext, plugins = listOf(inputs))
|
||||||
|
}
|
||||||
|
is NavTarget.ChangeModerators -> {
|
||||||
|
val inputs = ChangeRolesNode.Inputs(ChangeRoomMemberRolesListType.Moderators)
|
||||||
|
createNode<ChangeRolesNode>(buildContext = buildContext, plugins = listOf(inputs))
|
||||||
|
}
|
||||||
|
is NavTarget.ChangeRoomPermissions -> {
|
||||||
|
val callback = object : ChangeRoomPermissionsNode.Callback {
|
||||||
|
override fun onComplete(changesSaved: Boolean) {
|
||||||
|
onChangeComplete(changesSaved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createNode<ChangeRoomPermissionsNode>(buildContext = buildContext, plugins = listOf(callback))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun View(modifier: Modifier) {
|
||||||
|
Box(modifier = modifier) {
|
||||||
|
BackstackView()
|
||||||
|
AsyncIndicatorHost(modifier = Modifier.statusBarsPadding(), asyncIndicatorState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.changeroommemberroles.impl
|
package io.element.android.features.rolesandpermissions.impl
|
||||||
|
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.libraries.core.bool.orFalse
|
import io.element.android.libraries.core.bool.orFalse
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.analytics
|
package io.element.android.features.rolesandpermissions.impl.analytics
|
||||||
|
|
||||||
import im.vector.app.features.analytics.plan.RoomModeration
|
import im.vector.app.features.analytics.plan.RoomModeration
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
|
|
@ -5,12 +5,10 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions
|
package io.element.android.features.rolesandpermissions.impl.permissions
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
|
||||||
|
|
||||||
interface ChangeRoomPermissionsEvent {
|
interface ChangeRoomPermissionsEvent {
|
||||||
data class ChangeMinimumRoleForAction(val action: RoomPermissionType, val role: RoomMember.Role) : ChangeRoomPermissionsEvent
|
data class ChangeMinimumRoleForAction(val action: RoomPermissionType, val role: SelectableRole) : ChangeRoomPermissionsEvent
|
||||||
data object Save : ChangeRoomPermissionsEvent
|
data object Save : ChangeRoomPermissionsEvent
|
||||||
data object Exit : ChangeRoomPermissionsEvent
|
data object Exit : ChangeRoomPermissionsEvent
|
||||||
data object ResetPendingActions : ChangeRoomPermissionsEvent
|
data object ResetPendingActions : ChangeRoomPermissionsEvent
|
||||||
|
|
@ -5,9 +5,8 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions
|
package io.element.android.features.rolesandpermissions.impl.permissions
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
|
|
@ -16,25 +15,21 @@ import com.bumble.appyx.core.plugin.Plugin
|
||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import io.element.android.annotations.ContributesNode
|
import io.element.android.annotations.ContributesNode
|
||||||
import io.element.android.libraries.architecture.NodeInputs
|
import io.element.android.libraries.architecture.callback
|
||||||
import io.element.android.libraries.architecture.inputs
|
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
|
|
||||||
@ContributesNode(RoomScope::class)
|
@ContributesNode(RoomScope::class)
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
class ChangeRoomPermissionsNode(
|
class ChangeRoomPermissionsNode(
|
||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
presenterFactory: ChangeRoomPermissionsPresenter.Factory,
|
private val presenter: ChangeRoomPermissionsPresenter,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
@Parcelize
|
interface Callback : Plugin {
|
||||||
data class Inputs(
|
fun onComplete(changesSaved: Boolean)
|
||||||
val section: ChangeRoomPermissionsSection,
|
}
|
||||||
) : NodeInputs, Parcelable
|
|
||||||
|
|
||||||
private val inputs: Inputs = inputs()
|
private val callback: Callback = callback()
|
||||||
private val presenter = presenterFactory.create(inputs.section)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
|
|
@ -42,14 +37,7 @@ class ChangeRoomPermissionsNode(
|
||||||
ChangeRoomPermissionsView(
|
ChangeRoomPermissionsView(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
state = state,
|
state = state,
|
||||||
onBackClick = this::navigateUp,
|
onComplete = callback::onComplete,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
enum class ChangeRoomPermissionsSection : Parcelable {
|
|
||||||
RoomDetails,
|
|
||||||
MessagesAndContent,
|
|
||||||
MembershipModeration,
|
|
||||||
}
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions
|
package io.element.android.features.rolesandpermissions.impl.permissions
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
|
@ -15,50 +15,60 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Inject
|
||||||
import dev.zacsweers.metro.AssistedFactory
|
import io.element.android.features.rolesandpermissions.impl.analytics.trackPermissionChangeAnalytics
|
||||||
import dev.zacsweers.metro.AssistedInject
|
|
||||||
import io.element.android.features.roomdetails.impl.analytics.trackPermissionChangeAnalytics
|
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
|
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@AssistedInject
|
@Inject
|
||||||
class ChangeRoomPermissionsPresenter(
|
class ChangeRoomPermissionsPresenter(
|
||||||
@Assisted private val section: ChangeRoomPermissionsSection,
|
|
||||||
private val room: JoinedRoom,
|
private val room: JoinedRoom,
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
) : Presenter<ChangeRoomPermissionsState> {
|
) : Presenter<ChangeRoomPermissionsState> {
|
||||||
companion object {
|
companion object {
|
||||||
internal fun itemsForSection(section: ChangeRoomPermissionsSection) = when (section) {
|
private fun itemsForSection(section: RoomPermissionsSection) = when (section) {
|
||||||
ChangeRoomPermissionsSection.RoomDetails -> persistentListOf(
|
RoomPermissionsSection.SpaceDetails,
|
||||||
|
RoomPermissionsSection.RoomDetails -> persistentListOf(
|
||||||
RoomPermissionType.ROOM_NAME,
|
RoomPermissionType.ROOM_NAME,
|
||||||
RoomPermissionType.ROOM_AVATAR,
|
RoomPermissionType.ROOM_AVATAR,
|
||||||
RoomPermissionType.ROOM_TOPIC,
|
RoomPermissionType.ROOM_TOPIC,
|
||||||
)
|
)
|
||||||
ChangeRoomPermissionsSection.MessagesAndContent -> persistentListOf(
|
RoomPermissionsSection.MessagesAndContent -> persistentListOf(
|
||||||
RoomPermissionType.SEND_EVENTS,
|
RoomPermissionType.SEND_EVENTS,
|
||||||
RoomPermissionType.REDACT_EVENTS,
|
RoomPermissionType.REDACT_EVENTS,
|
||||||
)
|
)
|
||||||
ChangeRoomPermissionsSection.MembershipModeration -> persistentListOf(
|
RoomPermissionsSection.MembershipModeration -> persistentListOf(
|
||||||
RoomPermissionType.INVITE,
|
RoomPermissionType.INVITE,
|
||||||
RoomPermissionType.KICK,
|
RoomPermissionType.KICK,
|
||||||
RoomPermissionType.BAN,
|
RoomPermissionType.BAN,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
@AssistedFactory
|
private fun RoomPermissionsSection.shouldShow(isSpace: Boolean): Boolean {
|
||||||
interface Factory {
|
return when (this) {
|
||||||
fun create(section: ChangeRoomPermissionsSection): ChangeRoomPermissionsPresenter
|
RoomPermissionsSection.RoomDetails -> !isSpace
|
||||||
|
RoomPermissionsSection.MembershipModeration -> true
|
||||||
|
RoomPermissionsSection.MessagesAndContent -> !isSpace
|
||||||
|
RoomPermissionsSection.SpaceDetails -> isSpace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun buildItems(isSpace: Boolean) =
|
||||||
|
RoomPermissionsSection.entries
|
||||||
|
.filter { section -> section.shouldShow(isSpace) }
|
||||||
|
.associateWith { itemsForSection(it) }
|
||||||
|
.toImmutableMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val items: ImmutableList<RoomPermissionType> = itemsForSection(section)
|
private val itemsBySection = buildItems(isSpace = room.info().isSpace)
|
||||||
|
|
||||||
private var initialPermissions by mutableStateOf<RoomPowerLevelsValues?>(null)
|
private var initialPermissions by mutableStateOf<RoomPowerLevelsValues?>(null)
|
||||||
private var currentPermissions by mutableStateOf<RoomPowerLevelsValues?>(null)
|
private var currentPermissions by mutableStateOf<RoomPowerLevelsValues?>(null)
|
||||||
|
|
@ -80,15 +90,20 @@ class ChangeRoomPermissionsPresenter(
|
||||||
fun handleEvent(event: ChangeRoomPermissionsEvent) {
|
fun handleEvent(event: ChangeRoomPermissionsEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction -> {
|
is ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction -> {
|
||||||
|
val powerLevel = when (event.role) {
|
||||||
|
SelectableRole.Admin -> RoomMember.Role.Admin.powerLevel
|
||||||
|
SelectableRole.Moderator -> RoomMember.Role.Moderator.powerLevel
|
||||||
|
SelectableRole.Everyone -> RoomMember.Role.User.powerLevel
|
||||||
|
}
|
||||||
currentPermissions = when (event.action) {
|
currentPermissions = when (event.action) {
|
||||||
RoomPermissionType.BAN -> currentPermissions?.copy(ban = event.role.powerLevel)
|
RoomPermissionType.BAN -> currentPermissions?.copy(ban = powerLevel)
|
||||||
RoomPermissionType.INVITE -> currentPermissions?.copy(invite = event.role.powerLevel)
|
RoomPermissionType.INVITE -> currentPermissions?.copy(invite = powerLevel)
|
||||||
RoomPermissionType.KICK -> currentPermissions?.copy(kick = event.role.powerLevel)
|
RoomPermissionType.KICK -> currentPermissions?.copy(kick = powerLevel)
|
||||||
RoomPermissionType.SEND_EVENTS -> currentPermissions?.copy(sendEvents = event.role.powerLevel)
|
RoomPermissionType.SEND_EVENTS -> currentPermissions?.copy(sendEvents = powerLevel)
|
||||||
RoomPermissionType.REDACT_EVENTS -> currentPermissions?.copy(redactEvents = event.role.powerLevel)
|
RoomPermissionType.REDACT_EVENTS -> currentPermissions?.copy(redactEvents = powerLevel)
|
||||||
RoomPermissionType.ROOM_NAME -> currentPermissions?.copy(roomName = event.role.powerLevel)
|
RoomPermissionType.ROOM_NAME -> currentPermissions?.copy(roomName = powerLevel)
|
||||||
RoomPermissionType.ROOM_AVATAR -> currentPermissions?.copy(roomAvatar = event.role.powerLevel)
|
RoomPermissionType.ROOM_AVATAR -> currentPermissions?.copy(roomAvatar = powerLevel)
|
||||||
RoomPermissionType.ROOM_TOPIC -> currentPermissions?.copy(roomTopic = event.role.powerLevel)
|
RoomPermissionType.ROOM_TOPIC -> currentPermissions?.copy(roomTopic = powerLevel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is ChangeRoomPermissionsEvent.Save -> coroutineScope.save()
|
is ChangeRoomPermissionsEvent.Save -> coroutineScope.save()
|
||||||
|
|
@ -106,9 +121,8 @@ class ChangeRoomPermissionsPresenter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ChangeRoomPermissionsState(
|
return ChangeRoomPermissionsState(
|
||||||
section = section,
|
|
||||||
currentPermissions = currentPermissions,
|
currentPermissions = currentPermissions,
|
||||||
items = items,
|
itemsBySection = itemsBySection,
|
||||||
hasChanges = hasChanges,
|
hasChanges = hasChanges,
|
||||||
saveAction = saveAction,
|
saveAction = saveAction,
|
||||||
confirmExitAction = confirmExitAction,
|
confirmExitAction = confirmExitAction,
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.rolesandpermissions.impl.permissions
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import io.element.android.features.rolesandpermissions.impl.R
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
import io.element.android.libraries.designsystem.components.preferences.DropdownOption
|
||||||
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
|
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.ImmutableMap
|
||||||
|
|
||||||
|
data class ChangeRoomPermissionsState(
|
||||||
|
val currentPermissions: RoomPowerLevelsValues?,
|
||||||
|
val itemsBySection: ImmutableMap<RoomPermissionsSection, ImmutableList<RoomPermissionType>>,
|
||||||
|
val hasChanges: Boolean,
|
||||||
|
val saveAction: AsyncAction<Unit>,
|
||||||
|
val confirmExitAction: AsyncAction<Unit>,
|
||||||
|
val eventSink: (ChangeRoomPermissionsEvent) -> Unit,
|
||||||
|
) {
|
||||||
|
fun selectedRoleForType(type: RoomPermissionType): SelectableRole? {
|
||||||
|
if (currentPermissions == null) return null
|
||||||
|
val role = when (type) {
|
||||||
|
RoomPermissionType.BAN -> RoomMember.Role.forPowerLevel(currentPermissions.ban)
|
||||||
|
RoomPermissionType.INVITE -> RoomMember.Role.forPowerLevel(currentPermissions.invite)
|
||||||
|
RoomPermissionType.KICK -> RoomMember.Role.forPowerLevel(currentPermissions.kick)
|
||||||
|
RoomPermissionType.SEND_EVENTS -> RoomMember.Role.forPowerLevel(currentPermissions.sendEvents)
|
||||||
|
RoomPermissionType.REDACT_EVENTS -> RoomMember.Role.forPowerLevel(currentPermissions.redactEvents)
|
||||||
|
RoomPermissionType.ROOM_NAME -> RoomMember.Role.forPowerLevel(currentPermissions.roomName)
|
||||||
|
RoomPermissionType.ROOM_AVATAR -> RoomMember.Role.forPowerLevel(currentPermissions.roomAvatar)
|
||||||
|
RoomPermissionType.ROOM_TOPIC -> RoomMember.Role.forPowerLevel(currentPermissions.roomTopic)
|
||||||
|
}
|
||||||
|
return when (role) {
|
||||||
|
is RoomMember.Role.Owner,
|
||||||
|
RoomMember.Role.Admin -> SelectableRole.Admin
|
||||||
|
RoomMember.Role.Moderator -> SelectableRole.Moderator
|
||||||
|
RoomMember.Role.User -> SelectableRole.Everyone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class RoomPermissionsSection {
|
||||||
|
SpaceDetails,
|
||||||
|
RoomDetails,
|
||||||
|
MessagesAndContent,
|
||||||
|
MembershipModeration,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class SelectableRole : DropdownOption {
|
||||||
|
Admin {
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
override fun getText(): String = stringResource(R.string.screen_room_member_list_role_administrator)
|
||||||
|
},
|
||||||
|
Moderator {
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
override fun getText(): String = stringResource(R.string.screen_room_member_list_role_moderator)
|
||||||
|
},
|
||||||
|
Everyone {
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
override fun getText(): String = stringResource(R.string.screen_room_change_permissions_everyone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class RoomPermissionType {
|
||||||
|
BAN,
|
||||||
|
INVITE,
|
||||||
|
KICK,
|
||||||
|
SEND_EVENTS,
|
||||||
|
REDACT_EVENTS,
|
||||||
|
ROOM_NAME,
|
||||||
|
ROOM_AVATAR,
|
||||||
|
ROOM_TOPIC
|
||||||
|
}
|
||||||
|
|
@ -5,47 +5,39 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions
|
package io.element.android.features.rolesandpermissions.impl.permissions
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
|
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
|
|
||||||
class ChangeRoomPermissionsStateProvider : PreviewParameterProvider<ChangeRoomPermissionsState> {
|
class ChangeRoomPermissionsStateProvider : PreviewParameterProvider<ChangeRoomPermissionsState> {
|
||||||
override val values: Sequence<ChangeRoomPermissionsState>
|
override val values: Sequence<ChangeRoomPermissionsState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.RoomDetails),
|
aChangeRoomPermissionsState(),
|
||||||
aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.MessagesAndContent),
|
aChangeRoomPermissionsState(hasChanges = true),
|
||||||
aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.MembershipModeration),
|
aChangeRoomPermissionsState(hasChanges = true, saveAction = AsyncAction.Loading),
|
||||||
aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true),
|
|
||||||
aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true, saveAction = AsyncAction.Loading),
|
|
||||||
aChangeRoomPermissionsState(
|
aChangeRoomPermissionsState(
|
||||||
section = ChangeRoomPermissionsSection.RoomDetails,
|
|
||||||
hasChanges = true,
|
hasChanges = true,
|
||||||
saveAction = AsyncAction.Failure(IllegalStateException("Failed to save changes"))
|
saveAction = AsyncAction.Failure(IllegalStateException("Failed to save changes"))
|
||||||
),
|
),
|
||||||
aChangeRoomPermissionsState(
|
aChangeRoomPermissionsState(hasChanges = true, confirmExitAction = AsyncAction.ConfirmingNoParams),
|
||||||
section = ChangeRoomPermissionsSection.RoomDetails,
|
|
||||||
hasChanges = true,
|
|
||||||
confirmExitAction = AsyncAction.ConfirmingNoParams,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun aChangeRoomPermissionsState(
|
internal fun aChangeRoomPermissionsState(
|
||||||
section: ChangeRoomPermissionsSection,
|
|
||||||
currentPermissions: RoomPowerLevelsValues = previewPermissions(),
|
currentPermissions: RoomPowerLevelsValues = previewPermissions(),
|
||||||
items: List<RoomPermissionType> = ChangeRoomPermissionsPresenter.itemsForSection(section),
|
itemsBySection: Map<RoomPermissionsSection, ImmutableList<RoomPermissionType>> = ChangeRoomPermissionsPresenter.buildItems(false),
|
||||||
hasChanges: Boolean = false,
|
hasChanges: Boolean = false,
|
||||||
saveAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
saveAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
confirmExitAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
confirmExitAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
eventSink: (ChangeRoomPermissionsEvent) -> Unit = {},
|
eventSink: (ChangeRoomPermissionsEvent) -> Unit = {},
|
||||||
) = ChangeRoomPermissionsState(
|
) = ChangeRoomPermissionsState(
|
||||||
section = section,
|
|
||||||
currentPermissions = currentPermissions,
|
currentPermissions = currentPermissions,
|
||||||
items = items.toImmutableList(),
|
itemsBySection = itemsBySection.toImmutableMap(),
|
||||||
hasChanges = hasChanges,
|
hasChanges = hasChanges,
|
||||||
saveAction = saveAction,
|
saveAction = saveAction,
|
||||||
confirmExitAction = confirmExitAction,
|
confirmExitAction = confirmExitAction,
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.rolesandpermissions.impl.permissions
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import io.element.android.features.rolesandpermissions.impl.R
|
||||||
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
|
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||||
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceDropdown
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.ListSectionHeader
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ChangeRoomPermissionsView(
|
||||||
|
state: ChangeRoomPermissionsState,
|
||||||
|
onComplete: (Boolean) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
BackHandler {
|
||||||
|
state.eventSink(ChangeRoomPermissionsEvent.Exit)
|
||||||
|
}
|
||||||
|
Scaffold(
|
||||||
|
modifier = modifier,
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
titleStr = stringResource(R.string.screen_room_roles_and_permissions_permissions_header),
|
||||||
|
navigationIcon = {
|
||||||
|
BackButton(onClick = { state.eventSink(ChangeRoomPermissionsEvent.Exit) })
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
TextButton(
|
||||||
|
text = stringResource(CommonStrings.action_save),
|
||||||
|
onClick = { state.eventSink(ChangeRoomPermissionsEvent.Save) },
|
||||||
|
enabled = state.hasChanges,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { padding ->
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(padding)
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
state.itemsBySection.onEachIndexed { index, (section, items) ->
|
||||||
|
item {
|
||||||
|
ListSectionHeader(titleForSection(section), hasDivider = index > 0)
|
||||||
|
}
|
||||||
|
for (permissionType in items) {
|
||||||
|
item {
|
||||||
|
PreferenceDropdown(
|
||||||
|
title = titleForType(permissionType),
|
||||||
|
selectedOption = state.selectedRoleForType(permissionType),
|
||||||
|
options = SelectableRole.entries.toImmutableList(),
|
||||||
|
onSelectOption = { role ->
|
||||||
|
state.eventSink(
|
||||||
|
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(
|
||||||
|
action = permissionType,
|
||||||
|
role = role
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncActionView(
|
||||||
|
async = state.saveAction,
|
||||||
|
onSuccess = { onComplete(true) },
|
||||||
|
onErrorDismiss = { state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions) }
|
||||||
|
)
|
||||||
|
|
||||||
|
AsyncActionView(
|
||||||
|
async = state.confirmExitAction,
|
||||||
|
onSuccess = { onComplete(false) },
|
||||||
|
confirmationDialog = {
|
||||||
|
ConfirmationDialog(
|
||||||
|
title = stringResource(R.string.screen_room_change_role_unsaved_changes_title),
|
||||||
|
content = stringResource(R.string.screen_room_change_role_unsaved_changes_description),
|
||||||
|
submitText = stringResource(CommonStrings.action_save),
|
||||||
|
cancelText = stringResource(CommonStrings.action_discard),
|
||||||
|
onSubmitClick = { state.eventSink(ChangeRoomPermissionsEvent.Save) },
|
||||||
|
onDismiss = { state.eventSink(ChangeRoomPermissionsEvent.Exit) }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onErrorDismiss = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun titleForSection(section: RoomPermissionsSection): String = when (section) {
|
||||||
|
RoomPermissionsSection.SpaceDetails -> stringResource(R.string.screen_room_roles_and_permissions_space_details)
|
||||||
|
RoomPermissionsSection.RoomDetails -> stringResource(R.string.screen_room_roles_and_permissions_room_details)
|
||||||
|
RoomPermissionsSection.MessagesAndContent -> stringResource(R.string.screen_room_roles_and_permissions_messages_and_content)
|
||||||
|
RoomPermissionsSection.MembershipModeration -> stringResource(R.string.screen_room_roles_and_permissions_member_moderation)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun titleForType(type: RoomPermissionType): String = when (type) {
|
||||||
|
RoomPermissionType.INVITE -> stringResource(R.string.screen_room_change_permissions_invite_people)
|
||||||
|
RoomPermissionType.KICK -> stringResource(R.string.screen_room_change_permissions_remove_people)
|
||||||
|
RoomPermissionType.BAN -> stringResource(R.string.screen_room_change_permissions_ban_people)
|
||||||
|
RoomPermissionType.SEND_EVENTS -> stringResource(R.string.screen_room_change_permissions_send_messages)
|
||||||
|
RoomPermissionType.REDACT_EVENTS -> stringResource(R.string.screen_room_change_permissions_delete_messages)
|
||||||
|
RoomPermissionType.ROOM_NAME -> stringResource(R.string.screen_room_change_permissions_room_name)
|
||||||
|
RoomPermissionType.ROOM_AVATAR -> stringResource(R.string.screen_room_change_permissions_room_avatar)
|
||||||
|
RoomPermissionType.ROOM_TOPIC -> stringResource(R.string.screen_room_change_permissions_room_topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun ChangeRoomPermissionsViewPreview(@PreviewParameter(ChangeRoomPermissionsStateProvider::class) state: ChangeRoomPermissionsState) {
|
||||||
|
ElementPreview {
|
||||||
|
ChangeRoomPermissionsView(
|
||||||
|
state = state,
|
||||||
|
onComplete = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2024 New Vector Ltd.
|
* Copyright 2025 New Vector Ltd.
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.changeroommemberroles.impl
|
package io.element.android.features.rolesandpermissions.impl.roles
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.changeroommemberroles.impl
|
package io.element.android.features.rolesandpermissions.impl.roles
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
|
@ -17,10 +17,11 @@ import com.bumble.appyx.core.plugin.Plugin
|
||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import io.element.android.annotations.ContributesNode
|
import io.element.android.annotations.ContributesNode
|
||||||
import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesListType
|
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType
|
||||||
import io.element.android.libraries.architecture.NodeInputs
|
import io.element.android.libraries.architecture.NodeInputs
|
||||||
import io.element.android.libraries.architecture.appyx.launchMolecule
|
import io.element.android.libraries.architecture.appyx.launchMolecule
|
||||||
import io.element.android.libraries.architecture.inputs
|
import io.element.android.libraries.architecture.inputs
|
||||||
|
import io.element.android.libraries.core.bool.orFalse
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
|
@ -40,17 +41,17 @@ class ChangeRolesNode(
|
||||||
private val presenter = presenterFactory.create(inputs.listType.toRoomMemberRole())
|
private val presenter = presenterFactory.create(inputs.listType.toRoomMemberRole())
|
||||||
private val stateFlow = launchMolecule { presenter.present() }
|
private val stateFlow = launchMolecule { presenter.present() }
|
||||||
|
|
||||||
suspend fun waitForRoleChanged() {
|
suspend fun waitForCompletion(): Boolean {
|
||||||
stateFlow.first { it.savingState.isSuccess() }
|
val successState = stateFlow.first { it.savingState.isSuccess() }
|
||||||
|
return successState.savingState.dataOrNull().orFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state by stateFlow.collectAsState()
|
val state by stateFlow.collectAsState()
|
||||||
ChangeRolesView(
|
ChangeRolesView(
|
||||||
modifier = modifier,
|
|
||||||
state = state,
|
state = state,
|
||||||
navigateUp = this::navigateUp,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.changeroommemberroles.impl
|
package io.element.android.features.rolesandpermissions.impl.roles
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
|
@ -15,17 +15,18 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedFactory
|
import dev.zacsweers.metro.AssistedFactory
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import im.vector.app.features.analytics.plan.RoomModeration
|
import im.vector.app.features.analytics.plan.RoomModeration
|
||||||
|
import io.element.android.features.rolesandpermissions.impl.RoomMemberListDataSource
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
|
import io.element.android.libraries.di.annotations.RoomCoroutineScope
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
|
|
@ -51,6 +52,7 @@ class ChangeRolesPresenter(
|
||||||
private val room: JoinedRoom,
|
private val room: JoinedRoom,
|
||||||
private val dispatchers: CoroutineDispatchers,
|
private val dispatchers: CoroutineDispatchers,
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
|
@RoomCoroutineScope private val roomCoroutineScope: CoroutineScope,
|
||||||
) : Presenter<ChangeRolesState> {
|
) : Presenter<ChangeRolesState> {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
fun interface Factory {
|
fun interface Factory {
|
||||||
|
|
@ -59,7 +61,6 @@ class ChangeRolesPresenter(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): ChangeRolesState {
|
override fun present(): ChangeRolesState {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
val dataSource = remember { RoomMemberListDataSource(room, dispatchers) }
|
val dataSource = remember { RoomMemberListDataSource(room, dispatchers) }
|
||||||
var query by rememberSaveable { mutableStateOf<String?>(null) }
|
var query by rememberSaveable { mutableStateOf<String?>(null) }
|
||||||
var searchActive by rememberSaveable { mutableStateOf(false) }
|
var searchActive by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
@ -103,7 +104,6 @@ class ChangeRolesPresenter(
|
||||||
|
|
||||||
val roomInfo by room.roomInfoFlow.collectAsState()
|
val roomInfo by room.roomInfoFlow.collectAsState()
|
||||||
fun canChangeMemberRole(userId: UserId): Boolean {
|
fun canChangeMemberRole(userId: UserId): Boolean {
|
||||||
// This is used to group the
|
|
||||||
val currentUserRole = roomInfo.roleOf(room.sessionId)
|
val currentUserRole = roomInfo.roleOf(room.sessionId)
|
||||||
val otherUserRole = roomInfo.roleOf(userId)
|
val otherUserRole = roomInfo.roleOf(userId)
|
||||||
return currentUserRole.powerLevel > otherUserRole.powerLevel
|
return currentUserRole.powerLevel > otherUserRole.powerLevel
|
||||||
|
|
@ -142,7 +142,7 @@ class ChangeRolesPresenter(
|
||||||
saveState.value = AsyncAction.ConfirmingNoParams
|
saveState.value = AsyncAction.ConfirmingNoParams
|
||||||
}
|
}
|
||||||
!saveState.value.isLoading() -> {
|
!saveState.value.isLoading() -> {
|
||||||
coroutineScope.save(usersWithRole.value, selectedUsers, saveState)
|
roomCoroutineScope.save(usersWithRole.value, selectedUsers, saveState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -213,9 +213,9 @@ class ChangeRolesPresenter(
|
||||||
saveState.value = AsyncAction.Failure(it)
|
saveState.value = AsyncAction.Failure(it)
|
||||||
}
|
}
|
||||||
.onSuccess {
|
.onSuccess {
|
||||||
saveState.value = AsyncAction.Success(true)
|
|
||||||
// Asynchronously reload the room members
|
// Asynchronously reload the room members
|
||||||
launch { room.updateMembers() }
|
launch { room.updateMembers() }
|
||||||
|
saveState.value = AsyncAction.Success(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2024 New Vector Ltd.
|
* Copyright 2025 New Vector Ltd.
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.changeroommemberroles.impl
|
package io.element.android.features.rolesandpermissions.impl.roles
|
||||||
|
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2024 New Vector Ltd.
|
* Copyright 2025 New Vector Ltd.
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.changeroommemberroles.impl
|
package io.element.android.features.rolesandpermissions.impl.roles
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.changeroommemberroles.impl
|
package io.element.android.features.rolesandpermissions.impl.roles
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
|
@ -29,8 +29,6 @@ import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
@ -39,9 +37,9 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.features.rolesandpermissions.impl.R
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncIndicator
|
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost
|
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost
|
||||||
import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState
|
import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState
|
||||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||||
|
|
@ -50,6 +48,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||||
|
import io.element.android.libraries.designsystem.components.dialogs.SaveChangesDialog
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Checkbox
|
import io.element.android.libraries.designsystem.theme.components.Checkbox
|
||||||
|
|
@ -76,10 +75,8 @@ import kotlinx.collections.immutable.ImmutableList
|
||||||
@Composable
|
@Composable
|
||||||
fun ChangeRolesView(
|
fun ChangeRolesView(
|
||||||
state: ChangeRolesState,
|
state: ChangeRolesState,
|
||||||
navigateUp: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val latestNavigateUp by rememberUpdatedState(newValue = navigateUp)
|
|
||||||
BackHandler(enabled = !state.isSearchActive) {
|
BackHandler(enabled = !state.isSearchActive) {
|
||||||
state.eventSink(ChangeRolesEvent.Exit)
|
state.eventSink(ChangeRolesEvent.Exit)
|
||||||
}
|
}
|
||||||
|
|
@ -167,24 +164,13 @@ fun ChangeRolesView(
|
||||||
|
|
||||||
val asyncIndicatorState = rememberAsyncIndicatorState()
|
val asyncIndicatorState = rememberAsyncIndicatorState()
|
||||||
AsyncIndicatorHost(modifier = Modifier.statusBarsPadding(), asyncIndicatorState)
|
AsyncIndicatorHost(modifier = Modifier.statusBarsPadding(), asyncIndicatorState)
|
||||||
|
|
||||||
AsyncActionView(
|
AsyncActionView(
|
||||||
async = state.savingState,
|
async = state.savingState,
|
||||||
onSuccess = { changeSaved ->
|
onSuccess = {},
|
||||||
if (changeSaved) {
|
|
||||||
asyncIndicatorState.enqueue(durationMs = AsyncIndicator.DURATION_SHORT) {
|
|
||||||
AsyncIndicator.Custom(text = stringResource(CommonStrings.common_saved_changes))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
latestNavigateUp()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmationDialog = { confirming ->
|
confirmationDialog = { confirming ->
|
||||||
when (confirming) {
|
when (confirming) {
|
||||||
is AsyncAction.ConfirmingCancellation -> {
|
is AsyncAction.ConfirmingCancellation -> {
|
||||||
ConfirmationDialog(
|
SaveChangesDialog(
|
||||||
title = stringResource(CommonStrings.dialog_unsaved_changes_title),
|
|
||||||
content = stringResource(CommonStrings.dialog_unsaved_changes_description_android),
|
|
||||||
onSubmitClick = { state.eventSink(ChangeRolesEvent.Exit) },
|
onSubmitClick = { state.eventSink(ChangeRolesEvent.Exit) },
|
||||||
onDismiss = { state.eventSink(ChangeRolesEvent.CloseDialog) }
|
onDismiss = { state.eventSink(ChangeRolesEvent.CloseDialog) }
|
||||||
)
|
)
|
||||||
|
|
@ -415,8 +401,7 @@ private fun MemberRow(
|
||||||
internal fun ChangeRolesViewPreview(@PreviewParameter(ChangeRolesStateProvider::class) state: ChangeRolesState) {
|
internal fun ChangeRolesViewPreview(@PreviewParameter(ChangeRolesStateProvider::class) state: ChangeRolesState) {
|
||||||
ElementPreview {
|
ElementPreview {
|
||||||
ChangeRolesView(
|
ChangeRolesView(
|
||||||
state = state,
|
state = state
|
||||||
navigateUp = {},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.changeroommemberroles.impl
|
package io.element.android.features.rolesandpermissions.impl.roles
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -20,8 +20,8 @@ import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import io.element.android.annotations.ContributesNode
|
import io.element.android.annotations.ContributesNode
|
||||||
import io.element.android.appnav.di.RoomGraphFactory
|
import io.element.android.appnav.di.RoomGraphFactory
|
||||||
import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesEntryPoint
|
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint
|
||||||
import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesListType
|
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType
|
||||||
import io.element.android.libraries.architecture.NodeInputs
|
import io.element.android.libraries.architecture.NodeInputs
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
import io.element.android.libraries.architecture.inputs
|
import io.element.android.libraries.architecture.inputs
|
||||||
|
|
@ -70,7 +70,7 @@ class ChangeRoomMemberRolesRootNode(
|
||||||
|
|
||||||
override val roomId: RoomId = inputs.joinedRoom.roomId
|
override val roomId: RoomId = inputs.joinedRoom.roomId
|
||||||
|
|
||||||
override suspend fun waitForRoleChanged() {
|
override suspend fun waitForCompletion(): Boolean {
|
||||||
waitForChildAttached<ChangeRolesNode>().waitForRoleChanged()
|
return waitForChildAttached<ChangeRolesNode>().waitForCompletion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,13 +5,13 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.changeroommemberroles.impl
|
package io.element.android.features.rolesandpermissions.impl.roles
|
||||||
|
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesEntryPoint
|
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint
|
||||||
import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesListType
|
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.rolesandpermissions
|
package io.element.android.features.rolesandpermissions.impl.root
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.rolesandpermissions
|
package io.element.android.features.rolesandpermissions.impl.root
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
|
@ -39,9 +39,8 @@ class RolesAndPermissionsNode(
|
||||||
interface Callback : Plugin, RolesAndPermissionsNavigator {
|
interface Callback : Plugin, RolesAndPermissionsNavigator {
|
||||||
override fun openAdminList()
|
override fun openAdminList()
|
||||||
override fun openModeratorList()
|
override fun openModeratorList()
|
||||||
override fun openEditRoomDetailsPermissions()
|
override fun openEditPermissions()
|
||||||
override fun openMessagesAndContentPermissions()
|
|
||||||
override fun openModerationPermissions()
|
|
||||||
override fun onBackClick() {}
|
override fun onBackClick() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,7 +84,5 @@ interface RolesAndPermissionsNavigator {
|
||||||
fun onBackClick() {}
|
fun onBackClick() {}
|
||||||
fun openAdminList() {}
|
fun openAdminList() {}
|
||||||
fun openModeratorList() {}
|
fun openModeratorList() {}
|
||||||
fun openEditRoomDetailsPermissions() {}
|
fun openEditPermissions() {}
|
||||||
fun openMessagesAndContentPermissions() {}
|
|
||||||
fun openModerationPermissions() {}
|
|
||||||
}
|
}
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.rolesandpermissions
|
package io.element.android.features.rolesandpermissions.impl.root
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.rolesandpermissions
|
package io.element.android.features.rolesandpermissions.impl.root
|
||||||
|
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.rolesandpermissions
|
package io.element.android.features.rolesandpermissions.impl.root
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.rolesandpermissions
|
package io.element.android.features.rolesandpermissions.impl.root
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
|
@ -20,7 +20,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.roomdetails.impl.R
|
import io.element.android.features.rolesandpermissions.impl.R
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
|
|
@ -78,25 +78,16 @@ fun RolesAndPermissionsView(
|
||||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Edit()))
|
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Edit()))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ListSectionHeader(title = stringResource(R.string.screen_room_roles_and_permissions_permissions_header), hasDivider = true)
|
HorizontalDivider()
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_room_details)) },
|
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_permissions_header)) },
|
||||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Info())),
|
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Settings())),
|
||||||
onClick = { rolesAndPermissionsNavigator.openEditRoomDetailsPermissions() },
|
onClick = { rolesAndPermissionsNavigator.openEditPermissions() },
|
||||||
)
|
|
||||||
ListItem(
|
|
||||||
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_messages_and_content)) },
|
|
||||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Chat())),
|
|
||||||
onClick = { rolesAndPermissionsNavigator.openMessagesAndContentPermissions() },
|
|
||||||
)
|
|
||||||
ListItem(
|
|
||||||
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_member_moderation)) },
|
|
||||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.User())),
|
|
||||||
onClick = { rolesAndPermissionsNavigator.openModerationPermissions() },
|
|
||||||
)
|
)
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_reset)) },
|
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_reset)) },
|
||||||
|
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Delete())),
|
||||||
onClick = { state.eventSink(RolesAndPermissionsEvents.ResetPermissions) },
|
onClick = { state.eventSink(RolesAndPermissionsEvents.ResetPermissions) },
|
||||||
style = ListItemStyle.Destructive,
|
style = ListItemStyle.Destructive,
|
||||||
)
|
)
|
||||||
|
|
@ -9,10 +9,10 @@
|
||||||
<string name="screen_room_change_permissions_messages_and_content">"Messages and content"</string>
|
<string name="screen_room_change_permissions_messages_and_content">"Messages and content"</string>
|
||||||
<string name="screen_room_change_permissions_moderators">"Admins and moderators"</string>
|
<string name="screen_room_change_permissions_moderators">"Admins and moderators"</string>
|
||||||
<string name="screen_room_change_permissions_remove_people">"Remove people and decline requests to join"</string>
|
<string name="screen_room_change_permissions_remove_people">"Remove people and decline requests to join"</string>
|
||||||
<string name="screen_room_change_permissions_room_avatar">"Change room avatar"</string>
|
<string name="screen_room_change_permissions_room_avatar">"Change avatar"</string>
|
||||||
<string name="screen_room_change_permissions_room_details">"Room details"</string>
|
<string name="screen_room_change_permissions_room_details">"Room details"</string>
|
||||||
<string name="screen_room_change_permissions_room_name">"Change room name"</string>
|
<string name="screen_room_change_permissions_room_name">"Change name"</string>
|
||||||
<string name="screen_room_change_permissions_room_topic">"Change room topic"</string>
|
<string name="screen_room_change_permissions_room_topic">"Change topic"</string>
|
||||||
<string name="screen_room_change_permissions_send_messages">"Send messages"</string>
|
<string name="screen_room_change_permissions_send_messages">"Send messages"</string>
|
||||||
<string name="screen_room_change_role_administrators_title">"Edit Admins"</string>
|
<string name="screen_room_change_role_administrators_title">"Edit Admins"</string>
|
||||||
<string name="screen_room_change_role_confirm_add_admin_description">"You will not be able to undo this action. You are promoting the user to have the same power level as you."</string>
|
<string name="screen_room_change_role_confirm_add_admin_description">"You will not be able to undo this action. You are promoting the user to have the same power level as you."</string>
|
||||||
|
|
@ -66,5 +66,6 @@
|
||||||
<string name="screen_room_roles_and_permissions_reset_confirm_title">"Reset permissions?"</string>
|
<string name="screen_room_roles_and_permissions_reset_confirm_title">"Reset permissions?"</string>
|
||||||
<string name="screen_room_roles_and_permissions_roles_header">"Roles"</string>
|
<string name="screen_room_roles_and_permissions_roles_header">"Roles"</string>
|
||||||
<string name="screen_room_roles_and_permissions_room_details">"Room details"</string>
|
<string name="screen_room_roles_and_permissions_room_details">"Room details"</string>
|
||||||
|
<string name="screen_room_roles_and_permissions_space_details">"Space details"</string>
|
||||||
<string name="screen_room_roles_and_permissions_title">"Roles and permissions"</string>
|
<string name="screen_room_roles_and_permissions_title">"Roles and permissions"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions
|
package io.element.android.features.rolesandpermissions.impl.permissions
|
||||||
|
|
||||||
import app.cash.molecule.RecompositionMode
|
import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
|
|
@ -17,7 +17,6 @@ import im.vector.app.features.analytics.plan.RoomModeration
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember.Role.Admin
|
import io.element.android.libraries.matrix.api.room.RoomMember.Role.Admin
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember.Role.Moderator
|
import io.element.android.libraries.matrix.api.room.RoomMember.Role.Moderator
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember.Role.User
|
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
|
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
|
||||||
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||||
|
|
@ -26,19 +25,17 @@ import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class ChangeBaseRoomPermissionsPresenterTest {
|
class ChangeRoomPermissionsPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - initial state`() = runTest {
|
fun `present - initial state`() = runTest {
|
||||||
val section = ChangeRoomPermissionsSection.RoomDetails
|
val presenter = createChangeRoomPermissionsPresenter()
|
||||||
val presenter = createChangeRoomPermissionsPresenter(section = section)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
// Initial state, no permissions loaded
|
// Initial state, no permissions loaded
|
||||||
awaitItem().run {
|
awaitItem().run {
|
||||||
assertThat(this.section).isEqualTo(section)
|
|
||||||
assertThat(this.currentPermissions).isNull()
|
assertThat(this.currentPermissions).isNull()
|
||||||
assertThat(this.items).isNotEmpty()
|
assertThat(this.itemsBySection).isNotEmpty()
|
||||||
assertThat(this.hasChanges).isFalse()
|
assertThat(this.hasChanges).isFalse()
|
||||||
assertThat(this.saveAction).isEqualTo(AsyncAction.Uninitialized)
|
assertThat(this.saveAction).isEqualTo(AsyncAction.Uninitialized)
|
||||||
assertThat(this.confirmExitAction).isEqualTo(AsyncAction.Uninitialized)
|
assertThat(this.confirmExitAction).isEqualTo(AsyncAction.Uninitialized)
|
||||||
|
|
@ -50,42 +47,22 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - RoomDetails section contains the right items`() = runTest {
|
fun `present - items by section are correct for room`() = runTest {
|
||||||
val section = ChangeRoomPermissionsSection.RoomDetails
|
val presenter = createChangeRoomPermissionsPresenter()
|
||||||
val presenter = createChangeRoomPermissionsPresenter(section = section)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
assertThat(awaitUpdatedItem().items).containsExactly(
|
val itemsBySection = awaitUpdatedItem().itemsBySection
|
||||||
|
assertThat(itemsBySection[RoomPermissionsSection.RoomDetails]).containsExactly(
|
||||||
RoomPermissionType.ROOM_NAME,
|
RoomPermissionType.ROOM_NAME,
|
||||||
RoomPermissionType.ROOM_AVATAR,
|
RoomPermissionType.ROOM_AVATAR,
|
||||||
RoomPermissionType.ROOM_TOPIC,
|
RoomPermissionType.ROOM_TOPIC,
|
||||||
)
|
)
|
||||||
}
|
assertThat(itemsBySection[RoomPermissionsSection.MessagesAndContent]).containsExactly(
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - MessagesAndContent section contains the right items`() = runTest {
|
|
||||||
val section = ChangeRoomPermissionsSection.MessagesAndContent
|
|
||||||
val presenter = createChangeRoomPermissionsPresenter(section = section)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
assertThat(awaitUpdatedItem().items).containsExactly(
|
|
||||||
RoomPermissionType.SEND_EVENTS,
|
RoomPermissionType.SEND_EVENTS,
|
||||||
RoomPermissionType.REDACT_EVENTS,
|
RoomPermissionType.REDACT_EVENTS,
|
||||||
)
|
)
|
||||||
}
|
assertThat(itemsBySection[RoomPermissionsSection.MembershipModeration]).containsExactly(
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - MembershipModeration section contains the right items`() = runTest {
|
|
||||||
val section = ChangeRoomPermissionsSection.MembershipModeration
|
|
||||||
val presenter = createChangeRoomPermissionsPresenter(section = section)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
assertThat(awaitUpdatedItem().items).containsExactly(
|
|
||||||
RoomPermissionType.INVITE,
|
RoomPermissionType.INVITE,
|
||||||
RoomPermissionType.KICK,
|
RoomPermissionType.KICK,
|
||||||
RoomPermissionType.BAN,
|
RoomPermissionType.BAN,
|
||||||
|
|
@ -103,7 +80,7 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
||||||
assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel)
|
assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel)
|
||||||
assertThat(state.hasChanges).isFalse()
|
assertThat(state.hasChanges).isFalse()
|
||||||
|
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Moderator))
|
||||||
|
|
||||||
awaitItem().run {
|
awaitItem().run {
|
||||||
assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel)
|
assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel)
|
||||||
|
|
@ -120,18 +97,18 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
||||||
}.test {
|
}.test {
|
||||||
val state = awaitUpdatedItem()
|
val state = awaitUpdatedItem()
|
||||||
|
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, Moderator))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, SelectableRole.Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, Moderator))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, SelectableRole.Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, Moderator))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, SelectableRole.Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, Moderator))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, SelectableRole.Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, Moderator))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, SelectableRole.Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, Moderator))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, SelectableRole.Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, Moderator))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, SelectableRole.Moderator))
|
||||||
|
|
||||||
val items = cancelAndConsumeRemainingEvents()
|
val itemsBySection = cancelAndConsumeRemainingEvents()
|
||||||
|
|
||||||
(items.last() as? Event.Item<ChangeRoomPermissionsState>)?.value?.run {
|
(itemsBySection.last() as? Event.Item<ChangeRoomPermissionsState>)?.value?.run {
|
||||||
assertThat(currentPermissions).isEqualTo(
|
assertThat(currentPermissions).isEqualTo(
|
||||||
RoomPowerLevelsValues(
|
RoomPowerLevelsValues(
|
||||||
invite = Moderator.powerLevel,
|
invite = Moderator.powerLevel,
|
||||||
|
|
@ -165,14 +142,14 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
||||||
assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel)
|
assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel)
|
||||||
assertThat(state.hasChanges).isFalse()
|
assertThat(state.hasChanges).isFalse()
|
||||||
|
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, Moderator))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, SelectableRole.Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, Moderator))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, SelectableRole.Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, Moderator))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, SelectableRole.Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, User))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, SelectableRole.Everyone))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, Admin))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, SelectableRole.Admin))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, Admin))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, SelectableRole.Admin))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, Admin))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, SelectableRole.Admin))
|
||||||
skipItems(7)
|
skipItems(7)
|
||||||
assertThat(awaitItem().hasChanges).isTrue()
|
assertThat(awaitItem().hasChanges).isTrue()
|
||||||
|
|
||||||
|
|
@ -230,7 +207,7 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
||||||
assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel)
|
assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel)
|
||||||
assertThat(state.hasChanges).isFalse()
|
assertThat(state.hasChanges).isFalse()
|
||||||
|
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Moderator))
|
||||||
assertThat(awaitItem().hasChanges).isTrue()
|
assertThat(awaitItem().hasChanges).isTrue()
|
||||||
|
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.Save)
|
state.eventSink(ChangeRoomPermissionsEvent.Save)
|
||||||
|
|
@ -259,7 +236,7 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val state = awaitUpdatedItem()
|
val state = awaitUpdatedItem()
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Moderator))
|
||||||
assertThat(awaitItem().hasChanges).isTrue()
|
assertThat(awaitItem().hasChanges).isTrue()
|
||||||
|
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.Exit)
|
state.eventSink(ChangeRoomPermissionsEvent.Exit)
|
||||||
|
|
@ -285,13 +262,11 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createChangeRoomPermissionsPresenter(
|
private fun createChangeRoomPermissionsPresenter(
|
||||||
section: ChangeRoomPermissionsSection = ChangeRoomPermissionsSection.RoomDetails,
|
|
||||||
room: FakeJoinedRoom = FakeJoinedRoom(
|
room: FakeJoinedRoom = FakeJoinedRoom(
|
||||||
baseRoom = FakeBaseRoom(powerLevelsResult = { Result.success(defaultPermissions()) }),
|
baseRoom = FakeBaseRoom(powerLevelsResult = { Result.success(defaultPermissions()) }),
|
||||||
),
|
),
|
||||||
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
||||||
) = ChangeRoomPermissionsPresenter(
|
) = ChangeRoomPermissionsPresenter(
|
||||||
section = section,
|
|
||||||
room = room,
|
room = room,
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
)
|
)
|
||||||
|
|
@ -5,40 +5,40 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions
|
package io.element.android.features.rolesandpermissions.impl.permissions
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
import androidx.compose.ui.test.onAllNodesWithText
|
|
||||||
import androidx.compose.ui.test.onFirst
|
|
||||||
import androidx.compose.ui.test.performClick
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.roomdetails.impl.R
|
import io.element.android.features.rolesandpermissions.impl.R
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.clickOnFirst
|
import io.element.android.tests.testutils.clickOnFirst
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import io.element.android.tests.testutils.pressBackKey
|
import io.element.android.tests.testutils.pressBackKey
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
import org.junit.rules.TestRule
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ChangeBaseRoomPermissionsViewTest {
|
class ChangeRoomPermissionsViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on back icon invokes Exit`() {
|
fun `click on back icon invokes Exit`() {
|
||||||
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
|
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
|
||||||
rule.setChangeRoomPermissionsRule(
|
rule.setChangeRoomPermissionsRule(
|
||||||
eventsRecorder = recorder,
|
state = aChangeRoomPermissionsState(
|
||||||
|
eventSink = recorder
|
||||||
|
)
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
rule.pressBack()
|
||||||
recorder.assertSingle(ChangeRoomPermissionsEvent.Exit)
|
recorder.assertSingle(ChangeRoomPermissionsEvent.Exit)
|
||||||
|
|
@ -48,7 +48,9 @@ class ChangeBaseRoomPermissionsViewTest {
|
||||||
fun `click on back key invokes Exit`() {
|
fun `click on back key invokes Exit`() {
|
||||||
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
|
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
|
||||||
rule.setChangeRoomPermissionsRule(
|
rule.setChangeRoomPermissionsRule(
|
||||||
eventsRecorder = recorder,
|
state = aChangeRoomPermissionsState(
|
||||||
|
eventSink = recorder
|
||||||
|
)
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
rule.pressBackKey()
|
||||||
recorder.assertSingle(ChangeRoomPermissionsEvent.Exit)
|
recorder.assertSingle(ChangeRoomPermissionsEvent.Exit)
|
||||||
|
|
@ -59,11 +61,9 @@ class ChangeBaseRoomPermissionsViewTest {
|
||||||
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
|
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
|
||||||
rule.setChangeRoomPermissionsRule(
|
rule.setChangeRoomPermissionsRule(
|
||||||
state = aChangeRoomPermissionsState(
|
state = aChangeRoomPermissionsState(
|
||||||
section = ChangeRoomPermissionsSection.RoomDetails,
|
|
||||||
hasChanges = true,
|
hasChanges = true,
|
||||||
eventSink = recorder,
|
eventSink = recorder,
|
||||||
),
|
),
|
||||||
eventsRecorder = recorder,
|
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
rule.pressBackKey()
|
||||||
recorder.assertSingle(ChangeRoomPermissionsEvent.Exit)
|
recorder.assertSingle(ChangeRoomPermissionsEvent.Exit)
|
||||||
|
|
@ -74,12 +74,10 @@ class ChangeBaseRoomPermissionsViewTest {
|
||||||
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
|
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
|
||||||
rule.setChangeRoomPermissionsRule(
|
rule.setChangeRoomPermissionsRule(
|
||||||
state = aChangeRoomPermissionsState(
|
state = aChangeRoomPermissionsState(
|
||||||
section = ChangeRoomPermissionsSection.RoomDetails,
|
|
||||||
hasChanges = true,
|
hasChanges = true,
|
||||||
confirmExitAction = AsyncAction.ConfirmingNoParams,
|
confirmExitAction = AsyncAction.ConfirmingNoParams,
|
||||||
eventSink = recorder,
|
eventSink = recorder,
|
||||||
),
|
),
|
||||||
eventsRecorder = recorder,
|
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_discard)
|
rule.clickOn(CommonStrings.action_discard)
|
||||||
recorder.assertSingle(ChangeRoomPermissionsEvent.Exit)
|
recorder.assertSingle(ChangeRoomPermissionsEvent.Exit)
|
||||||
|
|
@ -90,12 +88,10 @@ class ChangeBaseRoomPermissionsViewTest {
|
||||||
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
|
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
|
||||||
rule.setChangeRoomPermissionsRule(
|
rule.setChangeRoomPermissionsRule(
|
||||||
state = aChangeRoomPermissionsState(
|
state = aChangeRoomPermissionsState(
|
||||||
section = ChangeRoomPermissionsSection.RoomDetails,
|
|
||||||
hasChanges = true,
|
hasChanges = true,
|
||||||
confirmExitAction = AsyncAction.ConfirmingNoParams,
|
confirmExitAction = AsyncAction.ConfirmingNoParams,
|
||||||
eventSink = recorder,
|
eventSink = recorder,
|
||||||
),
|
),
|
||||||
eventsRecorder = recorder,
|
|
||||||
)
|
)
|
||||||
rule.clickOnFirst(CommonStrings.action_save)
|
rule.clickOnFirst(CommonStrings.action_save)
|
||||||
recorder.assertSingle(ChangeRoomPermissionsEvent.Save)
|
recorder.assertSingle(ChangeRoomPermissionsEvent.Save)
|
||||||
|
|
@ -105,21 +101,19 @@ class ChangeBaseRoomPermissionsViewTest {
|
||||||
fun `click on a role item triggers ChangeRole event`() {
|
fun `click on a role item triggers ChangeRole event`() {
|
||||||
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
|
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
|
||||||
rule.setChangeRoomPermissionsRule(
|
rule.setChangeRoomPermissionsRule(
|
||||||
eventsRecorder = recorder,
|
state = aChangeRoomPermissionsState(
|
||||||
)
|
itemsBySection = persistentMapOf(
|
||||||
val admins = rule.activity.getText(R.string.screen_room_change_permissions_administrators).toString()
|
// Makes sure there is only one item to click on
|
||||||
val moderators = rule.activity.getText(R.string.screen_room_change_permissions_moderators).toString()
|
RoomPermissionsSection.RoomDetails to persistentListOf(RoomPermissionType.ROOM_NAME)
|
||||||
val users = rule.activity.getText(R.string.screen_room_change_permissions_everyone).toString()
|
),
|
||||||
rule.onAllNodesWithText(admins).onFirst().performClick()
|
eventSink = recorder,
|
||||||
rule.onAllNodesWithText(moderators).onFirst().performClick()
|
|
||||||
rule.onAllNodesWithText(users).onFirst().performClick()
|
|
||||||
recorder.assertList(
|
|
||||||
listOf(
|
|
||||||
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.Admin),
|
|
||||||
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.Moderator),
|
|
||||||
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.User),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
rule.clickOn(R.string.screen_room_change_permissions_room_name)
|
||||||
|
rule.clickOn(R.string.screen_room_change_permissions_everyone)
|
||||||
|
recorder.assertSingle(
|
||||||
|
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Everyone),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -127,11 +121,9 @@ class ChangeBaseRoomPermissionsViewTest {
|
||||||
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
|
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
|
||||||
rule.setChangeRoomPermissionsRule(
|
rule.setChangeRoomPermissionsRule(
|
||||||
state = aChangeRoomPermissionsState(
|
state = aChangeRoomPermissionsState(
|
||||||
section = ChangeRoomPermissionsSection.RoomDetails,
|
|
||||||
hasChanges = true,
|
hasChanges = true,
|
||||||
eventSink = recorder,
|
eventSink = recorder,
|
||||||
),
|
),
|
||||||
eventsRecorder = recorder,
|
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_save)
|
rule.clickOn(CommonStrings.action_save)
|
||||||
recorder.assertSingle(ChangeRoomPermissionsEvent.Save)
|
recorder.assertSingle(ChangeRoomPermissionsEvent.Save)
|
||||||
|
|
@ -139,14 +131,13 @@ class ChangeBaseRoomPermissionsViewTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `a successful save exits the screen`() {
|
fun `a successful save exits the screen`() {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnceWithParam(true) { callback ->
|
||||||
rule.setChangeRoomPermissionsRule(
|
rule.setChangeRoomPermissionsRule(
|
||||||
state = aChangeRoomPermissionsState(
|
state = aChangeRoomPermissionsState(
|
||||||
section = ChangeRoomPermissionsSection.RoomDetails,
|
|
||||||
hasChanges = true,
|
hasChanges = true,
|
||||||
saveAction = AsyncAction.Success(Unit),
|
saveAction = AsyncAction.Success(Unit),
|
||||||
),
|
),
|
||||||
onBackClick = callback
|
onComplete = callback
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_save)
|
rule.clickOn(CommonStrings.action_save)
|
||||||
}
|
}
|
||||||
|
|
@ -157,12 +148,10 @@ class ChangeBaseRoomPermissionsViewTest {
|
||||||
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
|
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
|
||||||
rule.setChangeRoomPermissionsRule(
|
rule.setChangeRoomPermissionsRule(
|
||||||
state = aChangeRoomPermissionsState(
|
state = aChangeRoomPermissionsState(
|
||||||
section = ChangeRoomPermissionsSection.RoomDetails,
|
|
||||||
hasChanges = true,
|
hasChanges = true,
|
||||||
saveAction = AsyncAction.Failure(IllegalStateException("Failed to set room power levels")),
|
saveAction = AsyncAction.Failure(IllegalStateException("Failed to set room power levels")),
|
||||||
eventSink = recorder,
|
eventSink = recorder,
|
||||||
),
|
),
|
||||||
eventsRecorder = recorder,
|
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
rule.clickOn(CommonStrings.action_ok)
|
||||||
recorder.assertSingle(ChangeRoomPermissionsEvent.ResetPendingActions)
|
recorder.assertSingle(ChangeRoomPermissionsEvent.ResetPendingActions)
|
||||||
|
|
@ -170,17 +159,13 @@ class ChangeBaseRoomPermissionsViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setChangeRoomPermissionsRule(
|
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setChangeRoomPermissionsRule(
|
||||||
eventsRecorder: EventsRecorder<ChangeRoomPermissionsEvent> = EventsRecorder(expectEvents = false),
|
state: ChangeRoomPermissionsState = aChangeRoomPermissionsState(),
|
||||||
state: ChangeRoomPermissionsState = aChangeRoomPermissionsState(
|
onComplete: (Boolean) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
section = ChangeRoomPermissionsSection.RoomDetails,
|
|
||||||
eventSink = eventsRecorder,
|
|
||||||
),
|
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
ChangeRoomPermissionsView(
|
ChangeRoomPermissionsView(
|
||||||
state = state,
|
state = state,
|
||||||
onBackClick = onBackClick,
|
onComplete = onComplete,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,10 +5,10 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.changeroommemberroles.impl
|
package io.element.android.features.rolesandpermissions.impl.roles
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesListType
|
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.changeroommemberroles.impl
|
package io.element.android.features.rolesandpermissions.impl.roles
|
||||||
|
|
||||||
import app.cash.molecule.RecompositionMode
|
import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
|
|
@ -351,21 +351,15 @@ class ChangeRolesPresenterTest {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
skipItems(1)
|
skipItems(2)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.selectedUsers).hasSize(1)
|
assertThat(initialState.selectedUsers).hasSize(1)
|
||||||
|
|
||||||
initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2)))
|
initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2)))
|
||||||
awaitItem().eventSink(ChangeRolesEvent.Save)
|
awaitItem().eventSink(ChangeRolesEvent.Save)
|
||||||
val confirmingState = awaitItem()
|
val confirmingState = awaitItem()
|
||||||
assertThat(confirmingState.savingState).isEqualTo(AsyncAction.ConfirmingNoParams)
|
assertThat(confirmingState.savingState).isEqualTo(AsyncAction.ConfirmingNoParams)
|
||||||
|
|
||||||
confirmingState.eventSink(ChangeRolesEvent.Save)
|
confirmingState.eventSink(ChangeRolesEvent.Save)
|
||||||
|
assertThat(awaitItem().savingState).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
val loadingState = awaitItem()
|
|
||||||
assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java)
|
|
||||||
skipItems(1)
|
|
||||||
|
|
||||||
assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(true))
|
assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -413,18 +407,12 @@ class ChangeRolesPresenterTest {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
skipItems(1)
|
skipItems(2)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.selectedUsers).hasSize(1)
|
assertThat(initialState.selectedUsers).hasSize(1)
|
||||||
|
|
||||||
initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2)))
|
initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2)))
|
||||||
|
|
||||||
awaitItem().eventSink(ChangeRolesEvent.Save)
|
awaitItem().eventSink(ChangeRolesEvent.Save)
|
||||||
|
assertThat(awaitItem().savingState).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
val loadingState = awaitItem()
|
|
||||||
assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java)
|
|
||||||
skipItems(1)
|
|
||||||
|
|
||||||
assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(true))
|
assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(true))
|
||||||
assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.ChangeMemberRole, RoomModeration.Role.Moderator))
|
assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.ChangeMemberRole, RoomModeration.Role.Moderator))
|
||||||
}
|
}
|
||||||
|
|
@ -491,7 +479,7 @@ class ChangeRolesPresenterTest {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
skipItems(1)
|
skipItems(2)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.selectedUsers).hasSize(1)
|
assertThat(initialState.selectedUsers).hasSize(1)
|
||||||
|
|
||||||
|
|
@ -501,8 +489,6 @@ class ChangeRolesPresenterTest {
|
||||||
|
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java)
|
assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
skipItems(1)
|
|
||||||
|
|
||||||
assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(true))
|
assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(true))
|
||||||
assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.ChangeMemberRole, RoomModeration.Role.User))
|
assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.ChangeMemberRole, RoomModeration.Role.User))
|
||||||
}
|
}
|
||||||
|
|
@ -520,7 +506,7 @@ class ChangeRolesPresenterTest {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
skipItems(1)
|
skipItems(2)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.selectedUsers).hasSize(1)
|
assertThat(initialState.selectedUsers).hasSize(1)
|
||||||
|
|
||||||
|
|
@ -529,7 +515,6 @@ class ChangeRolesPresenterTest {
|
||||||
awaitItem().eventSink(ChangeRolesEvent.Save)
|
awaitItem().eventSink(ChangeRolesEvent.Save)
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java)
|
assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
skipItems(1)
|
|
||||||
val failedState = awaitItem()
|
val failedState = awaitItem()
|
||||||
assertThat(failedState.savingState).isInstanceOf(AsyncAction.Failure::class.java)
|
assertThat(failedState.savingState).isInstanceOf(AsyncAction.Failure::class.java)
|
||||||
|
|
||||||
|
|
@ -567,5 +552,6 @@ internal fun TestScope.createChangeRolesPresenter(
|
||||||
room = room,
|
room = room,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
|
roomCoroutineScope = this,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.changeroommemberroles.impl
|
package io.element.android.features.rolesandpermissions.impl.roles
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
|
|
@ -23,7 +23,6 @@ import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.toMatrixUser
|
import io.element.android.libraries.matrix.api.room.toMatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
|
|
@ -306,12 +305,10 @@ class ChangeRolesViewTest {
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setChangeRolesContent(
|
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setChangeRolesContent(
|
||||||
state: ChangeRolesState,
|
state: ChangeRolesState,
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
ChangeRolesView(
|
ChangeRolesView(
|
||||||
state = state,
|
state = state,
|
||||||
navigateUp = onBackClick,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,12 +5,12 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.changeroommemberroles.impl
|
package io.element.android.features.rolesandpermissions.impl.roles
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesListType
|
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType
|
||||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||||
import io.element.android.tests.testutils.node.TestParentNode
|
import io.element.android.tests.testutils.node.TestParentNode
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2024 New Vector Ltd.
|
* Copyright 2025 New Vector Ltd.
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.changeroommemberroles.impl
|
package io.element.android.features.rolesandpermissions.impl.roles
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.rolesandpermissions
|
package io.element.android.features.rolesandpermissions.impl.root
|
||||||
|
|
||||||
import app.cash.molecule.RecompositionMode
|
import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
|
|
@ -5,13 +5,13 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.rolesandpermissions
|
package io.element.android.features.rolesandpermissions.impl.root
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.roomdetails.impl.R
|
import io.element.android.features.rolesandpermissions.impl.R
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
@ -83,14 +83,12 @@ class RolesAndPermissionsViewTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(qualifiers = "h640dp")
|
@Config(qualifiers = "h640dp")
|
||||||
fun `tapping on any of the permission items open the change permissions screen`() {
|
fun `tapping permission item open the change permissions screen`() {
|
||||||
ensureCalledTimes(3) { callback ->
|
ensureCalledTimes(1) { callback ->
|
||||||
rule.setRolesAndPermissionsView(
|
rule.setRolesAndPermissionsView(
|
||||||
openPermissionScreens = callback,
|
openEditPermissions = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_room_roles_and_permissions_room_details)
|
rule.clickOn(R.string.screen_room_roles_and_permissions_permissions_header)
|
||||||
rule.clickOn(R.string.screen_room_roles_and_permissions_messages_and_content)
|
|
||||||
rule.clickOn(R.string.screen_room_roles_and_permissions_member_moderation)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,7 +182,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoles
|
||||||
goBack: () -> Unit = EnsureNeverCalled(),
|
goBack: () -> Unit = EnsureNeverCalled(),
|
||||||
openAdminList: () -> Unit = EnsureNeverCalled(),
|
openAdminList: () -> Unit = EnsureNeverCalled(),
|
||||||
openModeratorList: () -> Unit = EnsureNeverCalled(),
|
openModeratorList: () -> Unit = EnsureNeverCalled(),
|
||||||
openPermissionScreens: () -> Unit = EnsureNeverCalled(),
|
openEditPermissions: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
setSafeContent {
|
setSafeContent {
|
||||||
RolesAndPermissionsView(
|
RolesAndPermissionsView(
|
||||||
|
|
@ -193,9 +191,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoles
|
||||||
override fun onBackClick() = goBack()
|
override fun onBackClick() = goBack()
|
||||||
override fun openAdminList() = openAdminList()
|
override fun openAdminList() = openAdminList()
|
||||||
override fun openModeratorList() = openModeratorList()
|
override fun openModeratorList() = openModeratorList()
|
||||||
override fun openEditRoomDetailsPermissions() = openPermissionScreens()
|
override fun openEditPermissions() = openEditPermissions()
|
||||||
override fun openModerationPermissions() = openPermissionScreens()
|
|
||||||
override fun openMessagesAndContentPermissions() = openPermissionScreens()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -10,11 +10,11 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.features.changeroommemberroles.test"
|
namespace = "io.element.android.features.rolesandpermissions.test"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.features.changeroommemberroles.api)
|
implementation(projects.features.rolesandpermissions.api)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.tests.testutils)
|
implementation(projects.tests.testutils)
|
||||||
|
|
@ -9,8 +9,8 @@ package io.element.android.features.changeroommemberroles.test
|
||||||
|
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesEntryPoint
|
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint
|
||||||
import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesListType
|
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.changeroommemberroles.test
|
||||||
|
|
||||||
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
|
import com.bumble.appyx.core.node.Node
|
||||||
|
import io.element.android.features.rolesandpermissions.api.RolesAndPermissionsEntryPoint
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
|
||||||
|
class FakeRolesAndPermissionsEntryPoint : RolesAndPermissionsEntryPoint {
|
||||||
|
override fun createNode(parentNode: Node, buildContext: BuildContext): Node {
|
||||||
|
lambdaError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -56,7 +56,7 @@ dependencies {
|
||||||
implementation(projects.features.verifysession.api)
|
implementation(projects.features.verifysession.api)
|
||||||
implementation(projects.features.reportroom.api)
|
implementation(projects.features.reportroom.api)
|
||||||
implementation(projects.features.roommembermoderation.api)
|
implementation(projects.features.roommembermoderation.api)
|
||||||
implementation(projects.features.changeroommemberroles.api)
|
implementation(projects.features.rolesandpermissions.api)
|
||||||
implementation(projects.features.invitepeople.api)
|
implementation(projects.features.invitepeople.api)
|
||||||
|
|
||||||
testCommonDependencies(libs, true)
|
testCommonDependencies(libs, true)
|
||||||
|
|
@ -69,7 +69,7 @@ dependencies {
|
||||||
testImplementation(projects.libraries.usersearch.test)
|
testImplementation(projects.libraries.usersearch.test)
|
||||||
testImplementation(projects.libraries.featureflag.test)
|
testImplementation(projects.libraries.featureflag.test)
|
||||||
testImplementation(projects.features.call.test)
|
testImplementation(projects.features.call.test)
|
||||||
testImplementation(projects.features.changeroommemberroles.test)
|
testImplementation(projects.features.rolesandpermissions.test)
|
||||||
testImplementation(projects.features.knockrequests.test)
|
testImplementation(projects.features.knockrequests.test)
|
||||||
testImplementation(projects.features.messages.test)
|
testImplementation(projects.features.messages.test)
|
||||||
testImplementation(projects.features.poll.test)
|
testImplementation(projects.features.poll.test)
|
||||||
|
|
|
||||||
|
|
@ -26,19 +26,19 @@ import io.element.android.annotations.ContributesNode
|
||||||
import io.element.android.appconfig.LearnMoreConfig
|
import io.element.android.appconfig.LearnMoreConfig
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallType
|
||||||
import io.element.android.features.call.api.ElementCallEntryPoint
|
import io.element.android.features.call.api.ElementCallEntryPoint
|
||||||
import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesEntryPoint
|
|
||||||
import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesListType
|
|
||||||
import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint
|
import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint
|
||||||
import io.element.android.features.messages.api.MessagesEntryPoint
|
import io.element.android.features.messages.api.MessagesEntryPoint
|
||||||
import io.element.android.features.poll.api.history.PollHistoryEntryPoint
|
import io.element.android.features.poll.api.history.PollHistoryEntryPoint
|
||||||
import io.element.android.features.reportroom.api.ReportRoomEntryPoint
|
import io.element.android.features.reportroom.api.ReportRoomEntryPoint
|
||||||
|
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint
|
||||||
|
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType
|
||||||
|
import io.element.android.features.rolesandpermissions.api.RolesAndPermissionsEntryPoint
|
||||||
import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
|
import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
|
||||||
import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditNode
|
import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditNode
|
||||||
import io.element.android.features.roomdetails.impl.invite.RoomInviteMembersNode
|
import io.element.android.features.roomdetails.impl.invite.RoomInviteMembersNode
|
||||||
import io.element.android.features.roomdetails.impl.members.RoomMemberListNode
|
import io.element.android.features.roomdetails.impl.members.RoomMemberListNode
|
||||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsNode
|
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsNode
|
||||||
import io.element.android.features.roomdetails.impl.notificationsettings.RoomNotificationSettingsNode
|
import io.element.android.features.roomdetails.impl.notificationsettings.RoomNotificationSettingsNode
|
||||||
import io.element.android.features.roomdetails.impl.rolesandpermissions.RolesAndPermissionsFlowNode
|
|
||||||
import io.element.android.features.roomdetails.impl.securityandprivacy.SecurityAndPrivacyFlowNode
|
import io.element.android.features.roomdetails.impl.securityandprivacy.SecurityAndPrivacyFlowNode
|
||||||
import io.element.android.features.userprofile.shared.UserProfileNodeHelper
|
import io.element.android.features.userprofile.shared.UserProfileNodeHelper
|
||||||
import io.element.android.features.verifysession.api.OutgoingVerificationEntryPoint
|
import io.element.android.features.verifysession.api.OutgoingVerificationEntryPoint
|
||||||
|
|
@ -82,6 +82,7 @@ class RoomDetailsFlowNode(
|
||||||
private val outgoingVerificationEntryPoint: OutgoingVerificationEntryPoint,
|
private val outgoingVerificationEntryPoint: OutgoingVerificationEntryPoint,
|
||||||
private val reportRoomEntryPoint: ReportRoomEntryPoint,
|
private val reportRoomEntryPoint: ReportRoomEntryPoint,
|
||||||
private val changeRoomMemberRolesEntryPoint: ChangeRoomMemberRolesEntryPoint,
|
private val changeRoomMemberRolesEntryPoint: ChangeRoomMemberRolesEntryPoint,
|
||||||
|
private val rolesAndPermissionsEntryPoint: RolesAndPermissionsEntryPoint,
|
||||||
) : BaseFlowNode<RoomDetailsFlowNode.NavTarget>(
|
) : BaseFlowNode<RoomDetailsFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
backstack = BackStack(
|
||||||
initialElement = plugins.filterIsInstance<RoomDetailsEntryPoint.Params>().first().initialElement.toNavTarget(),
|
initialElement = plugins.filterIsInstance<RoomDetailsEntryPoint.Params>().first().initialElement.toNavTarget(),
|
||||||
|
|
@ -156,10 +157,12 @@ class RoomDetailsFlowNode(
|
||||||
changeRoomMemberRolesNode: ChangeRoomMemberRolesEntryPoint.NodeProxy,
|
changeRoomMemberRolesNode: ChangeRoomMemberRolesEntryPoint.NodeProxy,
|
||||||
->
|
->
|
||||||
commonLifecycle.coroutineScope.launch {
|
commonLifecycle.coroutineScope.launch {
|
||||||
changeRoomMemberRolesNode.waitForRoleChanged()
|
val isNewOwnerSelected = changeRoomMemberRolesNode.waitForCompletion()
|
||||||
withContext(NonCancellable) {
|
withContext(NonCancellable) {
|
||||||
backstack.pop()
|
backstack.pop()
|
||||||
roomDetailsNode.onNewOwnersSelected()
|
if (isNewOwnerSelected) {
|
||||||
|
roomDetailsNode.onNewOwnersSelected()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -343,7 +346,7 @@ class RoomDetailsFlowNode(
|
||||||
}
|
}
|
||||||
|
|
||||||
is NavTarget.AdminSettings -> {
|
is NavTarget.AdminSettings -> {
|
||||||
createNode<RolesAndPermissionsFlowNode>(buildContext)
|
rolesAndPermissionsEntryPoint.createNode(this, buildContext)
|
||||||
}
|
}
|
||||||
NavTarget.PinnedMessagesList -> {
|
NavTarget.PinnedMessagesList -> {
|
||||||
val params = MessagesEntryPoint.Params(
|
val params = MessagesEntryPoint.Params(
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ import io.element.android.libraries.designsystem.components.async.AsyncActionVie
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
import io.element.android.libraries.designsystem.components.dialogs.SaveChangesDialog
|
||||||
import io.element.android.libraries.designsystem.modifiers.clearFocusOnTap
|
import io.element.android.libraries.designsystem.modifiers.clearFocusOnTap
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
|
@ -152,9 +152,7 @@ fun RoomDetailsEditView(
|
||||||
},
|
},
|
||||||
confirmationDialog = {
|
confirmationDialog = {
|
||||||
if (state.saveAction == AsyncAction.ConfirmingCancellation) {
|
if (state.saveAction == AsyncAction.ConfirmingCancellation) {
|
||||||
ConfirmationDialog(
|
SaveChangesDialog(
|
||||||
title = stringResource(CommonStrings.dialog_unsaved_changes_title),
|
|
||||||
content = stringResource(CommonStrings.dialog_unsaved_changes_description_android),
|
|
||||||
onSubmitClick = { state.eventSink(RoomDetailsEditEvents.OnBackPress) },
|
onSubmitClick = { state.eventSink(RoomDetailsEditEvents.OnBackPress) },
|
||||||
onDismiss = { state.eventSink(RoomDetailsEditEvents.CloseDialog) }
|
onDismiss = { state.eventSink(RoomDetailsEditEvents.CloseDialog) }
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.rolesandpermissions
|
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.lifecycle.coroutineScope
|
|
||||||
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.operation.pop
|
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
|
||||||
import dev.zacsweers.metro.Assisted
|
|
||||||
import dev.zacsweers.metro.AssistedInject
|
|
||||||
import io.element.android.annotations.ContributesNode
|
|
||||||
import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesEntryPoint
|
|
||||||
import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesListType
|
|
||||||
import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsNode
|
|
||||||
import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsSection
|
|
||||||
import io.element.android.libraries.architecture.BackstackView
|
|
||||||
import io.element.android.libraries.architecture.BaseFlowNode
|
|
||||||
import io.element.android.libraries.architecture.createNode
|
|
||||||
import io.element.android.libraries.di.RoomScope
|
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
|
|
||||||
@ContributesNode(RoomScope::class)
|
|
||||||
@AssistedInject
|
|
||||||
class RolesAndPermissionsFlowNode(
|
|
||||||
@Assisted buildContext: BuildContext,
|
|
||||||
@Assisted plugins: List<Plugin>,
|
|
||||||
private val changeRoomMemberRolesEntryPoint: ChangeRoomMemberRolesEntryPoint,
|
|
||||||
private val joinedRoom: JoinedRoom,
|
|
||||||
) : BaseFlowNode<RolesAndPermissionsFlowNode.NavTarget>(
|
|
||||||
backstack = BackStack(
|
|
||||||
initialElement = NavTarget.AdminSettings,
|
|
||||||
savedStateMap = buildContext.savedStateMap,
|
|
||||||
),
|
|
||||||
buildContext = buildContext,
|
|
||||||
plugins = plugins,
|
|
||||||
) {
|
|
||||||
sealed interface NavTarget : Parcelable {
|
|
||||||
@Parcelize
|
|
||||||
data object AdminSettings : NavTarget
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
data object AdminList : NavTarget
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
data object ModeratorList : NavTarget
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
data class ChangeRoomPermissions(val section: ChangeRoomPermissionsSection) : NavTarget
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBuilt() {
|
|
||||||
super.onBuilt()
|
|
||||||
whenChildAttached { lifecycle, node: ChangeRoomMemberRolesEntryPoint.NodeProxy ->
|
|
||||||
lifecycle.coroutineScope.launch {
|
|
||||||
node.waitForRoleChanged()
|
|
||||||
backstack.pop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
|
||||||
return when (navTarget) {
|
|
||||||
is NavTarget.AdminSettings -> {
|
|
||||||
val callback = object : RolesAndPermissionsNode.Callback {
|
|
||||||
override fun openAdminList() {
|
|
||||||
backstack.push(NavTarget.AdminList)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun openModeratorList() {
|
|
||||||
backstack.push(NavTarget.ModeratorList)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun openEditRoomDetailsPermissions() {
|
|
||||||
backstack.push(NavTarget.ChangeRoomPermissions(ChangeRoomPermissionsSection.RoomDetails))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun openMessagesAndContentPermissions() {
|
|
||||||
backstack.push(NavTarget.ChangeRoomPermissions(ChangeRoomPermissionsSection.MessagesAndContent))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun openModerationPermissions() {
|
|
||||||
backstack.push(NavTarget.ChangeRoomPermissions(ChangeRoomPermissionsSection.MembershipModeration))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
createNode<RolesAndPermissionsNode>(
|
|
||||||
buildContext = buildContext,
|
|
||||||
plugins = listOf(callback),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is NavTarget.AdminList -> {
|
|
||||||
changeRoomMemberRolesEntryPoint.createNode(
|
|
||||||
parentNode = this,
|
|
||||||
buildContext = buildContext,
|
|
||||||
room = joinedRoom,
|
|
||||||
listType = ChangeRoomMemberRolesListType.Admins,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is NavTarget.ModeratorList -> {
|
|
||||||
changeRoomMemberRolesEntryPoint.createNode(
|
|
||||||
parentNode = this,
|
|
||||||
buildContext = buildContext,
|
|
||||||
room = joinedRoom,
|
|
||||||
listType = ChangeRoomMemberRolesListType.Moderators,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is NavTarget.ChangeRoomPermissions -> {
|
|
||||||
val inputs = ChangeRoomPermissionsNode.Inputs(navTarget.section)
|
|
||||||
createNode<ChangeRoomPermissionsNode>(
|
|
||||||
buildContext = buildContext,
|
|
||||||
plugins = listOf(inputs),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun View(modifier: Modifier) {
|
|
||||||
BackstackView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions
|
|
||||||
|
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
|
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
|
||||||
|
|
||||||
data class ChangeRoomPermissionsState(
|
|
||||||
val section: ChangeRoomPermissionsSection,
|
|
||||||
val currentPermissions: RoomPowerLevelsValues?,
|
|
||||||
val items: ImmutableList<RoomPermissionType>,
|
|
||||||
val hasChanges: Boolean,
|
|
||||||
val saveAction: AsyncAction<Unit>,
|
|
||||||
val confirmExitAction: AsyncAction<Unit>,
|
|
||||||
val eventSink: (ChangeRoomPermissionsEvent) -> Unit,
|
|
||||||
)
|
|
||||||
|
|
||||||
enum class RoomPermissionType {
|
|
||||||
BAN,
|
|
||||||
INVITE,
|
|
||||||
KICK,
|
|
||||||
SEND_EVENTS,
|
|
||||||
REDACT_EVENTS,
|
|
||||||
ROOM_NAME,
|
|
||||||
ROOM_AVATAR,
|
|
||||||
ROOM_TOPIC
|
|
||||||
}
|
|
||||||
|
|
@ -1,189 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions
|
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
|
||||||
import io.element.android.features.roomdetails.impl.R
|
|
||||||
import io.element.android.libraries.core.bool.orFalse
|
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
|
||||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.ListItemStyle
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.ListSectionHeader
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
|
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun ChangeRoomPermissionsView(
|
|
||||||
state: ChangeRoomPermissionsState,
|
|
||||||
onBackClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
BackHandler {
|
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.Exit)
|
|
||||||
}
|
|
||||||
Scaffold(
|
|
||||||
modifier = modifier,
|
|
||||||
topBar = {
|
|
||||||
val title = when (state.section) {
|
|
||||||
ChangeRoomPermissionsSection.RoomDetails -> stringResource(R.string.screen_room_change_permissions_room_details)
|
|
||||||
ChangeRoomPermissionsSection.MessagesAndContent -> stringResource(R.string.screen_room_change_permissions_messages_and_content)
|
|
||||||
ChangeRoomPermissionsSection.MembershipModeration -> stringResource(R.string.screen_room_change_permissions_member_moderation)
|
|
||||||
}
|
|
||||||
TopAppBar(
|
|
||||||
titleStr = title,
|
|
||||||
navigationIcon = {
|
|
||||||
BackButton(onClick = { state.eventSink(ChangeRoomPermissionsEvent.Exit) })
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
TextButton(
|
|
||||||
text = stringResource(CommonStrings.action_save),
|
|
||||||
onClick = { state.eventSink(ChangeRoomPermissionsEvent.Save) },
|
|
||||||
enabled = state.hasChanges,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) { padding ->
|
|
||||||
LazyColumn(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(padding)
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
for ((index, permissionItem) in state.items.withIndex()) {
|
|
||||||
item {
|
|
||||||
ListSectionHeader(titleForSection(item = permissionItem), hasDivider = index > 0)
|
|
||||||
SelectRoleItem(
|
|
||||||
permissionsItem = permissionItem,
|
|
||||||
role = RoomMember.Role.Admin,
|
|
||||||
currentPermissions = state.currentPermissions
|
|
||||||
) { item, role ->
|
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role))
|
|
||||||
}
|
|
||||||
SelectRoleItem(
|
|
||||||
permissionsItem = permissionItem,
|
|
||||||
role = RoomMember.Role.Moderator,
|
|
||||||
currentPermissions = state.currentPermissions
|
|
||||||
) { item, role ->
|
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role))
|
|
||||||
}
|
|
||||||
SelectRoleItem(
|
|
||||||
permissionsItem = permissionItem,
|
|
||||||
role = RoomMember.Role.User,
|
|
||||||
currentPermissions = state.currentPermissions
|
|
||||||
) { item, role ->
|
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncActionView(
|
|
||||||
async = state.saveAction,
|
|
||||||
onSuccess = { onBackClick() },
|
|
||||||
onErrorDismiss = { state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions) }
|
|
||||||
)
|
|
||||||
|
|
||||||
AsyncActionView(
|
|
||||||
async = state.confirmExitAction,
|
|
||||||
onSuccess = { onBackClick() },
|
|
||||||
confirmationDialog = {
|
|
||||||
ConfirmationDialog(
|
|
||||||
title = stringResource(R.string.screen_room_change_role_unsaved_changes_title),
|
|
||||||
content = stringResource(R.string.screen_room_change_role_unsaved_changes_description),
|
|
||||||
submitText = stringResource(CommonStrings.action_save),
|
|
||||||
cancelText = stringResource(CommonStrings.action_discard),
|
|
||||||
onSubmitClick = { state.eventSink(ChangeRoomPermissionsEvent.Save) },
|
|
||||||
onDismiss = { state.eventSink(ChangeRoomPermissionsEvent.Exit) }
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onErrorDismiss = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SelectRoleItem(
|
|
||||||
permissionsItem: RoomPermissionType,
|
|
||||||
role: RoomMember.Role,
|
|
||||||
currentPermissions: RoomPowerLevelsValues?,
|
|
||||||
onClick: (RoomPermissionType, RoomMember.Role) -> Unit
|
|
||||||
) {
|
|
||||||
val title = when (role) {
|
|
||||||
RoomMember.Role.Admin -> stringResource(R.string.screen_room_change_permissions_administrators)
|
|
||||||
RoomMember.Role.Moderator -> stringResource(R.string.screen_room_change_permissions_moderators)
|
|
||||||
RoomMember.Role.User -> stringResource(R.string.screen_room_change_permissions_everyone)
|
|
||||||
else -> error("Unsupported role selected: $role")
|
|
||||||
}
|
|
||||||
ListItem(
|
|
||||||
headlineContent = { Text(text = title) },
|
|
||||||
trailingContent = if (currentPermissions?.isSelected(permissionsItem, role).orFalse()) {
|
|
||||||
ListItemContent.Icon(IconSource.Vector(CompoundIcons.Check()))
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
},
|
|
||||||
style = ListItemStyle.Primary,
|
|
||||||
onClick = { onClick(permissionsItem, role) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun RoomPowerLevelsValues.isSelected(item: RoomPermissionType, role: RoomMember.Role): Boolean {
|
|
||||||
return when (item) {
|
|
||||||
RoomPermissionType.BAN -> RoomMember.Role.forPowerLevel(ban) == role
|
|
||||||
RoomPermissionType.INVITE -> RoomMember.Role.forPowerLevel(invite) == role
|
|
||||||
RoomPermissionType.KICK -> RoomMember.Role.forPowerLevel(kick) == role
|
|
||||||
RoomPermissionType.SEND_EVENTS -> RoomMember.Role.forPowerLevel(sendEvents) == role
|
|
||||||
RoomPermissionType.REDACT_EVENTS -> RoomMember.Role.forPowerLevel(redactEvents) == role
|
|
||||||
RoomPermissionType.ROOM_NAME -> RoomMember.Role.forPowerLevel(roomName) == role
|
|
||||||
RoomPermissionType.ROOM_AVATAR -> RoomMember.Role.forPowerLevel(roomAvatar) == role
|
|
||||||
RoomPermissionType.ROOM_TOPIC -> RoomMember.Role.forPowerLevel(roomTopic) == role
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun titleForSection(item: RoomPermissionType): String = when (item) {
|
|
||||||
RoomPermissionType.INVITE -> stringResource(R.string.screen_room_change_permissions_invite_people)
|
|
||||||
RoomPermissionType.KICK -> stringResource(R.string.screen_room_change_permissions_remove_people)
|
|
||||||
RoomPermissionType.BAN -> stringResource(R.string.screen_room_change_permissions_ban_people)
|
|
||||||
RoomPermissionType.SEND_EVENTS -> stringResource(R.string.screen_room_change_permissions_send_messages)
|
|
||||||
RoomPermissionType.REDACT_EVENTS -> stringResource(R.string.screen_room_change_permissions_delete_messages)
|
|
||||||
RoomPermissionType.ROOM_NAME -> stringResource(R.string.screen_room_change_permissions_room_name)
|
|
||||||
RoomPermissionType.ROOM_AVATAR -> stringResource(R.string.screen_room_change_permissions_room_avatar)
|
|
||||||
RoomPermissionType.ROOM_TOPIC -> stringResource(R.string.screen_room_change_permissions_room_topic)
|
|
||||||
}
|
|
||||||
|
|
||||||
@PreviewsDayNight
|
|
||||||
@Composable
|
|
||||||
internal fun ChangeRoomPermissionsViewPreview(@PreviewParameter(ChangeRoomPermissionsStateProvider::class) state: ChangeRoomPermissionsState) {
|
|
||||||
ElementPreview {
|
|
||||||
ChangeRoomPermissionsView(
|
|
||||||
state = state,
|
|
||||||
onBackClick = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -14,10 +14,10 @@
|
||||||
<string name="screen_room_change_permissions_messages_and_content">"Messages and content"</string>
|
<string name="screen_room_change_permissions_messages_and_content">"Messages and content"</string>
|
||||||
<string name="screen_room_change_permissions_moderators">"Admins and moderators"</string>
|
<string name="screen_room_change_permissions_moderators">"Admins and moderators"</string>
|
||||||
<string name="screen_room_change_permissions_remove_people">"Remove people and decline requests to join"</string>
|
<string name="screen_room_change_permissions_remove_people">"Remove people and decline requests to join"</string>
|
||||||
<string name="screen_room_change_permissions_room_avatar">"Change room avatar"</string>
|
<string name="screen_room_change_permissions_room_avatar">"Change avatar"</string>
|
||||||
<string name="screen_room_change_permissions_room_details">"Room details"</string>
|
<string name="screen_room_change_permissions_room_details">"Room details"</string>
|
||||||
<string name="screen_room_change_permissions_room_name">"Change room name"</string>
|
<string name="screen_room_change_permissions_room_name">"Change name"</string>
|
||||||
<string name="screen_room_change_permissions_room_topic">"Change room topic"</string>
|
<string name="screen_room_change_permissions_room_topic">"Change topic"</string>
|
||||||
<string name="screen_room_change_permissions_send_messages">"Send messages"</string>
|
<string name="screen_room_change_permissions_send_messages">"Send messages"</string>
|
||||||
<string name="screen_room_change_role_administrators_title">"Edit Admins"</string>
|
<string name="screen_room_change_role_administrators_title">"Edit Admins"</string>
|
||||||
<string name="screen_room_change_role_confirm_add_admin_description">"You will not be able to undo this action. You are promoting the user to have the same power level as you."</string>
|
<string name="screen_room_change_role_confirm_add_admin_description">"You will not be able to undo this action. You are promoting the user to have the same power level as you."</string>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import com.bumble.appyx.testing.junit4.util.MainDispatcherRule
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.call.test.FakeElementCallEntryPoint
|
import io.element.android.features.call.test.FakeElementCallEntryPoint
|
||||||
import io.element.android.features.changeroommemberroles.test.FakeChangeRoomMemberRolesEntryPoint
|
import io.element.android.features.changeroommemberroles.test.FakeChangeRoomMemberRolesEntryPoint
|
||||||
|
import io.element.android.features.changeroommemberroles.test.FakeRolesAndPermissionsEntryPoint
|
||||||
import io.element.android.features.knockrequests.test.FakeKnockRequestsListEntryPoint
|
import io.element.android.features.knockrequests.test.FakeKnockRequestsListEntryPoint
|
||||||
import io.element.android.features.messages.test.FakeMessagesEntryPoint
|
import io.element.android.features.messages.test.FakeMessagesEntryPoint
|
||||||
import io.element.android.features.poll.test.history.FakePollHistoryEntryPoint
|
import io.element.android.features.poll.test.history.FakePollHistoryEntryPoint
|
||||||
|
|
@ -58,6 +59,7 @@ class DefaultRoomDetailsEntryPointTest {
|
||||||
outgoingVerificationEntryPoint = FakeOutgoingVerificationEntryPoint(),
|
outgoingVerificationEntryPoint = FakeOutgoingVerificationEntryPoint(),
|
||||||
reportRoomEntryPoint = FakeReportRoomEntryPoint(),
|
reportRoomEntryPoint = FakeReportRoomEntryPoint(),
|
||||||
changeRoomMemberRolesEntryPoint = FakeChangeRoomMemberRolesEntryPoint(),
|
changeRoomMemberRolesEntryPoint = FakeChangeRoomMemberRolesEntryPoint(),
|
||||||
|
rolesAndPermissionsEntryPoint = FakeRolesAndPermissionsEntryPoint(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val callback = object : RoomDetailsEntryPoint.Callback {
|
val callback = object : RoomDetailsEntryPoint.Callback {
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ interface SpaceEntryPoint : FeatureEntryPoint {
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun navigateToRoom(roomId: RoomId, viaParameters: List<String>)
|
fun navigateToRoom(roomId: RoomId, viaParameters: List<String>)
|
||||||
fun navigateToRoomDetails()
|
|
||||||
fun navigateToRoomMemberList()
|
fun navigateToRoomMemberList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||||
|
import com.bumble.appyx.navmodel.backstack.operation.pop
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
|
|
@ -26,14 +27,15 @@ import io.element.android.features.space.api.SpaceEntryPoint
|
||||||
import io.element.android.features.space.impl.di.SpaceFlowGraph
|
import io.element.android.features.space.impl.di.SpaceFlowGraph
|
||||||
import io.element.android.features.space.impl.leave.LeaveSpaceNode
|
import io.element.android.features.space.impl.leave.LeaveSpaceNode
|
||||||
import io.element.android.features.space.impl.root.SpaceNode
|
import io.element.android.features.space.impl.root.SpaceNode
|
||||||
|
import io.element.android.features.space.impl.settings.SpaceSettingsNode
|
||||||
import io.element.android.libraries.architecture.BackstackView
|
import io.element.android.libraries.architecture.BackstackView
|
||||||
import io.element.android.libraries.architecture.BaseFlowNode
|
import io.element.android.libraries.architecture.BaseFlowNode
|
||||||
import io.element.android.libraries.architecture.callback
|
import io.element.android.libraries.architecture.callback
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
import io.element.android.libraries.architecture.inputs
|
|
||||||
import io.element.android.libraries.di.DependencyInjectionGraphOwner
|
import io.element.android.libraries.di.DependencyInjectionGraphOwner
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.api.spaces.SpaceService
|
import io.element.android.libraries.matrix.api.spaces.SpaceService
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
|
@ -42,6 +44,7 @@ import kotlinx.parcelize.Parcelize
|
||||||
class SpaceFlowNode(
|
class SpaceFlowNode(
|
||||||
@Assisted val buildContext: BuildContext,
|
@Assisted val buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
|
room: JoinedRoom,
|
||||||
spaceService: SpaceService,
|
spaceService: SpaceService,
|
||||||
graphFactory: SpaceFlowGraph.Factory,
|
graphFactory: SpaceFlowGraph.Factory,
|
||||||
) : BaseFlowNode<SpaceFlowNode.NavTarget>(
|
) : BaseFlowNode<SpaceFlowNode.NavTarget>(
|
||||||
|
|
@ -52,15 +55,17 @@ class SpaceFlowNode(
|
||||||
buildContext = buildContext,
|
buildContext = buildContext,
|
||||||
plugins = plugins,
|
plugins = plugins,
|
||||||
), DependencyInjectionGraphOwner {
|
), DependencyInjectionGraphOwner {
|
||||||
private val inputs: SpaceEntryPoint.Inputs = inputs()
|
|
||||||
private val callback: SpaceEntryPoint.Callback = callback()
|
private val callback: SpaceEntryPoint.Callback = callback()
|
||||||
private val spaceRoomList = spaceService.spaceRoomList(inputs.roomId)
|
private val spaceRoomList = spaceService.spaceRoomList(room.roomId)
|
||||||
override val graph = graphFactory.create(spaceRoomList)
|
override val graph = graphFactory.create(spaceRoomList)
|
||||||
|
|
||||||
sealed interface NavTarget : Parcelable {
|
sealed interface NavTarget : Parcelable {
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object Root : NavTarget
|
data object Root : NavTarget
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data object Settings : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object Leave : NavTarget
|
data object Leave : NavTarget
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +82,16 @@ class SpaceFlowNode(
|
||||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||||
return when (navTarget) {
|
return when (navTarget) {
|
||||||
NavTarget.Leave -> {
|
NavTarget.Leave -> {
|
||||||
createNode<LeaveSpaceNode>(buildContext, listOf(inputs))
|
val callback = object : LeaveSpaceNode.Callback {
|
||||||
|
override fun closeLeaveSpaceFlow() {
|
||||||
|
backstack.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun navigateToRolesAndPermissions() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createNode<LeaveSpaceNode>(buildContext, listOf(callback))
|
||||||
}
|
}
|
||||||
NavTarget.Root -> {
|
NavTarget.Root -> {
|
||||||
val callback = object : SpaceNode.Callback {
|
val callback = object : SpaceNode.Callback {
|
||||||
|
|
@ -85,8 +99,8 @@ class SpaceFlowNode(
|
||||||
callback.navigateToRoom(roomId, viaParameters)
|
callback.navigateToRoom(roomId, viaParameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun navigateToRoomDetails() {
|
override fun navigateToSpaceSettings() {
|
||||||
callback.navigateToRoomDetails()
|
backstack.push(NavTarget.Settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun navigateToRoomMemberList() {
|
override fun navigateToRoomMemberList() {
|
||||||
|
|
@ -97,7 +111,35 @@ class SpaceFlowNode(
|
||||||
backstack.push(NavTarget.Leave)
|
backstack.push(NavTarget.Leave)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
createNode<SpaceNode>(buildContext, listOf(inputs, callback))
|
createNode<SpaceNode>(buildContext, listOf(callback))
|
||||||
|
}
|
||||||
|
NavTarget.Settings -> {
|
||||||
|
val callback = object : SpaceSettingsNode.Callback {
|
||||||
|
override fun closeSettings() {
|
||||||
|
backstack.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun navigateToSpaceInfo() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun navigateToSpaceMembers() {
|
||||||
|
callback.navigateToRoomMemberList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun navigateToRolesAndPermissions() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun navigateToSecurityAndPrivacy() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun startLeaveSpaceFlow() {
|
||||||
|
backstack.push(NavTarget.Leave)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createNode<SpaceSettingsNode>(buildContext, listOf(callback))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,10 @@ import com.bumble.appyx.core.plugin.Plugin
|
||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import io.element.android.annotations.ContributesNode
|
import io.element.android.annotations.ContributesNode
|
||||||
import io.element.android.features.space.api.SpaceEntryPoint
|
|
||||||
import io.element.android.features.space.impl.di.SpaceFlowScope
|
import io.element.android.features.space.impl.di.SpaceFlowScope
|
||||||
import io.element.android.libraries.architecture.inputs
|
import io.element.android.libraries.architecture.callback
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
|
|
||||||
@ContributesNode(SpaceFlowScope::class)
|
@ContributesNode(SpaceFlowScope::class)
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
|
|
@ -27,12 +27,19 @@ class LeaveSpaceNode(
|
||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
matrixClient: MatrixClient,
|
matrixClient: MatrixClient,
|
||||||
|
room: JoinedRoom,
|
||||||
presenterFactory: LeaveSpacePresenter.Factory,
|
presenterFactory: LeaveSpacePresenter.Factory,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
private val inputs: SpaceEntryPoint.Inputs = inputs()
|
interface Callback : Plugin {
|
||||||
private val leaveSpaceHandle = matrixClient.spaceService.getLeaveSpaceHandle(inputs.roomId)
|
fun closeLeaveSpaceFlow()
|
||||||
|
fun navigateToRolesAndPermissions()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val leaveSpaceHandle = matrixClient.spaceService.getLeaveSpaceHandle(room.roomId)
|
||||||
private val presenter: LeaveSpacePresenter = presenterFactory.create(leaveSpaceHandle)
|
private val presenter: LeaveSpacePresenter = presenterFactory.create(leaveSpaceHandle)
|
||||||
|
|
||||||
|
private val callback: Callback = callback()
|
||||||
|
|
||||||
override fun onBuilt() {
|
override fun onBuilt() {
|
||||||
super.onBuilt()
|
super.onBuilt()
|
||||||
lifecycle.subscribe(
|
lifecycle.subscribe(
|
||||||
|
|
@ -47,7 +54,8 @@ class LeaveSpaceNode(
|
||||||
val state = presenter.present()
|
val state = presenter.present()
|
||||||
LeaveSpaceView(
|
LeaveSpaceView(
|
||||||
state = state,
|
state = state,
|
||||||
onCancel = ::navigateUp,
|
onCancel = callback::closeLeaveSpaceFlow,
|
||||||
|
onRolesAndPermissionsClick = callback::navigateToRolesAndPermissions,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
fun LeaveSpaceView(
|
fun LeaveSpaceView(
|
||||||
state: LeaveSpaceState,
|
state: LeaveSpaceState,
|
||||||
onCancel: () -> Unit,
|
onCancel: () -> Unit,
|
||||||
|
onRolesAndPermissionsClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
|
@ -130,6 +131,9 @@ fun LeaveSpaceView(
|
||||||
state.eventSink(LeaveSpaceEvents.LeaveSpace)
|
state.eventSink(LeaveSpaceEvents.LeaveSpace)
|
||||||
},
|
},
|
||||||
onCancel = onCancel,
|
onCancel = onCancel,
|
||||||
|
// TODO enable when navigation is ready
|
||||||
|
showRolesAndPermissionsButton = false, // state.isLastAdmin,
|
||||||
|
onRolesAndPermissionsClick = onRolesAndPermissionsClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -210,6 +214,8 @@ private fun LeaveSpaceButtons(
|
||||||
showLeaveButton: Boolean,
|
showLeaveButton: Boolean,
|
||||||
selectedRoomsCount: Int,
|
selectedRoomsCount: Int,
|
||||||
onLeaveSpace: () -> Unit,
|
onLeaveSpace: () -> Unit,
|
||||||
|
showRolesAndPermissionsButton: Boolean,
|
||||||
|
onRolesAndPermissionsClick: () -> Unit,
|
||||||
onCancel: () -> Unit,
|
onCancel: () -> Unit,
|
||||||
) {
|
) {
|
||||||
ButtonColumnMolecule(
|
ButtonColumnMolecule(
|
||||||
|
|
@ -229,8 +235,14 @@ private fun LeaveSpaceButtons(
|
||||||
destructive = true,
|
destructive = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// TODO For least admin space, add a button to open the settings.
|
if (showRolesAndPermissionsButton) {
|
||||||
// See https://www.figma.com/design/kcnHxunG1LDWXsJhaNuiHz/ER-145--Spaces-on-Element-X?node-id=4622-59600
|
Button(
|
||||||
|
text = stringResource(CommonStrings.action_go_to_roles_and_permissions),
|
||||||
|
onClick = onRolesAndPermissionsClick,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
leadingIcon = IconSource.Vector(CompoundIcons.Settings()),
|
||||||
|
)
|
||||||
|
}
|
||||||
TextButton(
|
TextButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = stringResource(CommonStrings.action_cancel),
|
text = stringResource(CommonStrings.action_cancel),
|
||||||
|
|
@ -345,5 +357,6 @@ internal fun LeaveSpaceViewPreview(
|
||||||
LeaveSpaceView(
|
LeaveSpaceView(
|
||||||
state = state,
|
state = state,
|
||||||
onCancel = {},
|
onCancel = {},
|
||||||
|
onRolesAndPermissionsClick = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue