Skip to content

Commit 7b1f458

Browse files
authored
Merge pull request #4067 from element-hq/feature/fga/knock_requests_sdk
feat(knock requests) : branch logic for handling knock requests
2 parents 454c3ed + f4f669c commit 7b1f458

File tree

66 files changed

+1877
-316
lines changed

Some content is hidden

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

66 files changed

+1877
-316
lines changed

features/knockrequests/impl/build.gradle.kts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ plugins {
1414

1515
android {
1616
namespace = "io.element.android.features.knockrequests.impl"
17+
testOptions {
18+
unitTests {
19+
isIncludeAndroidResources = true
20+
}
21+
}
1722
}
1823

1924
setupAnvil()
@@ -26,11 +31,17 @@ dependencies {
2631
implementation(projects.libraries.matrixui)
2732
implementation(projects.libraries.uiStrings)
2833
implementation(projects.libraries.designsystem)
34+
implementation(projects.libraries.featureflag.api)
2935

3036
testImplementation(libs.test.junit)
3137
testImplementation(libs.coroutines.test)
3238
testImplementation(libs.molecule.runtime)
39+
testImplementation(libs.test.robolectric)
3340
testImplementation(libs.test.truth)
3441
testImplementation(libs.test.turbine)
3542
testImplementation(projects.libraries.matrix.test)
43+
testImplementation(projects.tests.testutils)
44+
testImplementation(libs.androidx.compose.ui.test.junit)
45+
testImplementation(projects.libraries.featureflag.test)
46+
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
3647
}

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

Lines changed: 0 additions & 45 deletions
This file was deleted.

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

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,85 @@
88
package io.element.android.features.knockrequests.impl.banner
99

1010
import androidx.compose.runtime.Composable
11+
import androidx.compose.runtime.MutableState
12+
import androidx.compose.runtime.collectAsState
13+
import androidx.compose.runtime.derivedStateOf
1114
import androidx.compose.runtime.getValue
1215
import androidx.compose.runtime.mutableStateOf
1316
import androidx.compose.runtime.remember
14-
import androidx.compose.runtime.setValue
15-
import io.element.android.libraries.architecture.AsyncAction
17+
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
18+
import io.element.android.features.knockrequests.impl.data.KnockRequestsService
1619
import io.element.android.libraries.architecture.Presenter
17-
import kotlinx.collections.immutable.persistentListOf
20+
import io.element.android.libraries.core.coroutine.mapState
21+
import io.element.android.libraries.core.extensions.firstIfSingle
22+
import kotlinx.collections.immutable.toImmutableList
23+
import kotlinx.coroutines.CoroutineScope
24+
import kotlinx.coroutines.delay
25+
import kotlinx.coroutines.launch
1826
import javax.inject.Inject
1927

20-
class KnockRequestsBannerPresenter @Inject constructor() : Presenter<KnockRequestsBannerState> {
28+
private const val ACCEPT_ERROR_DISPLAY_DURATION = 1500L
29+
30+
class KnockRequestsBannerPresenter @Inject constructor(
31+
private val knockRequestsService: KnockRequestsService,
32+
private val appCoroutineScope: CoroutineScope,
33+
) : Presenter<KnockRequestsBannerState> {
2134
@Composable
2235
override fun present(): KnockRequestsBannerState {
23-
var shouldShowBanner by remember { mutableStateOf(false) }
36+
val knockRequests by remember {
37+
knockRequestsService.knockRequestsFlow.mapState { knockRequests ->
38+
knockRequests.dataOrNull().orEmpty()
39+
.filter { !it.isSeen }
40+
.toImmutableList()
41+
}
42+
}.collectAsState()
43+
44+
val permissions by knockRequestsService.permissionsFlow.collectAsState()
45+
val showAcceptError = remember { mutableStateOf(false) }
46+
47+
val shouldShowBanner by remember {
48+
derivedStateOf {
49+
permissions.canHandle && knockRequests.isNotEmpty()
50+
}
51+
}
2452

2553
fun handleEvents(event: KnockRequestsBannerEvents) {
2654
when (event) {
27-
is KnockRequestsBannerEvents.AcceptSingleRequest -> Unit
55+
is KnockRequestsBannerEvents.AcceptSingleRequest -> {
56+
appCoroutineScope.acceptSingleKnockRequest(
57+
knockRequests = knockRequests,
58+
displayAcceptError = showAcceptError,
59+
)
60+
}
2861
is KnockRequestsBannerEvents.Dismiss -> {
29-
shouldShowBanner = false
62+
appCoroutineScope.launch {
63+
knockRequestsService.markAllKnockRequestsAsSeen()
64+
}
3065
}
3166
}
3267
}
3368

3469
return KnockRequestsBannerState(
35-
knockRequests = persistentListOf(),
36-
acceptAction = AsyncAction.Uninitialized,
37-
canAccept = false,
70+
knockRequests = knockRequests,
71+
displayAcceptError = showAcceptError.value,
72+
canAccept = permissions.canAccept,
3873
isVisible = shouldShowBanner,
3974
eventSink = ::handleEvents,
4075
)
4176
}
77+
78+
private fun CoroutineScope.acceptSingleKnockRequest(
79+
knockRequests: List<KnockRequestPresentable>,
80+
displayAcceptError: MutableState<Boolean>,
81+
) = launch {
82+
val knockRequest = knockRequests.firstIfSingle()
83+
if (knockRequest != null) {
84+
knockRequestsService.acceptKnockRequest(knockRequest, optimistic = true)
85+
.onFailure {
86+
displayAcceptError.value = true
87+
delay(ACCEPT_ERROR_DISPLAY_DURATION)
88+
displayAcceptError.value = false
89+
}
90+
}
91+
}
4292
}

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,15 @@ package io.element.android.features.knockrequests.impl.banner
1010
import androidx.compose.runtime.Composable
1111
import androidx.compose.ui.res.pluralStringResource
1212
import androidx.compose.ui.res.stringResource
13-
import io.element.android.features.knockrequests.impl.KnockRequest
1413
import io.element.android.features.knockrequests.impl.R
15-
import io.element.android.features.knockrequests.impl.getBestName
16-
import io.element.android.libraries.architecture.AsyncAction
14+
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
1715
import io.element.android.libraries.core.extensions.firstIfSingle
1816
import kotlinx.collections.immutable.ImmutableList
1917

2018
data class KnockRequestsBannerState(
2119
val isVisible: Boolean,
22-
val knockRequests: ImmutableList<KnockRequest>,
23-
val acceptAction: AsyncAction<Unit>,
20+
val knockRequests: ImmutableList<KnockRequestPresentable>,
21+
val displayAcceptError: Boolean,
2422
val canAccept: Boolean,
2523
val eventSink: (KnockRequestsBannerEvents) -> Unit,
2624
) {

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

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@
88
package io.element.android.features.knockrequests.impl.banner
99

1010
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
11-
import io.element.android.features.knockrequests.impl.KnockRequest
12-
import io.element.android.features.knockrequests.impl.aKnockRequest
13-
import io.element.android.libraries.architecture.AsyncAction
11+
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
12+
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
1413
import kotlinx.collections.immutable.toImmutableList
1514

1615
class KnockRequestsBannerStateProvider : PreviewParameterProvider<KnockRequestsBannerState> {
@@ -19,7 +18,7 @@ class KnockRequestsBannerStateProvider : PreviewParameterProvider<KnockRequestsB
1918
aKnockRequestsBannerState(),
2019
aKnockRequestsBannerState(
2120
knockRequests = listOf(
22-
aKnockRequest(
21+
aKnockRequestPresentable(
2322
reason = "A very long reason that should probably be truncated, " +
2423
"but could be also expanded so you can see it over the lines, wow," +
2524
"very amazing reason, I know, right, I'm so good at writing reasons."
@@ -28,30 +27,27 @@ class KnockRequestsBannerStateProvider : PreviewParameterProvider<KnockRequestsB
2827
),
2928
aKnockRequestsBannerState(
3029
knockRequests = listOf(
31-
aKnockRequest(),
32-
aKnockRequest(displayName = "Alice")
30+
aKnockRequestPresentable(),
31+
aKnockRequestPresentable(displayName = "Alice")
3332
)
3433
),
3534
aKnockRequestsBannerState(
3635
knockRequests = listOf(
37-
aKnockRequest(),
38-
aKnockRequest(displayName = "Alice"),
39-
aKnockRequest(displayName = "Bob"),
40-
aKnockRequest(displayName = "Charlie")
36+
aKnockRequestPresentable(),
37+
aKnockRequestPresentable(displayName = "Alice"),
38+
aKnockRequestPresentable(displayName = "Bob"),
39+
aKnockRequestPresentable(displayName = "Charlie")
4140
)
4241
),
4342
aKnockRequestsBannerState(
4443
canAccept = false
4544
),
4645
aKnockRequestsBannerState(
47-
acceptAction = AsyncAction.Loading
48-
),
49-
aKnockRequestsBannerState(
50-
acceptAction = AsyncAction.Failure(Throwable("Failed to accept knock"))
46+
displayAcceptError = true
5147
),
5248
aKnockRequestsBannerState(
5349
knockRequests = listOf(
54-
aKnockRequest(
50+
aKnockRequestPresentable(
5551
displayName = "A_very_long_display_name_so_that_the_text_can_be_displayed_on_multiple_lines"
5652
)
5753
)
@@ -60,14 +56,14 @@ class KnockRequestsBannerStateProvider : PreviewParameterProvider<KnockRequestsB
6056
}
6157

6258
fun aKnockRequestsBannerState(
63-
knockRequests: List<KnockRequest> = listOf(aKnockRequest()),
64-
acceptAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
59+
knockRequests: List<KnockRequestPresentable> = listOf(aKnockRequestPresentable()),
60+
displayAcceptError: Boolean = false,
6561
canAccept: Boolean = true,
6662
isVisible: Boolean = true,
6763
eventSink: (KnockRequestsBannerEvents) -> Unit = {}
6864
) = KnockRequestsBannerState(
6965
knockRequests = knockRequests.toImmutableList(),
70-
acceptAction = acceptAction,
66+
displayAcceptError = displayAcceptError,
7167
canAccept = canAccept,
7268
isVisible = isVisible,
7369
eventSink = eventSink,

0 commit comments

Comments
 (0)