From 0ab2ad1b8b97e7e332fff9b55736f5abd14ebd0d Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 25 Nov 2024 16:33:34 +0100 Subject: [PATCH 01/13] room details : display the item to show request to join list --- .../roomdetails/impl/RoomDetailsFlowNode.kt | 4 ++++ .../roomdetails/impl/RoomDetailsNode.kt | 8 ++++++- .../roomdetails/impl/RoomDetailsPresenter.kt | 18 ++++++++++++++- .../roomdetails/impl/RoomDetailsState.kt | 2 ++ .../impl/RoomDetailsStateProvider.kt | 4 ++++ .../roomdetails/impl/RoomDetailsView.kt | 22 +++++++++++++++++++ .../room/powerlevels/MatrixRoomPowerLevels.kt | 7 ++++++ .../matrix/ui/room/MatrixRoomState.kt | 8 +++++++ 8 files changed, 71 insertions(+), 2 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index 4030e30272..4d4b644bc1 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -141,6 +141,10 @@ class RoomDetailsFlowNode @AssistedInject constructor( backstack.push(NavTarget.PinnedMessagesList) } + override fun openKnockRequestsList() { + // TODO open the knock requests list screen + } + override fun onJoinCall() { val inputs = CallType.RoomCall( sessionId = room.sessionId, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt index 19c0b4ffe4..e9ad095c76 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt @@ -47,6 +47,7 @@ class RoomDetailsNode @AssistedInject constructor( fun openPollHistory() fun openAdminSettings() fun openPinnedMessagesList() + fun openKnockRequestsList() fun onJoinCall() } @@ -111,6 +112,10 @@ class RoomDetailsNode @AssistedInject constructor( callbacks.forEach { it.openPinnedMessagesList() } } + private fun openKnockRequestsLists() { + callbacks.forEach { it.openKnockRequestsList() } + } + @Composable override fun View(modifier: Modifier) { val context = LocalContext.current @@ -140,7 +145,8 @@ class RoomDetailsNode @AssistedInject constructor( openPollHistory = ::openPollHistory, openAdminSettings = this::openAdminSettings, onJoinCallClick = ::onJoinCall, - onPinnedMessagesClick = ::openPinnedMessages + onPinnedMessagesClick = ::openPinnedMessages, + onKnockRequestsClick = ::openKnockRequestsLists ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 586790b618..dc6568a60f 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -38,6 +38,7 @@ import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.powerlevels.canInvite import io.element.android.libraries.matrix.api.room.powerlevels.canSendState import io.element.android.libraries.matrix.api.room.roomNotificationSettings +import io.element.android.libraries.matrix.ui.room.canHandleKnockRequestsAsState import io.element.android.libraries.matrix.ui.room.getCurrentRoomMember import io.element.android.libraries.matrix.ui.room.getDirectRoomMember import io.element.android.libraries.matrix.ui.room.isOwnUserAdmin @@ -69,7 +70,7 @@ class RoomDetailsPresenter @Inject constructor( val canShowNotificationSettings = remember { mutableStateOf(false) } val roomInfo by room.roomInfoFlow.collectAsState(initial = null) val isUserAdmin = room.isOwnUserAdmin() - + val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val roomAvatar by remember { derivedStateOf { roomInfo?.avatarUrl ?: room.avatarUrl } } val roomName by remember { derivedStateOf { (roomInfo?.name ?: room.displayName).trim() } } @@ -90,6 +91,7 @@ class RoomDetailsPresenter @Inject constructor( val membersState by room.membersStateFlow.collectAsState() val canInvite by getCanInvite(membersState) + val canEditName by getCanSendState(membersState, StateEventType.ROOM_NAME) val canEditAvatar by getCanSendState(membersState, StateEventType.ROOM_AVATAR) val canEditTopic by getCanSendState(membersState, StateEventType.ROOM_TOPIC) @@ -99,6 +101,8 @@ class RoomDetailsPresenter @Inject constructor( val roomType by getRoomType(dmMember, currentMember) val roomCallState = roomCallStatePresenter.present() + val canHandleKnockRequests by room.canHandleKnockRequestsAsState(syncUpdateFlow.value) + val topicState = remember(canEditTopic, roomTopic, roomType) { val topic = roomTopic @@ -109,6 +113,12 @@ class RoomDetailsPresenter @Inject constructor( } } + val isKnockRequestsEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock).collectAsState(false) + val knockRequestsCount by remember { mutableStateOf(null) } + val canShowKnockRequests by remember { + derivedStateOf { isKnockRequestsEnabled && canHandleKnockRequests } + } + val roomNotificationSettingsState by room.roomNotificationSettingsStateFlow.collectAsState() fun handleEvents(event: RoomDetailsEvent) { @@ -153,10 +163,16 @@ class RoomDetailsPresenter @Inject constructor( heroes = roomInfo?.heroes.orEmpty().toPersistentList(), canShowPinnedMessages = canShowPinnedMessages, pinnedMessagesCount = pinnedMessagesCount, + canShowKnockRequests = canShowKnockRequests, + knockRequestsCount = knockRequestsCount, eventSink = ::handleEvents, ) } + private fun getCanBan(membersState: MatrixRoomMembersState): Any { + TODO("Not yet implemented") + } + @Composable private fun roomMemberDetailsPresenter(dmMemberState: RoomMember?) = remember(dmMemberState) { dmMemberState?.let { roomMember -> diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt index d43b0a813a..7f15c846f9 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt @@ -41,6 +41,8 @@ data class RoomDetailsState( val heroes: ImmutableList, val canShowPinnedMessages: Boolean, val pinnedMessagesCount: Int?, + val canShowKnockRequests: Boolean, + val knockRequestsCount: Int?, val eventSink: (RoomDetailsEvent) -> Unit ) { val roomBadges = buildList { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt index 49b9f73cb5..dcf5bc3054 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt @@ -102,6 +102,8 @@ fun aRoomDetailsState( heroes: List = emptyList(), canShowPinnedMessages: Boolean = true, pinnedMessagesCount: Int? = null, + canShowKnockRequests: Boolean = false, + knockRequestsCount: Int? = null, eventSink: (RoomDetailsEvent) -> Unit = {}, ) = RoomDetailsState( roomId = roomId, @@ -125,6 +127,8 @@ fun aRoomDetailsState( heroes = heroes.toPersistentList(), canShowPinnedMessages = canShowPinnedMessages, pinnedMessagesCount = pinnedMessagesCount, + canShowKnockRequests = canShowKnockRequests, + knockRequestsCount = knockRequestsCount, eventSink = eventSink ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index 7e89c3ef07..80f7beda56 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -104,6 +104,7 @@ fun RoomDetailsView( openAdminSettings: () -> Unit, onJoinCallClick: () -> Unit, onPinnedMessagesClick: () -> Unit, + onKnockRequestsClick: () -> Unit, modifier: Modifier = Modifier, ) { Scaffold( @@ -206,6 +207,12 @@ fun RoomDetailsView( memberCount = state.memberCount, openRoomMemberList = openRoomMemberList, ) + if (state.canShowKnockRequests) { + KnockRequestsItem( + knockRequestsCount = state.knockRequestsCount, + onKnockRequestsClick = onKnockRequestsClick + ) + } } } @@ -231,6 +238,20 @@ fun RoomDetailsView( } } +@Composable +private fun KnockRequestsItem(knockRequestsCount: Int?, onKnockRequestsClick: () -> Unit) { + ListItem( + headlineContent = { Text(stringResource(CommonStrings.screen_room_details_requests_to_join_title)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Notifications())), + trailingContent = if (knockRequestsCount == null || knockRequestsCount == 0) { + null + } else { + ListItemContent.Text(knockRequestsCount.toString()) + }, + onClick = onKnockRequestsClick, + ) +} + @OptIn(ExperimentalMaterial3Api::class) @Composable private fun RoomDetailsTopBar( @@ -613,5 +634,6 @@ private fun ContentToPreview(state: RoomDetailsState) { openAdminSettings = {}, onJoinCallClick = {}, onPinnedMessagesClick = {}, + onKnockRequestsClick = {}, ) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt index 682596a59d..ba81a5780c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt @@ -57,6 +57,13 @@ suspend fun MatrixRoom.canRedactOwn(): Result = canUserRedactOwn(sessio */ suspend fun MatrixRoom.canRedactOther(): Result = canUserRedactOther(sessionId) +/** + * Shortcut for checking if current user can handle knock requests. + */ +suspend fun MatrixRoom.canHandleKnockRequests(): Result = runCatching { + canInvite().getOrThrow() || canBan().getOrThrow() || canKick().getOrThrow() +} + /** * Shortcut for calling [MatrixRoom.canUserPinUnpin] with our own user. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt index 81ae3e6b89..59bd1fa773 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt @@ -17,6 +17,7 @@ import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.powerlevels.canBan +import io.element.android.libraries.matrix.api.room.powerlevels.canHandleKnockRequests import io.element.android.libraries.matrix.api.room.powerlevels.canInvite import io.element.android.libraries.matrix.api.room.powerlevels.canKick import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther @@ -86,6 +87,13 @@ fun MatrixRoom.canBanAsState(updateKey: Long): State { } } +@Composable +fun MatrixRoom.canHandleKnockRequestsAsState(updateKey: Long): State { + return produceState(initialValue = false, key1 = updateKey) { + value = canHandleKnockRequests().getOrElse { false } + } +} + @Composable fun MatrixRoom.userPowerLevelAsState(updateKey: Long): State { return produceState(initialValue = 0, key1 = updateKey) { From 795937b74c3a0d3d7bb084b049223c984c6a54f4 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Nov 2024 12:25:23 +0100 Subject: [PATCH 02/13] knock requests : create feature module --- features/knockrequests/api/build.gradle.kts | 22 ++++++++++ .../api/list/KnockRequestsListEntryPoint.kt | 12 ++++++ features/knockrequests/impl/build.gradle.kts | 35 +++++++++++++++ .../DefaultKnockRequestsListEntryPoint.kt | 23 ++++++++++ .../impl/list/KnockRequestsListEvents.kt | 12 ++++++ .../impl/list/KnockRequestsListNode.kt | 36 ++++++++++++++++ .../impl/list/KnockRequestsListPresenter.kt | 29 +++++++++++++ .../impl/list/KnockRequestsListState.kt | 14 ++++++ .../list/KnockRequestsListStateProvider.kt | 22 ++++++++++ .../impl/list/KnockRequestsListView.kt | 43 +++++++++++++++++++ features/roomdetails/impl/build.gradle.kts | 1 + .../roomdetails/impl/RoomDetailsFlowNode.kt | 10 ++++- 12 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 features/knockrequests/api/build.gradle.kts create mode 100644 features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/list/KnockRequestsListEntryPoint.kt create mode 100644 features/knockrequests/impl/build.gradle.kts create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/DefaultKnockRequestsListEntryPoint.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt diff --git a/features/knockrequests/api/build.gradle.kts b/features/knockrequests/api/build.gradle.kts new file mode 100644 index 0000000000..e3d563f2e0 --- /dev/null +++ b/features/knockrequests/api/build.gradle.kts @@ -0,0 +1,22 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +import extension.setupAnvil + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.knockrequests.api" +} + +dependencies { + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) +} + diff --git a/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/list/KnockRequestsListEntryPoint.kt b/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/list/KnockRequestsListEntryPoint.kt new file mode 100644 index 0000000000..0215b5cde9 --- /dev/null +++ b/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/list/KnockRequestsListEntryPoint.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.api.list + +import io.element.android.libraries.architecture.SimpleFeatureEntryPoint + +interface KnockRequestsListEntryPoint : SimpleFeatureEntryPoint diff --git a/features/knockrequests/impl/build.gradle.kts b/features/knockrequests/impl/build.gradle.kts new file mode 100644 index 0000000000..35d18b0c3a --- /dev/null +++ b/features/knockrequests/impl/build.gradle.kts @@ -0,0 +1,35 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +import extension.setupAnvil + +plugins { + id("io.element.android-compose-library") + id("kotlin-parcelize") +} + +android { + namespace = "io.element.android.features.knockrequests.impl" +} + +setupAnvil() + +dependencies { + api(projects.features.knockrequests.api) + implementation(projects.libraries.core) + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.matrixui) + implementation(projects.libraries.designsystem) + + testImplementation(libs.test.junit) + testImplementation(libs.coroutines.test) + testImplementation(libs.molecule.runtime) + testImplementation(libs.test.truth) + testImplementation(libs.test.turbine) + testImplementation(projects.libraries.matrix.test) +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/DefaultKnockRequestsListEntryPoint.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/DefaultKnockRequestsListEntryPoint.kt new file mode 100644 index 0000000000..c685f1cf37 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/DefaultKnockRequestsListEntryPoint.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.list + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.AppScope +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultKnockRequestsListEntryPoint @Inject constructor() : KnockRequestsListEntryPoint { + override fun createNode(parentNode: Node, buildContext: BuildContext): Node { + return parentNode.createNode(buildContext) + } +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt new file mode 100644 index 0000000000..f7f1755f32 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.list + +sealed interface KnockRequestsListEvents { + data object AcceptAll : KnockRequestsListEvents +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt new file mode 100644 index 0000000000..953b5991db --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.list + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.RoomScope + +@ContributesNode(RoomScope::class) +class KnockRequestsListNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: KnockRequestsListPresenter, +) : Node(buildContext, plugins = plugins) { + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + KnockRequestsListView( + state = state, + modifier = modifier + ) + } +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt new file mode 100644 index 0000000000..b290a5b99f --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.list + +import androidx.compose.runtime.Composable +import io.element.android.libraries.architecture.Presenter +import javax.inject.Inject + +class KnockRequestsListPresenter @Inject constructor() : Presenter { + + @Composable + override fun present(): KnockRequestsListState { + + fun handleEvents(event: KnockRequestsListEvents) { + when (event) { + KnockRequestsListEvents.AcceptAll -> Unit + } + } + + return KnockRequestsListState( + eventSink = ::handleEvents + ) + } +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt new file mode 100644 index 0000000000..8433c29a5a --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.list + +// TODO add your ui models. Remove the eventSink if you don't have events. +// Do not use default value, so no member get forgotten in the presenters. +data class KnockRequestsListState( + val eventSink: (KnockRequestsListEvents) -> Unit +) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt new file mode 100644 index 0000000000..4301ccdc32 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.list + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +open class KnockRequestsListStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aKnockRequestsListState(), + // Add other states here + ) +} + +fun aKnockRequestsListState() = KnockRequestsListState( + eventSink = {} +) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt new file mode 100644 index 0000000000..790a970883 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.list + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Text + +@Composable +fun KnockRequestsListView( + state: KnockRequestsListState, + modifier: Modifier = Modifier, +) { + Box(modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text( + "KnockRequestsList feature view", + color = MaterialTheme.colorScheme.primary, + ) + } +} + +@PreviewsDayNight +@Composable +internal fun KnockRequestsListViewPreview( + @PreviewParameter(KnockRequestsListStateProvider::class) state: KnockRequestsListState +) = ElementPreview { + KnockRequestsListView( + state = state, + ) +} diff --git a/features/roomdetails/impl/build.gradle.kts b/features/roomdetails/impl/build.gradle.kts index 231161e583..bfb2cfde7a 100644 --- a/features/roomdetails/impl/build.gradle.kts +++ b/features/roomdetails/impl/build.gradle.kts @@ -50,6 +50,7 @@ dependencies { implementation(projects.features.poll.api) implementation(projects.features.messages.api) implementation(projects.features.roomcall.api) + implementation(projects.features.knockrequests.api) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index 4d4b644bc1..5aab0be9f8 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -22,6 +22,7 @@ import im.vector.app.features.analytics.plan.Interaction import io.element.android.anvilannotations.ContributesNode import io.element.android.features.call.api.CallType import io.element.android.features.call.api.ElementCallEntryPoint +import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.poll.api.history.PollHistoryEntryPoint import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint @@ -59,6 +60,7 @@ class RoomDetailsFlowNode @AssistedInject constructor( private val room: MatrixRoom, private val analyticsService: AnalyticsService, private val messagesEntryPoint: MessagesEntryPoint, + private val knockRequestsListEntryPoint: KnockRequestsListEntryPoint, ) : BaseFlowNode( backstack = BackStack( initialElement = plugins.filterIsInstance().first().initialElement.toNavTarget(), @@ -103,6 +105,9 @@ class RoomDetailsFlowNode @AssistedInject constructor( @Parcelize data object PinnedMessagesList : NavTarget + + @Parcelize + data object KnockRequestsList : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { @@ -142,7 +147,7 @@ class RoomDetailsFlowNode @AssistedInject constructor( } override fun openKnockRequestsList() { - // TODO open the knock requests list screen + backstack.push(NavTarget.KnockRequestsList) } override fun onJoinCall() { @@ -253,6 +258,9 @@ class RoomDetailsFlowNode @AssistedInject constructor( .callback(callback) .build() } + NavTarget.KnockRequestsList -> { + knockRequestsListEntryPoint.createNode(this, buildContext) + } } } From bc09872baeb0cdbdedf3ddd617259697bb193d33 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Nov 2024 21:12:11 +0100 Subject: [PATCH 03/13] knock requests : start knock requests list view --- features/knockrequests/impl/build.gradle.kts | 1 + .../knockrequests/impl/KnockRequest.kt | 34 +++ .../impl/list/KnockRequestsListEvents.kt | 5 + .../impl/list/KnockRequestsListNode.kt | 1 + .../impl/list/KnockRequestsListPresenter.kt | 14 + .../impl/list/KnockRequestsListState.kt | 18 +- .../list/KnockRequestsListStateProvider.kt | 66 ++++- .../impl/list/KnockRequestsListView.kt | 260 +++++++++++++++++- .../components/avatar/AvatarSize.kt | 2 + 9 files changed, 388 insertions(+), 13 deletions(-) create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt diff --git a/features/knockrequests/impl/build.gradle.kts b/features/knockrequests/impl/build.gradle.kts index 35d18b0c3a..83f7132320 100644 --- a/features/knockrequests/impl/build.gradle.kts +++ b/features/knockrequests/impl/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrixui) + implementation(projects.libraries.uiStrings) implementation(projects.libraries.designsystem) testImplementation(libs.test.junit) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt new file mode 100644 index 0000000000..3a293dc565 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl + +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.user.MatrixUser + +data class KnockRequest( + val userId: UserId, + val displayName: String?, + val avatarUrl: String?, + val reason: String?, +) + +fun KnockRequest.getAvatarData(size: AvatarSize) = AvatarData( + id = userId.value, + name = displayName, + url = avatarUrl, + size = size, +) + +fun KnockRequest.getBestName(): String { + return displayName?.takeIf { it.isNotEmpty() } ?: userId.value +} + + + diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt index f7f1755f32..d84e5209b9 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt @@ -7,6 +7,11 @@ package io.element.android.features.knockrequests.impl.list +import io.element.android.features.knockrequests.impl.KnockRequest + sealed interface KnockRequestsListEvents { + data class Accept(val knockRequest: KnockRequest) : KnockRequestsListEvents + data class Decline(val knockRequest: KnockRequest) : KnockRequestsListEvents data object AcceptAll : KnockRequestsListEvents + data object DismissCurrentAction : KnockRequestsListEvents } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt index 953b5991db..0310f67d2e 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt @@ -30,6 +30,7 @@ class KnockRequestsListNode @AssistedInject constructor( val state = presenter.present() KnockRequestsListView( state = state, + onBackClick = ::navigateUp, modifier = modifier ) } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt index b290a5b99f..e7168e967b 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt @@ -8,21 +8,35 @@ package io.element.android.features.knockrequests.impl.list import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter +import kotlinx.collections.immutable.persistentListOf import javax.inject.Inject class KnockRequestsListPresenter @Inject constructor() : Presenter { @Composable override fun present(): KnockRequestsListState { + val actions = remember { + mutableStateOf(KnockRequestsCurrentAction.None) + } fun handleEvents(event: KnockRequestsListEvents) { when (event) { KnockRequestsListEvents.AcceptAll -> Unit + is KnockRequestsListEvents.Accept -> Unit + is KnockRequestsListEvents.Decline -> Unit + KnockRequestsListEvents.DismissCurrentAction -> { + actions.value = KnockRequestsCurrentAction.None + } } } return KnockRequestsListState( + knockRequests = AsyncData.Success(persistentListOf()), + currentAction = actions.value, eventSink = ::handleEvents ) } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt index 8433c29a5a..0aa96b4f28 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt @@ -7,8 +7,20 @@ package io.element.android.features.knockrequests.impl.list -// TODO add your ui models. Remove the eventSink if you don't have events. -// Do not use default value, so no member get forgotten in the presenters. +import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData +import kotlinx.collections.immutable.ImmutableList + data class KnockRequestsListState( - val eventSink: (KnockRequestsListEvents) -> Unit + val knockRequests: AsyncData>, + val currentAction: KnockRequestsCurrentAction, + val eventSink: (KnockRequestsListEvents) -> Unit, ) + +sealed interface KnockRequestsCurrentAction { + data object None : KnockRequestsCurrentAction + data class Accept(val async: AsyncAction) : KnockRequestsCurrentAction + data class Decline(val async: AsyncAction) : KnockRequestsCurrentAction + data class AcceptAll(val async: AsyncAction) : KnockRequestsCurrentAction +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt index 4301ccdc32..761c850180 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt @@ -8,15 +8,73 @@ package io.element.android.features.knockrequests.impl.list import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.core.UserId +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf open class KnockRequestsListStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aKnockRequestsListState(), - // Add other states here + aKnockRequestsListState( + knockRequests = AsyncData.Loading(), + ), + aKnockRequestsListState( + knockRequests = AsyncData.Success( + persistentListOf() + ), + ), + aKnockRequestsListState( + knockRequests = AsyncData.Success( + persistentListOf( + aKnockRequest() + ) + ), + ), + aKnockRequestsListState( + knockRequests = AsyncData.Success( + persistentListOf( + aKnockRequest(), + aKnockRequest( + userId = UserId("@user:example.com"), + displayName = null, + avatarUrl = null, + reason = null, + ) + ) + ), + ), + aKnockRequestsListState( + knockRequests = AsyncData.Success( + persistentListOf( + aKnockRequest() + ) + ), + actions = KnockRequestsCurrentAction.AcceptAll(AsyncAction.Loading), + ), ) } -fun aKnockRequestsListState() = KnockRequestsListState( - eventSink = {} +fun aKnockRequest( + userId: UserId = UserId("@jacob_ross:example.com"), + displayName: String? = "Jacob Ross", + avatarUrl: String? = null, + reason: String? = "Hi, I would like to get access to this room please.", +) = KnockRequest( + userId = userId, + displayName = displayName, + avatarUrl = avatarUrl, + reason = reason, +) + +fun aKnockRequestsListState( + knockRequests: AsyncData> = AsyncData.Success(persistentListOf()), + actions: KnockRequestsCurrentAction = KnockRequestsCurrentAction.None, + eventSink: (KnockRequestsListEvents) -> Unit = {}, +) = KnockRequestsListState( + knockRequests = knockRequests, + currentAction = actions, + eventSink = eventSink, ) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index 790a970883..b8ab403c1f 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -7,31 +7,278 @@ package io.element.android.features.knockrequests.impl.list +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.features.knockrequests.impl.getAvatarData +import io.element.android.features.knockrequests.impl.getBestName +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.components.async.AsyncActionView +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.aliasScreenTitle +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.ButtonSize +import io.element.android.libraries.designsystem.theme.components.HorizontalDivider +import io.element.android.libraries.designsystem.theme.components.OutlinedButton +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.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList @Composable fun KnockRequestsListView( state: KnockRequestsListState, + onBackClick: () -> Unit, modifier: Modifier = Modifier, ) { - Box(modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Text( - "KnockRequestsList feature view", - color = MaterialTheme.colorScheme.primary, + Scaffold( + modifier = modifier, + topBar = { + KnockRequestsListTopBar(onBackClick = onBackClick) + }, + content = { padding -> + KnockRequestsListContent( + state = state, + modifier = Modifier + .padding(padding) + .consumeWindowInsets(padding), + ) + } + ) +} + +@Composable +private fun KnockRequestsListContent(state: KnockRequestsListState, modifier: Modifier) { + + fun onAcceptClick(knockRequest: KnockRequest) { + state.eventSink(KnockRequestsListEvents.Accept(knockRequest)) + } + + fun onDeclineClick(knockRequest: KnockRequest) { + state.eventSink(KnockRequestsListEvents.Decline(knockRequest)) + } + + Box(modifier, contentAlignment = Alignment.Center) { + when (state.knockRequests) { + is AsyncData.Success -> { + val knockRequests = state.knockRequests.data + if (knockRequests.isEmpty()) { + KnockRequestsListEmpty() + } else { + KnockRequestsList( + knockRequests = knockRequests, + onAcceptClick = ::onAcceptClick, + onDeclineClick = ::onDeclineClick, + ) + } + } + else -> Unit + } + KnockRequestsActionsView( + actions = state.currentAction, + onDismiss = { + state.eventSink(KnockRequestsListEvents.AcceptAll) + }, + modifier = Modifier.align(Alignment.BottomCenter), ) } } +@Composable +private fun KnockRequestsActionsView( + actions: KnockRequestsCurrentAction, + onDismiss: () -> Unit, + modifier: Modifier = Modifier, +) { + Box(modifier) { + when (actions) { + is KnockRequestsCurrentAction.AcceptAll -> { + AsyncActionView( + async = actions.async, + onSuccess = {}, + onErrorDismiss = onDismiss, + ) + } + is KnockRequestsCurrentAction.Accept -> { + AsyncActionView( + async = actions.async, + onSuccess = {}, + onErrorDismiss = onDismiss, + ) + } + is KnockRequestsCurrentAction.Decline -> { + AsyncActionView( + async = actions.async, + onSuccess = {}, + onErrorDismiss = onDismiss, + ) + } + KnockRequestsCurrentAction.None -> Unit + } + } +} + +@Composable +private fun KnockRequestsList( + knockRequests: ImmutableList, + onAcceptClick: (KnockRequest) -> Unit, + onDeclineClick: (KnockRequest) -> Unit, + modifier: Modifier = Modifier, +) { + LazyColumn(modifier = modifier) { + itemsIndexed(knockRequests) { index, knockRequest -> + KnockRequestItem( + knockRequest = knockRequest, + onAcceptClick = onAcceptClick, + onDeclineClick = onDeclineClick, + ) + if (index != knockRequests.size - 1) { + HorizontalDivider() + } + } + } +} + +@Composable +private fun KnockRequestItem( + knockRequest: KnockRequest, + onAcceptClick: (KnockRequest) -> Unit, + onDeclineClick: (KnockRequest) -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 12.dp) + ) { + Avatar(knockRequest.getAvatarData(AvatarSize.KnockRequestItem)) + Spacer(modifier = Modifier.width(16.dp)) + Column(modifier = Modifier) { + // Name + Text( + modifier = Modifier.clipToBounds(), + text = knockRequest.getBestName(), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.primary, + style = ElementTheme.typography.fontBodyLgMedium, + ) + // UserId + if (!knockRequest.displayName.isNullOrEmpty()) { + Text( + text = knockRequest.userId.value, + color = MaterialTheme.colorScheme.secondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = ElementTheme.typography.fontBodyMdRegular, + ) + } + // Reason + if (!knockRequest.reason.isNullOrBlank()) { + Spacer(modifier = Modifier.height(12.dp)) + Text( + text = knockRequest.reason, + style = ElementTheme.typography.fontBodyMdRegular, + ) + } + Spacer(modifier = Modifier.height(12.dp)) + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) { + OutlinedButton( + text = stringResource(CommonStrings.action_decline), + onClick = { + onDeclineClick(knockRequest) + }, + size = ButtonSize.MediumLowPadding, + modifier = Modifier.weight(1f), + ) + Button( + text = stringResource(CommonStrings.action_accept), + onClick = { + onAcceptClick(knockRequest) + }, + size = ButtonSize.MediumLowPadding, + modifier = Modifier.weight(1f), + ) + } + + Spacer(modifier = Modifier.height(12.dp)) + TextButton( + text = stringResource(CommonStrings.screen_knock_requests_list_decline_and_ban_action_title), + onClick = { + onAcceptClick(knockRequest) + }, + destructive = true, + size = ButtonSize.Small, + modifier = Modifier.fillMaxWidth(), + ) + + } + } +} + +@Composable +private fun KnockRequestsListEmpty( + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier.padding( + horizontal = 32.dp, + vertical = 48.dp, + ), + contentAlignment = Alignment.Center, + ) { + IconTitleSubtitleMolecule( + title = "No pending request to join", + subTitle = "When somebody will ask to join the room, you’ll be able to see their request here.", + iconStyle = BigIcon.Style.Default(CompoundIcons.Pin()), + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun KnockRequestsListTopBar(onBackClick: () -> Unit) { + TopAppBar( + title = { + Text( + text = "Requests to join", + style = ElementTheme.typography.aliasScreenTitle, + ) + }, + navigationIcon = { BackButton(onClick = onBackClick) }, + ) +} + @PreviewsDayNight @Composable internal fun KnockRequestsListViewPreview( @@ -39,5 +286,6 @@ internal fun KnockRequestsListViewPreview( ) = ElementPreview { KnockRequestsListView( state = state, + onBackClick = {}, ) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt index 49a3e93e87..c5bd468abe 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt @@ -54,4 +54,6 @@ enum class AvatarSize(val dp: Dp) { EditProfileDetails(96.dp), Suggestion(32.dp), + + KnockRequestItem(52.dp), } From 1978c9dbdbe2238cb35046c78df262a47427a0ac Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Nov 2024 21:31:23 +0100 Subject: [PATCH 04/13] knock requests : accept all ui --- .../impl/list/KnockRequestsListState.kt | 4 +- .../impl/list/KnockRequestsListView.kt | 39 ++++++++++++++++--- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt index 0aa96b4f28..072ff5bdee 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt @@ -16,7 +16,9 @@ data class KnockRequestsListState( val knockRequests: AsyncData>, val currentAction: KnockRequestsCurrentAction, val eventSink: (KnockRequestsListEvents) -> Unit, -) +) { + val canAcceptAll = knockRequests is AsyncData.Success && knockRequests.data.size > 1 +} sealed interface KnockRequestsCurrentAction { data object None : KnockRequestsCurrentAction diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index b8ab403c1f..416a00194c 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -7,12 +7,14 @@ package io.element.android.features.knockrequests.impl.list +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -25,6 +27,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter @@ -88,12 +92,12 @@ private fun KnockRequestsListContent(state: KnockRequestsListState, modifier: Mo state.eventSink(KnockRequestsListEvents.Decline(knockRequest)) } - Box(modifier, contentAlignment = Alignment.Center) { + Box(modifier.fillMaxSize()) { when (state.knockRequests) { is AsyncData.Success -> { val knockRequests = state.knockRequests.data if (knockRequests.isEmpty()) { - KnockRequestsListEmpty() + KnockRequestsEmptyList() } else { KnockRequestsList( knockRequests = knockRequests, @@ -107,10 +111,17 @@ private fun KnockRequestsListContent(state: KnockRequestsListState, modifier: Mo KnockRequestsActionsView( actions = state.currentAction, onDismiss = { - state.eventSink(KnockRequestsListEvents.AcceptAll) + state.eventSink(KnockRequestsListEvents.DismissCurrentAction) }, - modifier = Modifier.align(Alignment.BottomCenter), ) + if (state.canAcceptAll) { + KnockRequestsAcceptAll( + onClick = { + state.eventSink(KnockRequestsListEvents.AcceptAll) + }, + modifier = Modifier.align(Alignment.BottomCenter), + ) + } } } @@ -247,7 +258,25 @@ private fun KnockRequestItem( } @Composable -private fun KnockRequestsListEmpty( +private fun KnockRequestsAcceptAll(onClick: () -> Unit, modifier: Modifier) { + Box( + modifier = modifier + .shadow(elevation = 24.dp, spotColor = Color.Transparent) + .background(color = ElementTheme.colors.bgCanvasDefault) + .padding(vertical = 12.dp, horizontal = 16.dp) + ) + { + OutlinedButton( + text = ("Accept all"), + onClick = onClick, + size = ButtonSize.Medium, + modifier = Modifier.fillMaxWidth(), + ) + } +} + +@Composable +private fun KnockRequestsEmptyList( modifier: Modifier = Modifier, ) { Box( From 20ed6bb0d2141200a2562fda1a142c3fffa2e7df Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 27 Nov 2024 11:40:47 +0100 Subject: [PATCH 05/13] knock requests : add permissions check --- .../impl/list/KnockRequestsListEvents.kt | 1 + .../impl/list/KnockRequestsListPresenter.kt | 62 +++++++++++++--- .../impl/list/KnockRequestsListState.kt | 8 +- .../list/KnockRequestsListStateProvider.kt | 45 +++++++++++- .../impl/list/KnockRequestsListView.kt | 73 +++++++++++++------ 5 files changed, 150 insertions(+), 39 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt index d84e5209b9..132c137ce2 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt @@ -12,6 +12,7 @@ import io.element.android.features.knockrequests.impl.KnockRequest sealed interface KnockRequestsListEvents { data class Accept(val knockRequest: KnockRequest) : KnockRequestsListEvents data class Decline(val knockRequest: KnockRequest) : KnockRequestsListEvents + data class DeclineAndBan(val knockRequest: KnockRequest) : KnockRequestsListEvents data object AcceptAll : KnockRequestsListEvents data object DismissCurrentAction : KnockRequestsListEvents } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt index e7168e967b..8ae993c9ef 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt @@ -8,35 +8,77 @@ package io.element.android.features.knockrequests.impl.list import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.ui.room.canBanAsState +import io.element.android.libraries.matrix.ui.room.canInviteAsState +import io.element.android.libraries.matrix.ui.room.canKickAsState import kotlinx.collections.immutable.persistentListOf import javax.inject.Inject -class KnockRequestsListPresenter @Inject constructor() : Presenter { +class KnockRequestsListPresenter @Inject constructor( + private val room: MatrixRoom, +) : Presenter { @Composable override fun present(): KnockRequestsListState { - val actions = remember { - mutableStateOf(KnockRequestsCurrentAction.None) - } + val currentAction = remember { mutableStateOf(KnockRequestsCurrentAction.None) } + val syncUpdateFlow = room.syncUpdateFlow.collectAsState() + val canBan by room.canBanAsState(syncUpdateFlow.value) + val canDecline by room.canKickAsState(syncUpdateFlow.value) + val canAccept by room.canInviteAsState(syncUpdateFlow.value) fun handleEvents(event: KnockRequestsListEvents) { when (event) { - KnockRequestsListEvents.AcceptAll -> Unit - is KnockRequestsListEvents.Accept -> Unit - is KnockRequestsListEvents.Decline -> Unit - KnockRequestsListEvents.DismissCurrentAction -> { - actions.value = KnockRequestsCurrentAction.None + KnockRequestsListEvents.AcceptAll -> { + currentAction.value = KnockRequestsCurrentAction.AcceptAll(AsyncAction.Uninitialized) } + is KnockRequestsListEvents.Accept -> { + currentAction.value = KnockRequestsCurrentAction.Accept(event.knockRequest, AsyncAction.Uninitialized) + } + is KnockRequestsListEvents.Decline -> { + currentAction.value = KnockRequestsCurrentAction.Decline(event.knockRequest, AsyncAction.Uninitialized) + } + is KnockRequestsListEvents.DeclineAndBan -> { + currentAction.value = KnockRequestsCurrentAction.DeclineAndBan(event.knockRequest, AsyncAction.Uninitialized) + } + KnockRequestsListEvents.DismissCurrentAction -> { + currentAction.value = KnockRequestsCurrentAction.None + } + } + } + + LaunchedEffect(currentAction) { + when (val action = currentAction.value) { + is KnockRequestsCurrentAction.Accept -> { + // Accept the knock request + } + is KnockRequestsCurrentAction.Decline -> { + // Decline the knock request + } + is KnockRequestsCurrentAction.DeclineAndBan -> { + // Decline and ban the user + } + is KnockRequestsCurrentAction.AcceptAll -> { + // Accept all knock requests + } + KnockRequestsCurrentAction.None -> Unit } } return KnockRequestsListState( knockRequests = AsyncData.Success(persistentListOf()), - currentAction = actions.value, + currentAction = currentAction.value, + canAccept = canAccept, + canDecline = canDecline, + canBan = canBan, eventSink = ::handleEvents ) } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt index 072ff5bdee..8c4cd3e0ed 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt @@ -15,6 +15,9 @@ import kotlinx.collections.immutable.ImmutableList data class KnockRequestsListState( val knockRequests: AsyncData>, val currentAction: KnockRequestsCurrentAction, + val canAccept: Boolean, + val canDecline: Boolean, + val canBan: Boolean, val eventSink: (KnockRequestsListEvents) -> Unit, ) { val canAcceptAll = knockRequests is AsyncData.Success && knockRequests.data.size > 1 @@ -22,7 +25,8 @@ data class KnockRequestsListState( sealed interface KnockRequestsCurrentAction { data object None : KnockRequestsCurrentAction - data class Accept(val async: AsyncAction) : KnockRequestsCurrentAction - data class Decline(val async: AsyncAction) : KnockRequestsCurrentAction + data class Accept(val knockRequest: KnockRequest, val async: AsyncAction) : KnockRequestsCurrentAction + data class Decline(val knockRequest: KnockRequest, val async: AsyncAction) : KnockRequestsCurrentAction + data class DeclineAndBan(val knockRequest: KnockRequest, val async: AsyncAction) : KnockRequestsCurrentAction data class AcceptAll(val async: AsyncAction) : KnockRequestsCurrentAction } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt index 761c850180..5a94a000ae 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt @@ -52,7 +52,40 @@ open class KnockRequestsListStateProvider : PreviewParameterProvider> = AsyncData.Success(persistentListOf()), - actions: KnockRequestsCurrentAction = KnockRequestsCurrentAction.None, + currentAction: KnockRequestsCurrentAction = KnockRequestsCurrentAction.None, + canAccept: Boolean = true, + canDecline: Boolean = true, + canBan: Boolean = true, eventSink: (KnockRequestsListEvents) -> Unit = {}, ) = KnockRequestsListState( knockRequests = knockRequests, - currentAction = actions, + currentAction = currentAction, + canAccept = canAccept, + canDecline = canDecline, + canBan = canBan, eventSink = eventSink, ) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index 416a00194c..bbf7facfd8 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -101,6 +101,9 @@ private fun KnockRequestsListContent(state: KnockRequestsListState, modifier: Mo } else { KnockRequestsList( knockRequests = knockRequests, + canAccept = state.canAccept, + canDecline = state.canDecline, + canBan = state.canBan, onAcceptClick = ::onAcceptClick, onDeclineClick = ::onDeclineClick, ) @@ -154,6 +157,13 @@ private fun KnockRequestsActionsView( onErrorDismiss = onDismiss, ) } + is KnockRequestsCurrentAction.DeclineAndBan -> { + AsyncActionView( + async = actions.async, + onSuccess = {}, + onErrorDismiss = onDismiss, + ) + } KnockRequestsCurrentAction.None -> Unit } } @@ -162,6 +172,9 @@ private fun KnockRequestsActionsView( @Composable private fun KnockRequestsList( knockRequests: ImmutableList, + canAccept: Boolean, + canDecline: Boolean, + canBan: Boolean, onAcceptClick: (KnockRequest) -> Unit, onDeclineClick: (KnockRequest) -> Unit, modifier: Modifier = Modifier, @@ -171,6 +184,9 @@ private fun KnockRequestsList( KnockRequestItem( knockRequest = knockRequest, onAcceptClick = onAcceptClick, + canBan = canBan, + canDecline = canDecline, + canAccept = canAccept, onDeclineClick = onDeclineClick, ) if (index != knockRequests.size - 1) { @@ -183,6 +199,9 @@ private fun KnockRequestsList( @Composable private fun KnockRequestItem( knockRequest: KnockRequest, + canAccept: Boolean, + canDecline: Boolean, + canBan: Boolean, onAcceptClick: (KnockRequest) -> Unit, onDeclineClick: (KnockRequest) -> Unit, modifier: Modifier = Modifier, @@ -194,7 +213,7 @@ private fun KnockRequestItem( ) { Avatar(knockRequest.getAvatarData(AvatarSize.KnockRequestItem)) Spacer(modifier = Modifier.width(16.dp)) - Column(modifier = Modifier) { + Column { // Name Text( modifier = Modifier.clipToBounds(), @@ -222,37 +241,43 @@ private fun KnockRequestItem( style = ElementTheme.typography.fontBodyMdRegular, ) } + // Actions Spacer(modifier = Modifier.height(12.dp)) Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) { - OutlinedButton( - text = stringResource(CommonStrings.action_decline), - onClick = { - onDeclineClick(knockRequest) - }, - size = ButtonSize.MediumLowPadding, - modifier = Modifier.weight(1f), - ) - Button( - text = stringResource(CommonStrings.action_accept), + if (canDecline) { + OutlinedButton( + text = stringResource(CommonStrings.action_decline), + onClick = { + onDeclineClick(knockRequest) + }, + size = ButtonSize.MediumLowPadding, + modifier = Modifier.weight(1f), + ) + } + if (canAccept) { + Button( + text = stringResource(CommonStrings.action_accept), + onClick = { + onAcceptClick(knockRequest) + }, + size = ButtonSize.MediumLowPadding, + modifier = Modifier.weight(1f), + ) + } + } + if (canBan) { + Spacer(modifier = Modifier.height(12.dp)) + TextButton( + text = stringResource(CommonStrings.screen_knock_requests_list_decline_and_ban_action_title), onClick = { onAcceptClick(knockRequest) }, - size = ButtonSize.MediumLowPadding, - modifier = Modifier.weight(1f), + destructive = true, + size = ButtonSize.Small, + modifier = Modifier.fillMaxWidth(), ) } - Spacer(modifier = Modifier.height(12.dp)) - TextButton( - text = stringResource(CommonStrings.screen_knock_requests_list_decline_and_ban_action_title), - onClick = { - onAcceptClick(knockRequest) - }, - destructive = true, - size = ButtonSize.Small, - modifier = Modifier.fillMaxWidth(), - ) - } } } From 95cf18dbb5df4bbd45d8f89e2ff526dbad9ffdb1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 29 Nov 2024 16:37:46 +0100 Subject: [PATCH 06/13] knock requests : adjust spacing for actions --- .../features/knockrequests/impl/list/KnockRequestsListView.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index bbf7facfd8..de08550d9c 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -242,7 +242,9 @@ private fun KnockRequestItem( ) } // Actions - Spacer(modifier = Modifier.height(12.dp)) + if (canDecline || canAccept) { + Spacer(modifier = Modifier.height(12.dp)) + } Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) { if (canDecline) { OutlinedButton( From 0f12b151fda5eff6583804409fcfebc30b078147 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 2 Dec 2024 10:55:38 +0100 Subject: [PATCH 07/13] knock requests : allow reason to be expanded --- .../list/KnockRequestsListStateProvider.kt | 11 +++++ .../impl/list/KnockRequestsListView.kt | 42 +++++++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt index 5a94a000ae..7b7b1e4f5e 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt @@ -33,6 +33,17 @@ open class KnockRequestsListStateProvider : PreviewParameterProvider + if (!isExpanded && result.hasVisualOverflow) { + isExpandable = true + } + }, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f), + ) + Box(modifier = Modifier.size(24.dp)) { + if (isExpandable) { + Icon( + imageVector = if (isExpanded) CompoundIcons.ChevronUp() else CompoundIcons.ChevronDown(), + contentDescription = null, + tint = ElementTheme.colors.iconTertiary, + ) + } + } + } } // Actions if (canDecline || canAccept) { From 822dbca424d8ceac8e6ee6be962c2d5d568e3b56 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 4 Dec 2024 13:48:15 +0100 Subject: [PATCH 08/13] knock requests : add formatted date --- .../knockrequests/impl/KnockRequest.kt | 1 + .../list/KnockRequestsListStateProvider.kt | 2 ++ .../impl/list/KnockRequestsListView.kt | 30 +++++++++++++------ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt index 3a293dc565..81ba322abe 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt @@ -17,6 +17,7 @@ data class KnockRequest( val displayName: String?, val avatarUrl: String?, val reason: String?, + val formattedDate: String?, ) fun KnockRequest.getAvatarData(size: AvatarSize) = AvatarData( diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt index 7b7b1e4f5e..551f6b89b0 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt @@ -106,11 +106,13 @@ fun aKnockRequest( displayName: String? = "Jacob Ross", avatarUrl: String? = null, reason: String? = "Hi, I would like to get access to this room please.", + formattedDate: String = "20 Nov 2024", ) = KnockRequest( userId = userId, displayName = displayName, avatarUrl = avatarUrl, reason = reason, + formattedDate = formattedDate, ) fun aKnockRequestsListState( diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index 2d9480a02e..abc0a09970 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -222,15 +222,27 @@ private fun KnockRequestItem( Avatar(knockRequest.getAvatarData(AvatarSize.KnockRequestItem)) Spacer(modifier = Modifier.width(16.dp)) Column { - // Name - Text( - modifier = Modifier.clipToBounds(), - text = knockRequest.getBestName(), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.primary, - style = ElementTheme.typography.fontBodyLgMedium, - ) + // Name and date + Row { + Text( + modifier = Modifier + .clipToBounds() + .weight(1f), + text = knockRequest.getBestName(), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.primary, + style = ElementTheme.typography.fontBodyLgMedium, + ) + if (!knockRequest.formattedDate.isNullOrEmpty()) { + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = knockRequest.formattedDate, + color = MaterialTheme.colorScheme.secondary, + style = ElementTheme.typography.fontBodySmRegular, + ) + } + } // UserId if (!knockRequest.displayName.isNullOrEmpty()) { Text( From 80f25e637a4ab4c39be4789e0cf9272b483e66af Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 4 Dec 2024 14:15:17 +0100 Subject: [PATCH 09/13] knock requests : cleaning up --- features/knockrequests/api/build.gradle.kts | 3 -- .../knockrequests/impl/KnockRequest.kt | 4 -- .../impl/list/KnockRequestsListNode.kt | 2 - .../impl/list/KnockRequestsListPresenter.kt | 3 +- .../impl/list/KnockRequestsListView.kt | 45 ++++++++++++++----- .../impl/src/main/res/values/localazy.xml | 17 +++++++ .../roomdetails/impl/RoomDetailsPresenter.kt | 4 -- .../src/main/res/values/localazy.xml | 5 --- tools/localazy/config.json | 6 +++ 9 files changed, 57 insertions(+), 32 deletions(-) create mode 100644 features/knockrequests/impl/src/main/res/values/localazy.xml diff --git a/features/knockrequests/api/build.gradle.kts b/features/knockrequests/api/build.gradle.kts index e3d563f2e0..90bcb6e568 100644 --- a/features/knockrequests/api/build.gradle.kts +++ b/features/knockrequests/api/build.gradle.kts @@ -5,8 +5,6 @@ * Please see LICENSE in the repository root for full details. */ -import extension.setupAnvil - plugins { id("io.element.android-library") } @@ -19,4 +17,3 @@ dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) } - diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt index 81ba322abe..d9df9a5bc2 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt @@ -10,7 +10,6 @@ package io.element.android.features.knockrequests.impl import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.api.user.MatrixUser data class KnockRequest( val userId: UserId, @@ -30,6 +29,3 @@ fun KnockRequest.getAvatarData(size: AvatarSize) = AvatarData( fun KnockRequest.getBestName(): String { return displayName?.takeIf { it.isNotEmpty() } ?: userId.value } - - - diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt index 0310f67d2e..ce8d602861 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt @@ -15,7 +15,6 @@ import com.bumble.appyx.core.plugin.Plugin import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode -import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.RoomScope @ContributesNode(RoomScope::class) @@ -24,7 +23,6 @@ class KnockRequestsListNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: KnockRequestsListPresenter, ) : Node(buildContext, plugins = plugins) { - @Composable override fun View(modifier: Modifier) { val state = presenter.present() diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt index 8ae993c9ef..fcafc5428b 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt @@ -26,7 +26,6 @@ import javax.inject.Inject class KnockRequestsListPresenter @Inject constructor( private val room: MatrixRoom, ) : Presenter { - @Composable override fun present(): KnockRequestsListState { val currentAction = remember { mutableStateOf(KnockRequestsCurrentAction.None) } @@ -56,7 +55,7 @@ class KnockRequestsListPresenter @Inject constructor( } LaunchedEffect(currentAction) { - when (val action = currentAction.value) { + when (currentAction.value) { is KnockRequestsCurrentAction.Accept -> { // Accept the knock request } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index abc0a09970..ca289eeef4 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets @@ -28,7 +29,9 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -36,6 +39,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter @@ -43,6 +47,7 @@ import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.features.knockrequests.impl.R import io.element.android.features.knockrequests.impl.getAvatarData import io.element.android.features.knockrequests.impl.getBestName import io.element.android.libraries.architecture.AsyncData @@ -54,6 +59,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.text.toDp import io.element.android.libraries.designsystem.theme.aliasScreenTitle import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.ButtonSize @@ -90,8 +96,10 @@ fun KnockRequestsListView( } @Composable -private fun KnockRequestsListContent(state: KnockRequestsListState, modifier: Modifier) { - +private fun KnockRequestsListContent( + state: KnockRequestsListState, + modifier: Modifier = Modifier, +) { fun onAcceptClick(knockRequest: KnockRequest) { state.eventSink(KnockRequestsListEvents.Accept(knockRequest)) } @@ -100,6 +108,8 @@ private fun KnockRequestsListContent(state: KnockRequestsListState, modifier: Mo state.eventSink(KnockRequestsListEvents.Decline(knockRequest)) } + var bottomPaddingInPixels by remember { mutableIntStateOf(0) } + Box(modifier.fillMaxSize()) { when (state.knockRequests) { is AsyncData.Success -> { @@ -114,6 +124,7 @@ private fun KnockRequestsListContent(state: KnockRequestsListState, modifier: Mo canBan = state.canBan, onAcceptClick = ::onAcceptClick, onDeclineClick = ::onDeclineClick, + contentPadding = PaddingValues(bottom = bottomPaddingInPixels.toDp()), ) } } @@ -130,6 +141,9 @@ private fun KnockRequestsListContent(state: KnockRequestsListState, modifier: Mo onClick = { state.eventSink(KnockRequestsListEvents.AcceptAll) }, + onHeightChange = { height -> + bottomPaddingInPixels = height + }, modifier = Modifier.align(Alignment.BottomCenter), ) } @@ -186,8 +200,12 @@ private fun KnockRequestsList( onAcceptClick: (KnockRequest) -> Unit, onDeclineClick: (KnockRequest) -> Unit, modifier: Modifier = Modifier, + contentPadding: PaddingValues = PaddingValues(0.dp), ) { - LazyColumn(modifier = modifier) { + LazyColumn( + modifier = modifier.fillMaxSize(), + contentPadding = contentPadding, + ) { itemsIndexed(knockRequests) { index, knockRequest -> KnockRequestItem( knockRequest = knockRequest, @@ -316,7 +334,7 @@ private fun KnockRequestItem( if (canBan) { Spacer(modifier = Modifier.height(12.dp)) TextButton( - text = stringResource(CommonStrings.screen_knock_requests_list_decline_and_ban_action_title), + text = stringResource(R.string.screen_knock_requests_list_decline_and_ban_action_title), onClick = { onAcceptClick(knockRequest) }, @@ -325,22 +343,25 @@ private fun KnockRequestItem( modifier = Modifier.fillMaxWidth(), ) } - } } } @Composable -private fun KnockRequestsAcceptAll(onClick: () -> Unit, modifier: Modifier) { +private fun KnockRequestsAcceptAll( + onClick: () -> Unit, + onHeightChange: (Int) -> Unit, + modifier: Modifier = Modifier +) { Box( modifier = modifier .shadow(elevation = 24.dp, spotColor = Color.Transparent) .background(color = ElementTheme.colors.bgCanvasDefault) .padding(vertical = 12.dp, horizontal = 16.dp) - ) - { + .onSizeChanged { onHeightChange(it.height) } + ) { OutlinedButton( - text = ("Accept all"), + text = stringResource(R.string.screen_knock_requests_list_accept_all_button_title), onClick = onClick, size = ButtonSize.Medium, modifier = Modifier.fillMaxWidth(), @@ -360,8 +381,8 @@ private fun KnockRequestsEmptyList( contentAlignment = Alignment.Center, ) { IconTitleSubtitleMolecule( - title = "No pending request to join", - subTitle = "When somebody will ask to join the room, you’ll be able to see their request here.", + title = stringResource(R.string.screen_knock_requests_list_empty_state_title), + subTitle = stringResource(R.string.screen_knock_requests_list_empty_state_description), iconStyle = BigIcon.Style.Default(CompoundIcons.Pin()), ) } @@ -373,7 +394,7 @@ private fun KnockRequestsListTopBar(onBackClick: () -> Unit) { TopAppBar( title = { Text( - text = "Requests to join", + text = stringResource(R.string.screen_knock_requests_list_title), style = ElementTheme.typography.aliasScreenTitle, ) }, diff --git a/features/knockrequests/impl/src/main/res/values/localazy.xml b/features/knockrequests/impl/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..df14d665b8 --- /dev/null +++ b/features/knockrequests/impl/src/main/res/values/localazy.xml @@ -0,0 +1,17 @@ + + + "Yes, accept all" + "Are you sure you want to accept all requests to join?" + "Accept all requests" + "Accept all" + "Yes, decline and ban" + "Are you sure you want to decline and ban %1$s? This user won’t be able to request access to join this room again." + "Decline and ban from accessing" + "Yes, decline" + "Are you sure you want to decline %1$s request to join this room?" + "Decline access" + "Decline and ban" + "When somebody will ask to join the room, you’ll be able to see their request here." + "No pending request to join" + "Requests to join" + diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index dc6568a60f..46588ae5fe 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -169,10 +169,6 @@ class RoomDetailsPresenter @Inject constructor( ) } - private fun getCanBan(membersState: MatrixRoomMembersState): Any { - TODO("Not yet implemented") - } - @Composable private fun roomMemberDetailsPresenter(dmMemberState: RoomMember?) = remember(dmMemberState) { dmMemberState?.let { roomMember -> diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 18d580d9f4..f52b53564b 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -296,11 +296,6 @@ Reason: %1$s." "Hey, talk to me on %1$s: %2$s" "%1$s Android" "Rageshake to report bug" - "Accept all" - "Decline and ban" - "When somebody will ask to join the room, you’ll be able to see their request here." - "No pending request to join" - "Requests to join" "Failed selecting media, please try again." "Failed processing media to upload, please try again." "Failed uploading media, please try again." diff --git a/tools/localazy/config.json b/tools/localazy/config.json index f18744bae9..9bf178ac85 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -286,6 +286,12 @@ "screen_join_room_.*", "screen\\.join_room\\..*" ] + }, + { + "name" : ":features:knockrequests:impl", + "includeRegex" : [ + "screen\\.knock_requests_list\\..*" + ] } ] } From 6bf9c41d6b9918710940ff1e0e44880168908d1f Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 4 Dec 2024 13:48:20 +0000 Subject: [PATCH 10/13] Update screenshots --- ....knockrequests.impl.list_KnockRequestsListView_Day_0_en.png | 3 +++ ....knockrequests.impl.list_KnockRequestsListView_Day_1_en.png | 3 +++ ....knockrequests.impl.list_KnockRequestsListView_Day_2_en.png | 3 +++ ....knockrequests.impl.list_KnockRequestsListView_Day_3_en.png | 3 +++ ....knockrequests.impl.list_KnockRequestsListView_Day_4_en.png | 3 +++ ....knockrequests.impl.list_KnockRequestsListView_Day_5_en.png | 3 +++ ....knockrequests.impl.list_KnockRequestsListView_Day_6_en.png | 3 +++ ....knockrequests.impl.list_KnockRequestsListView_Day_7_en.png | 3 +++ ....knockrequests.impl.list_KnockRequestsListView_Day_8_en.png | 3 +++ ....knockrequests.impl.list_KnockRequestsListView_Day_9_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_0_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_1_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_2_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_3_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_4_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_5_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_6_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_7_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_8_en.png | 3 +++ ...nockrequests.impl.list_KnockRequestsListView_Night_9_en.png | 3 +++ ...ies.designsystem.components.avatar_Avatar_Avatars_78_en.png | 3 +++ ...ies.designsystem.components.avatar_Avatar_Avatars_79_en.png | 3 +++ ...ies.designsystem.components.avatar_Avatar_Avatars_80_en.png | 3 +++ 23 files changed, 69 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_5_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_6_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_7_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_8_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_9_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_5_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_6_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_7_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_8_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_9_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_78_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_79_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_80_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_0_en.png new file mode 100644 index 0000000000..d2ddc2c7c6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbbd687e5e0a1fd3a1be442937e32990dc72a6e4817d6ffc29f6f596eeddef1b +size 8086 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_1_en.png new file mode 100644 index 0000000000..66c6102947 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9595edf8183796a0a0efa838a3c5265e07f70452933e6255d79f536cfca64ac +size 26138 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_2_en.png new file mode 100644 index 0000000000..b75df7ecea --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a71bcfe210fdc6b2a0c54fe187d9528811bd6b5d442a66a1151b7a00ae514273 +size 33141 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_3_en.png new file mode 100644 index 0000000000..92f98168bb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:036a4f8f2f478df0e1705ff4bfd04599f6c0251936d2a80d750c9055d9d63ae0 +size 41550 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_4_en.png new file mode 100644 index 0000000000..f125ca034e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99f070f9b5bdea3aa057776b303be171daff36f684770174f46b37ecc0eea781 +size 52135 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_5_en.png new file mode 100644 index 0000000000..c14ef5dd52 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7e66d3f7d10e759f629795580545cda6644292020ce36763d07e9d476dcc231 +size 30325 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_6_en.png new file mode 100644 index 0000000000..466e591450 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:586021e67718aa949b1de04799a43d9da8189fadacdb6dba23405c762fc7ee06 +size 30618 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_7_en.png new file mode 100644 index 0000000000..2952c5bd81 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_7_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b71dc6dc0d29801e81d1d4e52df811624583f69c0510f2f650676747a326455 +size 30296 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_8_en.png new file mode 100644 index 0000000000..9c47af83a4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6be2cfe7991bce6c622f4f2e45f4ac96810cdc9462d9a4d7aea11baf69ab5ac +size 27446 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_9_en.png new file mode 100644 index 0000000000..8f0414d4b3 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_9_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9afb58cfcd13c064f0b17d449318d5111aaa914cb8875fe0596c66e5a8a17051 +size 30647 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_0_en.png new file mode 100644 index 0000000000..5188e40e69 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ff4bc9e0588ea6a8396572b9ac9fb2401c3193fc3f1fbeb75efd69be6dda24d +size 7867 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_1_en.png new file mode 100644 index 0000000000..055bfccd93 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37e24295a404463010f6d36ab94b1ac3c2e193d9122637abbeb0a43abcb427c1 +size 25669 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_2_en.png new file mode 100644 index 0000000000..b9982135b9 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7548a1029b4e63722aca769800728c0bb519d3d736cb54138bc5e053a1aca46e +size 32150 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_3_en.png new file mode 100644 index 0000000000..2c6855f6ab --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b094584e67852da5ecb74511143943d3526965a548a24d5bb4e23540fc77b90 +size 40527 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_4_en.png new file mode 100644 index 0000000000..0bca790524 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79e34ab35d9bd44d6e7694df882995f401f49a2ba042c6a3b2c010c12657750f +size 50637 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_5_en.png new file mode 100644 index 0000000000..45b2a829ef --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c30b6e25ec148d4f98a6c305b7d71af6c7e413cc3425f5a6f5b42a201039f491 +size 29007 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_6_en.png new file mode 100644 index 0000000000..b39ccb0150 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5c6abc8bd5ca40eb91d926adc798cee95a8aacb0e87d937983c6240173fe2b9 +size 30071 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_7_en.png new file mode 100644 index 0000000000..215cef276c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_7_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b67c8d2cedb724ece75a2171463c4e035d562c4480d39b11b1072f4fdae3053 +size 29840 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_8_en.png new file mode 100644 index 0000000000..171b5f5aa5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15903c9494100becc4dd2bc6e9fb7f38f8eebbb595cbc251cfc0f839d9a99a27 +size 27322 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_9_en.png new file mode 100644 index 0000000000..54a5fc7d9c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_9_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68294db4cbd8bb42f4a0b6534b48585341beb2bc405e93cf74a2655f049522ff +size 29768 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_78_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_78_en.png new file mode 100644 index 0000000000..cf85f766ab --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_78_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66c29b560708bb2d8870d8cf5caf4f0da49815b5527fed7294a88c0b3aa05c73 +size 18079 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_79_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_79_en.png new file mode 100644 index 0000000000..b27ebae0ce --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_79_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3dd106bc9b54dfc0c4e3e9a5f04d3df52f58f71e4c3cd3d80f53d3f1308f22a1 +size 16838 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_80_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_80_en.png new file mode 100644 index 0000000000..1698660c8e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_80_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4979794c700bc8bab1bc767cc2387ce3be28b9d2c0b6da0696b237445ce7df95 +size 21225 From 5ac401be57e1a36d4fc93cd0b2df41e49c492dc7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 5 Dec 2024 12:15:35 +0100 Subject: [PATCH 11/13] knock requests : fix stability of KnockRequestsCurrentAction --- .../features/knockrequests/impl/list/KnockRequestsListState.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt index 8c4cd3e0ed..3ba10e9302 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt @@ -7,6 +7,7 @@ package io.element.android.features.knockrequests.impl.list +import androidx.compose.runtime.Immutable import io.element.android.features.knockrequests.impl.KnockRequest import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData @@ -23,6 +24,7 @@ data class KnockRequestsListState( val canAcceptAll = knockRequests is AsyncData.Success && knockRequests.data.size > 1 } +@Immutable sealed interface KnockRequestsCurrentAction { data object None : KnockRequestsCurrentAction data class Accept(val knockRequest: KnockRequest, val async: AsyncAction) : KnockRequestsCurrentAction From be1a8a3d16f41082704f2f14ff7caff7f6d6a0b7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 5 Dec 2024 12:57:15 +0100 Subject: [PATCH 12/13] knock requests : fix test on room details view --- .../roomdetails/impl/RoomDetailsView.kt | 4 ++-- .../impl/src/main/res/values/localazy.xml | 3 +++ .../roomdetails/impl/RoomDetailsViewTest.kt | 19 ++++++++++++++++++- .../src/main/res/values/localazy.xml | 2 -- tools/localazy/config.json | 1 + 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index 80f7beda56..d77cd9e6ea 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -241,7 +241,7 @@ fun RoomDetailsView( @Composable private fun KnockRequestsItem(knockRequestsCount: Int?, onKnockRequestsClick: () -> Unit) { ListItem( - headlineContent = { Text(stringResource(CommonStrings.screen_room_details_requests_to_join_title)) }, + headlineContent = { Text(stringResource(R.string.screen_room_details_requests_to_join_title)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Notifications())), trailingContent = if (knockRequestsCount == null || knockRequestsCount == 0) { null @@ -546,7 +546,7 @@ private fun PinnedMessagesItem( ) { val analyticsService = LocalAnalyticsService.current ListItem( - headlineContent = { Text(stringResource(CommonStrings.screen_room_details_pinned_events_row_title)) }, + headlineContent = { Text(stringResource(R.string.screen_room_details_pinned_events_row_title)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Pin())), trailingContent = if (pinnedMessagesCount == null) { diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index 19400c85b3..50c2d639be 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -49,9 +49,12 @@ "Invite people" "Leave conversation" "Leave room" + "Media and files" "Custom" "Default" "Notifications" + "Pinned messages" + "Requests to join" "Roles and permissions" "Room name" "Security" diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt index e4eb8d5f69..abbca71b53 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt @@ -129,7 +129,7 @@ class RoomDetailsViewTest { ), onPinnedMessagesClick = callback, ) - rule.clickOn(CommonStrings.screen_room_details_pinned_events_row_title) + rule.clickOn(R.string.screen_room_details_pinned_events_row_title) } } @@ -253,6 +253,21 @@ class RoomDetailsViewTest { rule.clickOn(R.string.screen_room_details_leave_room_title) eventsRecorder.assertSingle(RoomDetailsEvent.LeaveRoom) } + + @Config(qualifiers = "h1024dp") + @Test + fun `click on knock requests invokes expected callback`() { + ensureCalledOnce { callback -> + rule.setRoomDetailView( + state = aRoomDetailsState( + eventSink = EventsRecorder(expectEvents = false), + canShowKnockRequests = true, + ), + onKnockRequestsClick = callback, + ) + rule.clickOn(R.string.screen_room_details_requests_to_join_title) + } + } } private fun AndroidComposeTestRule.setRoomDetailView( @@ -270,6 +285,7 @@ private fun AndroidComposeTestRule.setRoomD openAdminSettings: () -> Unit = EnsureNeverCalled(), onJoinCallClick: () -> Unit = EnsureNeverCalled(), onPinnedMessagesClick: () -> Unit = EnsureNeverCalled(), + onKnockRequestsClick: () -> Unit = EnsureNeverCalled(), ) { setContent { RoomDetailsView( @@ -285,6 +301,7 @@ private fun AndroidComposeTestRule.setRoomD openAdminSettings = openAdminSettings, onJoinCallClick = onJoinCallClick, onPinnedMessagesClick = onPinnedMessagesClick, + onKnockRequestsClick = onKnockRequestsClick, ) } } diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 8ab9695844..75b0f6853a 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -320,8 +320,6 @@ Reason: %1$s." "Your message was not sent because %1$s has not verified all devices" "One or more of your devices are unverified. You can send the message anyway, or you can cancel for now and try again later after you have verified all of your devices." "Your message was not sent because you have not verified one or more of your devices" - "Pinned messages" - "Requests to join" "Failed processing media to upload, please try again." "Could not retrieve user details" diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 9bf178ac85..fe95d1930e 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -165,6 +165,7 @@ "name" : ":features:roomdetails:impl", "includeRegex" : [ "screen_room_details_.*", + "screen\\.room_details\\..*", "screen_room_member_list_.*", "screen_room_notification_settings_.*", "screen_notification_settings_edit_failed_updating_default_mode", From 584f20a08888fc8c5fb9497645f98ff47181e143 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 5 Dec 2024 18:28:31 +0100 Subject: [PATCH 13/13] knock requests : small nit on RoomDetailsNode Co-authored-by: Benoit Marty --- .../android/features/roomdetails/impl/RoomDetailsNode.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt index e9ad095c76..a56a028808 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt @@ -146,7 +146,7 @@ class RoomDetailsNode @AssistedInject constructor( openAdminSettings = this::openAdminSettings, onJoinCallClick = ::onJoinCall, onPinnedMessagesClick = ::openPinnedMessages, - onKnockRequestsClick = ::openKnockRequestsLists + onKnockRequestsClick = ::openKnockRequestsLists, ) } }