Skip to content

Commit eae73ac

Browse files
authored
Merge pull request #3995 from element-hq/feature/fga/requests_to_join_list
feat(knock_requests_list) : implement design
2 parents d87062d + 584f20a commit eae73ac

File tree

49 files changed

+1031
-20
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1031
-20
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
plugins {
9+
id("io.element.android-library")
10+
}
11+
12+
android {
13+
namespace = "io.element.android.features.knockrequests.api"
14+
}
15+
16+
dependencies {
17+
implementation(projects.libraries.architecture)
18+
implementation(projects.libraries.matrix.api)
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.features.knockrequests.api.list
9+
10+
import io.element.android.libraries.architecture.SimpleFeatureEntryPoint
11+
12+
interface KnockRequestsListEntryPoint : SimpleFeatureEntryPoint
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
import extension.setupAnvil
9+
10+
plugins {
11+
id("io.element.android-compose-library")
12+
id("kotlin-parcelize")
13+
}
14+
15+
android {
16+
namespace = "io.element.android.features.knockrequests.impl"
17+
}
18+
19+
setupAnvil()
20+
21+
dependencies {
22+
api(projects.features.knockrequests.api)
23+
implementation(projects.libraries.core)
24+
implementation(projects.libraries.architecture)
25+
implementation(projects.libraries.matrix.api)
26+
implementation(projects.libraries.matrixui)
27+
implementation(projects.libraries.uiStrings)
28+
implementation(projects.libraries.designsystem)
29+
30+
testImplementation(libs.test.junit)
31+
testImplementation(libs.coroutines.test)
32+
testImplementation(libs.molecule.runtime)
33+
testImplementation(libs.test.truth)
34+
testImplementation(libs.test.turbine)
35+
testImplementation(projects.libraries.matrix.test)
36+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.features.knockrequests.impl
9+
10+
import io.element.android.libraries.designsystem.components.avatar.AvatarData
11+
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
12+
import io.element.android.libraries.matrix.api.core.UserId
13+
14+
data class KnockRequest(
15+
val userId: UserId,
16+
val displayName: String?,
17+
val avatarUrl: String?,
18+
val reason: String?,
19+
val formattedDate: String?,
20+
)
21+
22+
fun KnockRequest.getAvatarData(size: AvatarSize) = AvatarData(
23+
id = userId.value,
24+
name = displayName,
25+
url = avatarUrl,
26+
size = size,
27+
)
28+
29+
fun KnockRequest.getBestName(): String {
30+
return displayName?.takeIf { it.isNotEmpty() } ?: userId.value
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.features.knockrequests.impl.list
9+
10+
import com.bumble.appyx.core.modality.BuildContext
11+
import com.bumble.appyx.core.node.Node
12+
import com.squareup.anvil.annotations.ContributesBinding
13+
import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint
14+
import io.element.android.libraries.architecture.createNode
15+
import io.element.android.libraries.di.AppScope
16+
import javax.inject.Inject
17+
18+
@ContributesBinding(AppScope::class)
19+
class DefaultKnockRequestsListEntryPoint @Inject constructor() : KnockRequestsListEntryPoint {
20+
override fun createNode(parentNode: Node, buildContext: BuildContext): Node {
21+
return parentNode.createNode<KnockRequestsListNode>(buildContext)
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.features.knockrequests.impl.list
9+
10+
import io.element.android.features.knockrequests.impl.KnockRequest
11+
12+
sealed interface KnockRequestsListEvents {
13+
data class Accept(val knockRequest: KnockRequest) : KnockRequestsListEvents
14+
data class Decline(val knockRequest: KnockRequest) : KnockRequestsListEvents
15+
data class DeclineAndBan(val knockRequest: KnockRequest) : KnockRequestsListEvents
16+
data object AcceptAll : KnockRequestsListEvents
17+
data object DismissCurrentAction : KnockRequestsListEvents
18+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.features.knockrequests.impl.list
9+
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.ui.Modifier
12+
import com.bumble.appyx.core.modality.BuildContext
13+
import com.bumble.appyx.core.node.Node
14+
import com.bumble.appyx.core.plugin.Plugin
15+
import dagger.assisted.Assisted
16+
import dagger.assisted.AssistedInject
17+
import io.element.android.anvilannotations.ContributesNode
18+
import io.element.android.libraries.di.RoomScope
19+
20+
@ContributesNode(RoomScope::class)
21+
class KnockRequestsListNode @AssistedInject constructor(
22+
@Assisted buildContext: BuildContext,
23+
@Assisted plugins: List<Plugin>,
24+
private val presenter: KnockRequestsListPresenter,
25+
) : Node(buildContext, plugins = plugins) {
26+
@Composable
27+
override fun View(modifier: Modifier) {
28+
val state = presenter.present()
29+
KnockRequestsListView(
30+
state = state,
31+
onBackClick = ::navigateUp,
32+
modifier = modifier
33+
)
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.features.knockrequests.impl.list
9+
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.runtime.LaunchedEffect
12+
import androidx.compose.runtime.collectAsState
13+
import androidx.compose.runtime.getValue
14+
import androidx.compose.runtime.mutableStateOf
15+
import androidx.compose.runtime.remember
16+
import io.element.android.libraries.architecture.AsyncAction
17+
import io.element.android.libraries.architecture.AsyncData
18+
import io.element.android.libraries.architecture.Presenter
19+
import io.element.android.libraries.matrix.api.room.MatrixRoom
20+
import io.element.android.libraries.matrix.ui.room.canBanAsState
21+
import io.element.android.libraries.matrix.ui.room.canInviteAsState
22+
import io.element.android.libraries.matrix.ui.room.canKickAsState
23+
import kotlinx.collections.immutable.persistentListOf
24+
import javax.inject.Inject
25+
26+
class KnockRequestsListPresenter @Inject constructor(
27+
private val room: MatrixRoom,
28+
) : Presenter<KnockRequestsListState> {
29+
@Composable
30+
override fun present(): KnockRequestsListState {
31+
val currentAction = remember { mutableStateOf<KnockRequestsCurrentAction>(KnockRequestsCurrentAction.None) }
32+
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
33+
val canBan by room.canBanAsState(syncUpdateFlow.value)
34+
val canDecline by room.canKickAsState(syncUpdateFlow.value)
35+
val canAccept by room.canInviteAsState(syncUpdateFlow.value)
36+
37+
fun handleEvents(event: KnockRequestsListEvents) {
38+
when (event) {
39+
KnockRequestsListEvents.AcceptAll -> {
40+
currentAction.value = KnockRequestsCurrentAction.AcceptAll(AsyncAction.Uninitialized)
41+
}
42+
is KnockRequestsListEvents.Accept -> {
43+
currentAction.value = KnockRequestsCurrentAction.Accept(event.knockRequest, AsyncAction.Uninitialized)
44+
}
45+
is KnockRequestsListEvents.Decline -> {
46+
currentAction.value = KnockRequestsCurrentAction.Decline(event.knockRequest, AsyncAction.Uninitialized)
47+
}
48+
is KnockRequestsListEvents.DeclineAndBan -> {
49+
currentAction.value = KnockRequestsCurrentAction.DeclineAndBan(event.knockRequest, AsyncAction.Uninitialized)
50+
}
51+
KnockRequestsListEvents.DismissCurrentAction -> {
52+
currentAction.value = KnockRequestsCurrentAction.None
53+
}
54+
}
55+
}
56+
57+
LaunchedEffect(currentAction) {
58+
when (currentAction.value) {
59+
is KnockRequestsCurrentAction.Accept -> {
60+
// Accept the knock request
61+
}
62+
is KnockRequestsCurrentAction.Decline -> {
63+
// Decline the knock request
64+
}
65+
is KnockRequestsCurrentAction.DeclineAndBan -> {
66+
// Decline and ban the user
67+
}
68+
is KnockRequestsCurrentAction.AcceptAll -> {
69+
// Accept all knock requests
70+
}
71+
KnockRequestsCurrentAction.None -> Unit
72+
}
73+
}
74+
75+
return KnockRequestsListState(
76+
knockRequests = AsyncData.Success(persistentListOf()),
77+
currentAction = currentAction.value,
78+
canAccept = canAccept,
79+
canDecline = canDecline,
80+
canBan = canBan,
81+
eventSink = ::handleEvents
82+
)
83+
}
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.features.knockrequests.impl.list
9+
10+
import androidx.compose.runtime.Immutable
11+
import io.element.android.features.knockrequests.impl.KnockRequest
12+
import io.element.android.libraries.architecture.AsyncAction
13+
import io.element.android.libraries.architecture.AsyncData
14+
import kotlinx.collections.immutable.ImmutableList
15+
16+
data class KnockRequestsListState(
17+
val knockRequests: AsyncData<ImmutableList<KnockRequest>>,
18+
val currentAction: KnockRequestsCurrentAction,
19+
val canAccept: Boolean,
20+
val canDecline: Boolean,
21+
val canBan: Boolean,
22+
val eventSink: (KnockRequestsListEvents) -> Unit,
23+
) {
24+
val canAcceptAll = knockRequests is AsyncData.Success && knockRequests.data.size > 1
25+
}
26+
27+
@Immutable
28+
sealed interface KnockRequestsCurrentAction {
29+
data object None : KnockRequestsCurrentAction
30+
data class Accept(val knockRequest: KnockRequest, val async: AsyncAction<Unit>) : KnockRequestsCurrentAction
31+
data class Decline(val knockRequest: KnockRequest, val async: AsyncAction<Unit>) : KnockRequestsCurrentAction
32+
data class DeclineAndBan(val knockRequest: KnockRequest, val async: AsyncAction<Unit>) : KnockRequestsCurrentAction
33+
data class AcceptAll(val async: AsyncAction<Unit>) : KnockRequestsCurrentAction
34+
}

0 commit comments

Comments
 (0)