mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 21:32:34 +00:00
Hide full screen intent banner as soon as the permission is granted
The permission is granted from the system settings, outside of the app, so nothing triggered a recomposition of FullScreenIntentPermissionsPresenter when the user came back. The "Enhance your call experience" banner kept being displayed until something else caused a recomposition. Check the permission again with LifecycleResumeEffect, as done in BatteryOptimizationPresenter, and add a test for this case.
This commit is contained in:
+11
-1
@@ -15,11 +15,15 @@ import android.provider.Settings
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||||
import androidx.datastore.preferences.core.edit
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.lifecycle.compose.LifecycleResumeEffect
|
||||||
import dev.zacsweers.metro.AppScope
|
import dev.zacsweers.metro.AppScope
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import dev.zacsweers.metro.SingleIn
|
import dev.zacsweers.metro.SingleIn
|
||||||
@@ -61,9 +65,15 @@ class FullScreenIntentPermissionsPresenter(
|
|||||||
@Composable
|
@Composable
|
||||||
override fun present(): FullScreenIntentPermissionsState {
|
override fun present(): FullScreenIntentPermissionsState {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val isGranted = notificationManagerCompat.canUseFullScreenIntent()
|
var isGranted by remember { mutableStateOf(notificationManagerCompat.canUseFullScreenIntent()) }
|
||||||
val isBannerDismissed by isFullScreenIntentBannerDismissed.collectAsState(initial = true)
|
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) {
|
fun handleEvent(event: FullScreenIntentPermissionsEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
FullScreenIntentPermissionsEvent.Dismiss -> coroutineScope.launch {
|
FullScreenIntentPermissionsEvent.Dismiss -> coroutineScope.launch {
|
||||||
|
|||||||
+30
-18
@@ -11,9 +11,7 @@ package io.element.android.libraries.fullscreenintent.test
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import app.cash.molecule.RecompositionMode
|
import androidx.lifecycle.Lifecycle
|
||||||
import app.cash.molecule.moleculeFlow
|
|
||||||
import app.cash.turbine.test
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsEvent
|
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.api.intent.ExternalIntentLauncher
|
||||||
import io.element.android.services.toolbox.test.intent.FakeExternalIntentLauncher
|
import io.element.android.services.toolbox.test.intent.FakeExternalIntentLauncher
|
||||||
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
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.WarmUpRule
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
|
import io.element.android.tests.testutils.testWithLifecycleOwner
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
@@ -45,9 +45,7 @@ class FullScreenIntentPermissionsPresenterTest {
|
|||||||
every { canUseFullScreenIntent() } returns false
|
every { canUseFullScreenIntent() } returns false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.testWithLifecycleOwner {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val initialItem = awaitItem()
|
val initialItem = awaitItem()
|
||||||
assertThat(initialItem.shouldDisplayBanner).isTrue()
|
assertThat(initialItem.shouldDisplayBanner).isTrue()
|
||||||
@@ -61,21 +59,39 @@ class FullScreenIntentPermissionsPresenterTest {
|
|||||||
every { canUseFullScreenIntent() } returns true
|
every { canUseFullScreenIntent() } returns true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.testWithLifecycleOwner {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val initialItem = awaitItem()
|
val initialItem = awaitItem()
|
||||||
assertThat(initialItem.shouldDisplayBanner).isFalse()
|
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
|
@Test
|
||||||
fun `dismissFullScreenIntentBanner - makes shouldDisplay false`() = runTest {
|
fun `dismissFullScreenIntentBanner - makes shouldDisplay false`() = runTest {
|
||||||
val presenter = createPresenter()
|
val presenter = createPresenter()
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.testWithLifecycleOwner {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val loadedItem = awaitItem()
|
val loadedItem = awaitItem()
|
||||||
loadedItem.eventSink(FullScreenIntentPermissionsEvent.Dismiss)
|
loadedItem.eventSink(FullScreenIntentPermissionsEvent.Dismiss)
|
||||||
@@ -89,9 +105,7 @@ class FullScreenIntentPermissionsPresenterTest {
|
|||||||
val launchLambda = lambdaRecorder<Intent, Unit> { _ -> }
|
val launchLambda = lambdaRecorder<Intent, Unit> { _ -> }
|
||||||
val externalIntentLauncher = FakeExternalIntentLauncher(launchLambda)
|
val externalIntentLauncher = FakeExternalIntentLauncher(launchLambda)
|
||||||
val presenter = createPresenter(externalIntentLauncher = externalIntentLauncher)
|
val presenter = createPresenter(externalIntentLauncher = externalIntentLauncher)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.testWithLifecycleOwner {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val loadedItem = awaitItem()
|
val loadedItem = awaitItem()
|
||||||
loadedItem.eventSink(FullScreenIntentPermissionsEvent.OpenSettings)
|
loadedItem.eventSink(FullScreenIntentPermissionsEvent.OpenSettings)
|
||||||
@@ -108,9 +122,7 @@ class FullScreenIntentPermissionsPresenterTest {
|
|||||||
buildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(Build.VERSION_CODES.Q),
|
buildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(Build.VERSION_CODES.Q),
|
||||||
externalIntentLauncher = externalIntentLauncher,
|
externalIntentLauncher = externalIntentLauncher,
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.testWithLifecycleOwner {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val loadedItem = awaitItem()
|
val loadedItem = awaitItem()
|
||||||
loadedItem.eventSink(FullScreenIntentPermissionsEvent.OpenSettings)
|
loadedItem.eventSink(FullScreenIntentPermissionsEvent.OpenSettings)
|
||||||
|
|||||||
Reference in New Issue
Block a user