Merge pull request #7867 from bardiharborow/feature/full-screen-intent-banner-refresh

Hide full screen intent banner as soon as the permission is granted
This commit is contained in:
Benoit Marty
2026-10-06 11:46:15 +02:00
committed by GitHub
2 changed files with 41 additions and 19 deletions
@@ -15,11 +15,15 @@ import android.provider.Settings
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.core.app.NotificationManagerCompat
import androidx.core.net.toUri
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.lifecycle.compose.LifecycleResumeEffect
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
@@ -61,9 +65,15 @@ class FullScreenIntentPermissionsPresenter(
@Composable
override fun present(): FullScreenIntentPermissionsState {
val coroutineScope = rememberCoroutineScope()
val isGranted = notificationManagerCompat.canUseFullScreenIntent()
var isGranted by remember { mutableStateOf(notificationManagerCompat.canUseFullScreenIntent()) }
val isBannerDismissed by isFullScreenIntentBannerDismissed.collectAsState(initial = true)
// The permission can be granted from the system settings, so check it again when the user comes back to the app.
LifecycleResumeEffect(Unit) {
isGranted = notificationManagerCompat.canUseFullScreenIntent()
onPauseOrDispose {}
}
fun handleEvent(event: FullScreenIntentPermissionsEvent) {
when (event) {
FullScreenIntentPermissionsEvent.Dismiss -> coroutineScope.launch {
@@ -11,9 +11,7 @@ package io.element.android.libraries.fullscreenintent.test
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationManagerCompat
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import androidx.lifecycle.Lifecycle
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsEvent
@@ -23,8 +21,10 @@ import io.element.android.libraries.preferences.test.FakePreferenceDataStoreFact
import io.element.android.services.toolbox.api.intent.ExternalIntentLauncher
import io.element.android.services.toolbox.test.intent.FakeExternalIntentLauncher
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
import io.element.android.tests.testutils.FakeLifecycleOwner
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.testWithLifecycleOwner
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,9 +45,7 @@ class FullScreenIntentPermissionsPresenterTest {
every { canUseFullScreenIntent() } returns false
}
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.testWithLifecycleOwner {
skipItems(1)
val initialItem = awaitItem()
assertThat(initialItem.shouldDisplayBanner).isTrue()
@@ -61,21 +59,39 @@ class FullScreenIntentPermissionsPresenterTest {
every { canUseFullScreenIntent() } returns true
}
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.testWithLifecycleOwner {
skipItems(1)
val initialItem = awaitItem()
assertThat(initialItem.shouldDisplayBanner).isFalse()
}
}
@Test
fun `shouldDisplay - becomes false when permission is granted while the app is in background`() = runTest {
var canUseFullScreenIntent = false
val presenter = createPresenter(
notificationManagerCompat = mockk {
every { canUseFullScreenIntent() } answers { canUseFullScreenIntent }
}
)
val lifecycleOwner = FakeLifecycleOwner(Lifecycle.State.RESUMED)
presenter.testWithLifecycleOwner(lifecycleOwner) {
skipItems(1)
assertThat(awaitItem().shouldDisplayBanner).isTrue()
// User opens the system settings and grants the permission
lifecycleOwner.givenState(Lifecycle.State.STARTED)
canUseFullScreenIntent = true
lifecycleOwner.givenState(Lifecycle.State.RESUMED)
val finalItem = awaitItem()
assertThat(finalItem.permissionGranted).isTrue()
assertThat(finalItem.shouldDisplayBanner).isFalse()
}
}
@Test
fun `dismissFullScreenIntentBanner - makes shouldDisplay false`() = runTest {
val presenter = createPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.testWithLifecycleOwner {
skipItems(1)
val loadedItem = awaitItem()
loadedItem.eventSink(FullScreenIntentPermissionsEvent.Dismiss)
@@ -89,9 +105,7 @@ class FullScreenIntentPermissionsPresenterTest {
val launchLambda = lambdaRecorder<Intent, Unit> { _ -> }
val externalIntentLauncher = FakeExternalIntentLauncher(launchLambda)
val presenter = createPresenter(externalIntentLauncher = externalIntentLauncher)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.testWithLifecycleOwner {
skipItems(1)
val loadedItem = awaitItem()
loadedItem.eventSink(FullScreenIntentPermissionsEvent.OpenSettings)
@@ -108,9 +122,7 @@ class FullScreenIntentPermissionsPresenterTest {
buildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(Build.VERSION_CODES.Q),
externalIntentLauncher = externalIntentLauncher,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.testWithLifecycleOwner {
skipItems(1)
val loadedItem = awaitItem()
loadedItem.eventSink(FullScreenIntentPermissionsEvent.OpenSettings)