Skip to content

Commit 80ae086

Browse files
committed
knock requests : rework knock requests service to avoid reloading of data (and weird ui glitch because of them)
1 parent 7f37228 commit 80ae086

File tree

12 files changed

+127
-100
lines changed

12 files changed

+127
-100
lines changed

features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,6 @@ import io.element.android.features.knockrequests.impl.data.KnockRequestsService
1919
import io.element.android.libraries.architecture.Presenter
2020
import io.element.android.libraries.core.coroutine.mapState
2121
import io.element.android.libraries.core.extensions.firstIfSingle
22-
import io.element.android.libraries.featureflag.api.FeatureFlagService
23-
import io.element.android.libraries.featureflag.api.FeatureFlags
24-
import io.element.android.libraries.matrix.api.room.MatrixRoom
25-
import io.element.android.libraries.matrix.ui.room.canHandleKnockRequestsAsState
26-
import io.element.android.libraries.matrix.ui.room.canInviteAsState
2722
import kotlinx.collections.immutable.toImmutableList
2823
import kotlinx.coroutines.CoroutineScope
2924
import kotlinx.coroutines.delay
@@ -33,10 +28,8 @@ import javax.inject.Inject
3328
private const val ACCEPT_ERROR_DISPLAY_DURATION = 1500L
3429

3530
class KnockRequestsBannerPresenter @Inject constructor(
36-
private val room: MatrixRoom,
3731
private val knockRequestsService: KnockRequestsService,
3832
private val appCoroutineScope: CoroutineScope,
39-
private val featureFlagService: FeatureFlagService,
4033
) : Presenter<KnockRequestsBannerState> {
4134
@Composable
4235
override fun present(): KnockRequestsBannerState {
@@ -48,15 +41,12 @@ class KnockRequestsBannerPresenter @Inject constructor(
4841
}
4942
}.collectAsState()
5043

51-
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
52-
val canAccept by room.canInviteAsState(syncUpdateFlow.value)
53-
val canHandleKnockRequests by room.canHandleKnockRequestsAsState(syncUpdateFlow.value)
44+
val permissions by knockRequestsService.permissionsFlow.collectAsState()
5445
val showAcceptError = remember { mutableStateOf(false) }
55-
val isKnockRequestsEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock).collectAsState(false)
5646

5747
val shouldShowBanner by remember {
5848
derivedStateOf {
59-
isKnockRequestsEnabled && canHandleKnockRequests && knockRequests.isNotEmpty()
49+
permissions.canHandle && knockRequests.isNotEmpty()
6050
}
6151
}
6252

@@ -79,7 +69,7 @@ class KnockRequestsBannerPresenter @Inject constructor(
7969
return KnockRequestsBannerState(
8070
knockRequests = knockRequests,
8171
displayAcceptError = showAcceptError.value,
82-
canAccept = canAccept,
72+
canAccept = permissions.canAccept,
8373
isVisible = shouldShowBanner,
8474
eventSink = ::handleEvents,
8575
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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.data
9+
10+
import io.element.android.libraries.matrix.api.room.MatrixRoom
11+
import io.element.android.libraries.matrix.api.room.powerlevels.canBan
12+
import io.element.android.libraries.matrix.api.room.powerlevels.canInvite
13+
import io.element.android.libraries.matrix.api.room.powerlevels.canKick
14+
import kotlinx.coroutines.flow.Flow
15+
import kotlinx.coroutines.flow.map
16+
17+
data class KnockRequestPermissions(
18+
val canAccept: Boolean,
19+
val canDecline: Boolean,
20+
val canBan: Boolean,
21+
) {
22+
val canHandle = canAccept || canDecline || canBan
23+
}
24+
25+
fun MatrixRoom.knockRequestPermissionsFlow(): Flow<KnockRequestPermissions> {
26+
return syncUpdateFlow.map {
27+
val canAccept = canInvite().getOrDefault(false)
28+
val canDecline = canKick().getOrDefault(false)
29+
val canBan = canBan().getOrDefault(false)
30+
KnockRequestPermissions(canAccept, canDecline, canBan)
31+
}
32+
}

features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestWrapper.kt

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,23 @@ import io.element.android.libraries.matrix.api.core.UserId
1212
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
1313

1414
class KnockRequestWrapper(
15-
private val knockRequest: KnockRequest,
15+
private val inner: KnockRequest,
1616
dateFormatter: (Long?) -> String? = { null }
1717
) : KnockRequestPresentable {
18-
override val eventId: EventId = knockRequest.eventId
19-
override val userId: UserId = knockRequest.userId
20-
override val displayName: String? = knockRequest.displayName
21-
override val avatarUrl: String? = knockRequest.avatarUrl
22-
override val reason: String? = knockRequest.reason?.trim()
23-
override val formattedDate: String? = dateFormatter(knockRequest.timestamp)
18+
override val eventId: EventId = inner.eventId
19+
override val userId: UserId = inner.userId
20+
override val displayName: String? = inner.displayName
21+
override val avatarUrl: String? = inner.avatarUrl
22+
override val reason: String? = inner.reason?.trim()
23+
override val formattedDate: String? = dateFormatter(inner.timestamp)
2424

25-
val isSeen: Boolean = knockRequest.isSeen
25+
val isSeen: Boolean = inner.isSeen
2626

27-
suspend fun accept(): Result<Unit> = knockRequest.accept()
27+
suspend fun accept(): Result<Unit> = inner.accept()
2828

29-
suspend fun decline(reason: String?): Result<Unit> = knockRequest.decline(reason)
29+
suspend fun decline(reason: String?): Result<Unit> = inner.decline(reason)
3030

31-
suspend fun declineAndBan(reason: String?): Result<Unit> = knockRequest.declineAndBan(reason)
31+
suspend fun declineAndBan(reason: String?): Result<Unit> = inner.declineAndBan(reason)
3232

33-
suspend fun markAsSeen(): Result<Unit> = knockRequest.markAsSeen()
33+
suspend fun markAsSeen(): Result<Unit> = inner.markAsSeen()
3434
}

features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsModule.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,21 @@ import dagger.Module
1212
import dagger.Provides
1313
import io.element.android.libraries.di.RoomScope
1414
import io.element.android.libraries.di.SingleIn
15+
import io.element.android.libraries.featureflag.api.FeatureFlagService
16+
import io.element.android.libraries.featureflag.api.FeatureFlags
1517
import io.element.android.libraries.matrix.api.room.MatrixRoom
1618

1719
@Module
1820
@ContributesTo(RoomScope::class)
1921
object KnockRequestsModule {
2022
@Provides
2123
@SingleIn(RoomScope::class)
22-
fun knockRequestsService(room: MatrixRoom): KnockRequestsService {
23-
return KnockRequestsService(room.knockRequestsFlow, room.roomCoroutineScope)
24+
fun knockRequestsService(room: MatrixRoom, featureFlagService: FeatureFlagService): KnockRequestsService {
25+
return KnockRequestsService(
26+
knockRequestsFlow = room.knockRequestsFlow,
27+
permissionsFlow = room.knockRequestPermissionsFlow(),
28+
isKnockFeatureEnabledFlow = featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock),
29+
coroutineScope = room.roomCoroutineScope
30+
)
2431
}
2532
}

features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package io.element.android.features.knockrequests.impl.data
1010
import io.element.android.libraries.architecture.AsyncData
1111
import io.element.android.libraries.matrix.api.core.EventId
1212
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
13+
import kotlinx.collections.immutable.persistentListOf
1314
import kotlinx.collections.immutable.toImmutableList
1415
import kotlinx.coroutines.CoroutineScope
1516
import kotlinx.coroutines.async
@@ -19,27 +20,40 @@ import kotlinx.coroutines.flow.MutableStateFlow
1920
import kotlinx.coroutines.flow.SharingStarted
2021
import kotlinx.coroutines.flow.combine
2122
import kotlinx.coroutines.flow.getAndUpdate
22-
import kotlinx.coroutines.flow.map
2323
import kotlinx.coroutines.flow.stateIn
2424
import kotlinx.coroutines.supervisorScope
2525

2626
class KnockRequestsService(
2727
knockRequestsFlow: Flow<List<KnockRequest>>,
28+
permissionsFlow: Flow<KnockRequestPermissions>,
29+
isKnockFeatureEnabledFlow: Flow<Boolean>,
2830
coroutineScope: CoroutineScope,
2931
) {
3032
// Keep track of the knock requests that have been handled, so we don't have to wait for sync to remove them.
3133
private val handledKnockRequestIds = MutableStateFlow<Set<EventId>>(emptySet())
3234

3335
val knockRequestsFlow = combine(
34-
knockRequestsFlow.wrapped(),
36+
isKnockFeatureEnabledFlow,
37+
knockRequestsFlow,
3538
handledKnockRequestIds,
36-
) { knockRequests, handledKnockIds ->
37-
val presentableKnockRequests = knockRequests
38-
.filter { it.eventId !in handledKnockIds }
39-
.toImmutableList()
40-
AsyncData.Success(presentableKnockRequests)
39+
) { isKnockEnabled, knockRequests, handledKnockIds ->
40+
if (!isKnockEnabled) {
41+
AsyncData.Success(persistentListOf())
42+
} else {
43+
val presentableKnockRequests = knockRequests
44+
.filter { it.eventId !in handledKnockIds }
45+
.map { inner -> KnockRequestWrapper(inner) }
46+
.toImmutableList()
47+
AsyncData.Success(presentableKnockRequests)
48+
}
4149
}.stateIn(coroutineScope, SharingStarted.Lazily, AsyncData.Loading())
4250

51+
val permissionsFlow = permissionsFlow.stateIn(
52+
scope = coroutineScope,
53+
started = SharingStarted.Lazily,
54+
initialValue = KnockRequestPermissions(canAccept = false, canDecline = false, canBan = false)
55+
)
56+
4357
private fun knockRequestsList() = knockRequestsFlow.value.dataOrNull().orEmpty()
4458

4559
private fun getKnockRequestById(eventId: EventId): KnockRequestWrapper? {
@@ -128,8 +142,4 @@ class KnockRequestsService(
128142
}
129143

130144
private fun knockRequestNotFoundResult() = Result.failure<Unit>(IllegalArgumentException("Knock request not found"))
131-
132-
private fun Flow<List<KnockRequest>>.wrapped() = map { knockRequests ->
133-
knockRequests.map { KnockRequestWrapper(it) }
134-
}
135145
}

features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,19 @@ import io.element.android.features.knockrequests.impl.data.KnockRequestsService
2020
import io.element.android.libraries.architecture.AsyncAction
2121
import io.element.android.libraries.architecture.Presenter
2222
import io.element.android.libraries.architecture.runUpdatingState
23-
import io.element.android.libraries.matrix.api.room.MatrixRoom
24-
import io.element.android.libraries.matrix.ui.room.canBanAsState
25-
import io.element.android.libraries.matrix.ui.room.canInviteAsState
26-
import io.element.android.libraries.matrix.ui.room.canKickAsState
2723
import kotlinx.coroutines.CoroutineScope
2824
import kotlinx.coroutines.launch
2925
import javax.inject.Inject
3026

3127
class KnockRequestsListPresenter @Inject constructor(
32-
private val room: MatrixRoom,
3328
private val knockRequestsService: KnockRequestsService,
3429
) : Presenter<KnockRequestsListState> {
3530
@Composable
3631
override fun present(): KnockRequestsListState {
3732
val asyncAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
3833
var actionTarget by remember { mutableStateOf<KnockRequestsActionTarget>(KnockRequestsActionTarget.None) }
3934

40-
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
41-
val canBan by room.canBanAsState(syncUpdateFlow.value)
42-
val canDecline by room.canKickAsState(syncUpdateFlow.value)
43-
val canAccept by room.canInviteAsState(syncUpdateFlow.value)
44-
35+
val permissions by knockRequestsService.permissionsFlow.collectAsState()
4536
val knockRequests by knockRequestsService.knockRequestsFlow.collectAsState()
4637

4738
val coroutineScope = rememberCoroutineScope()
@@ -79,10 +70,8 @@ class KnockRequestsListPresenter @Inject constructor(
7970
return KnockRequestsListState(
8071
knockRequests = knockRequests,
8172
actionTarget = actionTarget,
73+
permissions = permissions,
8274
asyncAction = asyncAction.value,
83-
canAccept = canAccept,
84-
canDecline = canDecline,
85-
canBan = canBan,
8675
eventSink = ::handleEvents
8776
)
8877
}

features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package io.element.android.features.knockrequests.impl.list
99

1010
import androidx.compose.runtime.Immutable
11+
import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions
1112
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
1213
import io.element.android.libraries.architecture.AsyncAction
1314
import io.element.android.libraries.architecture.AsyncData
@@ -17,12 +18,10 @@ data class KnockRequestsListState(
1718
val knockRequests: AsyncData<ImmutableList<KnockRequestPresentable>>,
1819
val actionTarget: KnockRequestsActionTarget,
1920
val asyncAction: AsyncAction<Unit>,
20-
val canAccept: Boolean,
21-
val canDecline: Boolean,
22-
val canBan: Boolean,
21+
val permissions: KnockRequestPermissions,
2322
val eventSink: (KnockRequestsListEvents) -> Unit,
2423
) {
25-
val canAcceptAll = canAccept && knockRequests is AsyncData.Success && knockRequests.data.size > 1
24+
val canAcceptAll = permissions.canAccept && knockRequests is AsyncData.Success && knockRequests.data.size > 1
2625
}
2726

2827
@Immutable

features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package io.element.android.features.knockrequests.impl.list
99

1010
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
11+
import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions
1112
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
1213
import io.element.android.features.knockrequests.impl.data.aKnockRequest
1314
import io.element.android.libraries.architecture.AsyncAction
@@ -82,7 +83,11 @@ open class KnockRequestsListStateProvider : PreviewParameterProvider<KnockReques
8283
aKnockRequest()
8384
)
8485
),
85-
canAccept = false,
86+
permissions = KnockRequestPermissions(
87+
canAccept = false,
88+
canDecline = true,
89+
canBan = true,
90+
),
8691
actionTarget = KnockRequestsActionTarget.AcceptAll,
8792
asyncAction = AsyncAction.Failure(Throwable("Failed to accept all")),
8893
),
@@ -92,24 +97,35 @@ open class KnockRequestsListStateProvider : PreviewParameterProvider<KnockReques
9297
aKnockRequest()
9398
)
9499
),
95-
canDecline = false,
100+
permissions = KnockRequestPermissions(
101+
canAccept = true,
102+
canDecline = false,
103+
canBan = true,
104+
),
96105
),
97106
aKnockRequestsListState(
98107
knockRequests = AsyncData.Success(
99108
persistentListOf(
100109
aKnockRequest()
101110
)
102111
),
103-
canAccept = false,
104-
canDecline = false,
112+
permissions = KnockRequestPermissions(
113+
canAccept = false,
114+
canDecline = false,
115+
canBan = true,
116+
),
105117
),
106118
aKnockRequestsListState(
107119
knockRequests = AsyncData.Success(
108120
persistentListOf(
109121
aKnockRequest()
110122
)
111123
),
112-
canBan = false,
124+
permissions = KnockRequestPermissions(
125+
canAccept = true,
126+
canDecline = true,
127+
canBan = false,
128+
),
113129
),
114130
)
115131
}
@@ -118,16 +134,16 @@ fun aKnockRequestsListState(
118134
knockRequests: AsyncData<ImmutableList<KnockRequestPresentable>> = AsyncData.Success(persistentListOf()),
119135
actionTarget: KnockRequestsActionTarget = KnockRequestsActionTarget.None,
120136
asyncAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
121-
canAccept: Boolean = true,
122-
canDecline: Boolean = true,
123-
canBan: Boolean = true,
137+
permissions: KnockRequestPermissions = KnockRequestPermissions(
138+
canAccept = true,
139+
canDecline = true,
140+
canBan = true,
141+
),
124142
eventSink: (KnockRequestsListEvents) -> Unit = {},
125143
) = KnockRequestsListState(
126144
knockRequests = knockRequests,
127145
actionTarget = actionTarget,
128146
asyncAction = asyncAction,
129-
canAccept = canAccept,
130-
canDecline = canDecline,
131-
canBan = canBan,
147+
permissions = permissions,
132148
eventSink = eventSink,
133149
)

features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,9 @@ private fun KnockRequestsListContent(
126126
} else {
127127
KnockRequestsList(
128128
knockRequests = knockRequests,
129-
canAccept = state.canAccept,
130-
canDecline = state.canDecline,
131-
canBan = state.canBan,
129+
canAccept = state.permissions.canAccept,
130+
canDecline = state.permissions.canDecline,
131+
canBan = state.permissions.canBan,
132132
onAcceptClick = ::onAcceptClick,
133133
onDeclineClick = ::onDeclineClick,
134134
onBanClick = ::onBanClick,

0 commit comments

Comments
 (0)