Avoid using the Activity, and use eventSink instead of lambda in states.

This commit is contained in:
Benoit Marty 2025-06-10 16:01:19 +02:00
parent a306fbd0e7
commit dc2d5a253a
8 changed files with 45 additions and 36 deletions

View file

@ -7,13 +7,13 @@
package io.element.android.features.roomlist.impl.components package io.element.android.features.roomlist.impl.components
import androidx.activity.compose.LocalActivity
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import io.element.android.libraries.designsystem.components.Announcement import io.element.android.libraries.designsystem.components.Announcement
import io.element.android.libraries.designsystem.components.AnnouncementType import io.element.android.libraries.designsystem.components.AnnouncementType
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.push.api.battery.BatteryOptimizationEvents
import io.element.android.libraries.push.api.battery.BatteryOptimizationState import io.element.android.libraries.push.api.battery.BatteryOptimizationState
import io.element.android.libraries.push.api.battery.aBatteryOptimizationState import io.element.android.libraries.push.api.battery.aBatteryOptimizationState
@ -22,7 +22,6 @@ internal fun BatteryOptimizationBanner(
state: BatteryOptimizationState, state: BatteryOptimizationState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val activity = LocalActivity.current
Announcement( Announcement(
modifier = modifier.roomListBannerPadding(), modifier = modifier.roomListBannerPadding(),
// TODO Localazy // TODO Localazy
@ -30,8 +29,8 @@ internal fun BatteryOptimizationBanner(
description = "To be sure to receive all the notifications, it can help to disable the battery optimization for this application.", description = "To be sure to receive all the notifications, it can help to disable the battery optimization for this application.",
type = AnnouncementType.Actionable( type = AnnouncementType.Actionable(
actionText = "Yes, disable", actionText = "Yes, disable",
onActionClick = { state.openSettings(activity) }, onActionClick = { state.eventSink(BatteryOptimizationEvents.DoAction) },
onDismissClick = state.dismiss, onDismissClick = { state.eventSink(BatteryOptimizationEvents.Dismiss) },
), ),
) )
} }

View file

@ -0,0 +1,13 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.push.api.battery
sealed interface BatteryOptimizationEvents {
data object Dismiss : BatteryOptimizationEvents
data object DoAction : BatteryOptimizationEvents
}

View file

@ -7,10 +7,7 @@
package io.element.android.libraries.push.api.battery package io.element.android.libraries.push.api.battery
import android.app.Activity
data class BatteryOptimizationState( data class BatteryOptimizationState(
val shouldDisplayBanner: Boolean, val shouldDisplayBanner: Boolean,
val dismiss: () -> Unit, val eventSink: (BatteryOptimizationEvents) -> Unit,
val openSettings: (Activity?) -> Unit,
) )

View file

@ -7,14 +7,10 @@
package io.element.android.libraries.push.api.battery package io.element.android.libraries.push.api.battery
import android.app.Activity
fun aBatteryOptimizationState( fun aBatteryOptimizationState(
shouldDisplayBanner: Boolean = false, shouldDisplayBanner: Boolean = false,
dismiss: () -> Unit = {}, eventSink: (BatteryOptimizationEvents) -> Unit = {},
openSettings: (Activity?) -> Unit = {},
) = BatteryOptimizationState( ) = BatteryOptimizationState(
shouldDisplayBanner = shouldDisplayBanner, shouldDisplayBanner = shouldDisplayBanner,
dismiss = dismiss, eventSink = eventSink,
openSettings = openSettings,
) )

View file

@ -8,7 +8,6 @@
package io.element.android.libraries.push.impl.battery package io.element.android.libraries.push.impl.battery
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -19,6 +18,7 @@ import androidx.core.net.toUri
import com.squareup.anvil.annotations.ContributesBinding import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.ApplicationContext
import io.element.android.services.toolbox.api.intent.ExternalIntentLauncher
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -39,16 +39,16 @@ interface BatteryOptimization {
* This will open the system settings where the user can disable battery optimizations. * This will open the system settings where the user can disable battery optimizations.
* See https://developer.android.com/training/monitoring-device-state/doze-standby#exemption-cases * See https://developer.android.com/training/monitoring-device-state/doze-standby#exemption-cases
* *
* @param activity The activity from which to start the settings intent.
* @return true if the intent was successfully started, false if the activity was not found * @return true if the intent was successfully started, false if the activity was not found
*/ */
fun requestDisablingBatteryOptimization(activity: Activity?): Boolean fun requestDisablingBatteryOptimization(): Boolean
} }
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
class AndroidBatteryOptimization @Inject constructor( class AndroidBatteryOptimization @Inject constructor(
@ApplicationContext @ApplicationContext
private val context: Context, private val context: Context,
private val externalIntentLauncher: ExternalIntentLauncher,
) : BatteryOptimization { ) : BatteryOptimization {
override fun isIgnoringBatteryOptimizations(): Boolean { override fun isIgnoringBatteryOptimizations(): Boolean {
return context.getSystemService<PowerManager>() return context.getSystemService<PowerManager>()
@ -56,13 +56,12 @@ class AndroidBatteryOptimization @Inject constructor(
} }
@SuppressLint("BatteryLife") @SuppressLint("BatteryLife")
override fun requestDisablingBatteryOptimization(activity: Activity?): Boolean { override fun requestDisablingBatteryOptimization(): Boolean {
activity ?: return false
val intent = Intent() val intent = Intent()
intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
intent.data = ("package:" + context.packageName).toUri() intent.data = ("package:" + context.packageName).toUri()
return try { return try {
activity.startActivity(intent) externalIntentLauncher.launch(intent)
true true
} catch (exception: ActivityNotFoundException) { } catch (exception: ActivityNotFoundException) {
Timber.w(exception, "Cannot request ignoring battery optimizations.") Timber.w(exception, "Cannot request ignoring battery optimizations.")

View file

@ -16,6 +16,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.LifecycleResumeEffect
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.push.api.battery.BatteryOptimizationEvents
import io.element.android.libraries.push.api.battery.BatteryOptimizationState import io.element.android.libraries.push.api.battery.BatteryOptimizationState
import io.element.android.libraries.push.impl.push.MutableBatteryOptimizationStore import io.element.android.libraries.push.impl.push.MutableBatteryOptimizationStore
import io.element.android.libraries.push.impl.store.PushDataStore import io.element.android.libraries.push.impl.store.PushDataStore
@ -45,22 +46,26 @@ class BatteryOptimizationPresenter @Inject constructor(
onPauseOrDispose {} onPauseOrDispose {}
} }
return BatteryOptimizationState( fun handleEvents(event: BatteryOptimizationEvents) {
shouldDisplayBanner = localShouldDisplayBanner && storeShouldDisplayBanner && !isSystemIgnoringBatteryOptimizations, when (event) {
dismiss = { BatteryOptimizationEvents.Dismiss -> coroutineScope.launch {
coroutineScope.launch {
mutableBatteryOptimizationStore.onOptimizationBannerDismissed() mutableBatteryOptimizationStore.onOptimizationBannerDismissed()
} }
}, BatteryOptimizationEvents.DoAction -> {
openSettings = { activity -> isRequestSent = true
isRequestSent = true if (batteryOptimization.requestDisablingBatteryOptimization().not()) {
if (batteryOptimization.requestDisablingBatteryOptimization(activity).not()) { // If not able to perform the request, ensure that we do not display the banner again
// If not able to perform the request, ensure that we do not display the banner again coroutineScope.launch {
coroutineScope.launch { mutableBatteryOptimizationStore.onOptimizationBannerDismissed()
mutableBatteryOptimizationStore.onOptimizationBannerDismissed() }
} }
} }
} }
}
return BatteryOptimizationState(
shouldDisplayBanner = localShouldDisplayBanner && storeShouldDisplayBanner && !isSystemIgnoringBatteryOptimizations,
eventSink = ::handleEvents,
) )
} }
} }

View file

@ -9,6 +9,7 @@ package io.element.android.libraries.push.impl.battery
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.push.api.battery.BatteryOptimizationEvents
import io.element.android.libraries.push.impl.push.FakeMutableBatteryOptimizationStore import io.element.android.libraries.push.impl.push.FakeMutableBatteryOptimizationStore
import io.element.android.libraries.push.impl.push.MutableBatteryOptimizationStore import io.element.android.libraries.push.impl.push.MutableBatteryOptimizationStore
import io.element.android.libraries.push.impl.store.InMemoryPushDataStore import io.element.android.libraries.push.impl.store.InMemoryPushDataStore
@ -96,7 +97,7 @@ class BatteryOptimizationPresenterTest {
assertThat(initialState.shouldDisplayBanner).isFalse() assertThat(initialState.shouldDisplayBanner).isFalse()
val displayedItem = awaitItem() val displayedItem = awaitItem()
assertThat(displayedItem.shouldDisplayBanner).isTrue() assertThat(displayedItem.shouldDisplayBanner).isTrue()
displayedItem.dismiss() displayedItem.eventSink(BatteryOptimizationEvents.Dismiss)
onOptimizationBannerDismissedResult.assertions().isCalledOnce() onOptimizationBannerDismissedResult.assertions().isCalledOnce()
} }
} }
@ -122,7 +123,7 @@ class BatteryOptimizationPresenterTest {
assertThat(initialState.shouldDisplayBanner).isFalse() assertThat(initialState.shouldDisplayBanner).isFalse()
val displayedItem = awaitItem() val displayedItem = awaitItem()
assertThat(displayedItem.shouldDisplayBanner).isTrue() assertThat(displayedItem.shouldDisplayBanner).isTrue()
displayedItem.openSettings(null) displayedItem.eventSink(BatteryOptimizationEvents.DoAction)
requestDisablingBatteryOptimizationResult.assertions().isCalledOnce() requestDisablingBatteryOptimizationResult.assertions().isCalledOnce()
onOptimizationBannerDismissedResult.assertions().isCalledOnce() onOptimizationBannerDismissedResult.assertions().isCalledOnce()
} }
@ -148,7 +149,7 @@ class BatteryOptimizationPresenterTest {
assertThat(initialState.shouldDisplayBanner).isFalse() assertThat(initialState.shouldDisplayBanner).isFalse()
val displayedItem = awaitItem() val displayedItem = awaitItem()
assertThat(displayedItem.shouldDisplayBanner).isTrue() assertThat(displayedItem.shouldDisplayBanner).isTrue()
displayedItem.openSettings(null) displayedItem.eventSink(BatteryOptimizationEvents.DoAction)
requestDisablingBatteryOptimizationResult.assertions().isCalledOnce() requestDisablingBatteryOptimizationResult.assertions().isCalledOnce()
batteryOptimization.isIgnoringBatteryOptimizationsResult = true batteryOptimization.isIgnoringBatteryOptimizationsResult = true
lifeCycleOwner.givenState(Lifecycle.State.RESUMED) lifeCycleOwner.givenState(Lifecycle.State.RESUMED)

View file

@ -7,7 +7,6 @@
package io.element.android.libraries.push.impl.battery package io.element.android.libraries.push.impl.battery
import android.app.Activity
import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaError
class FakeBatteryOptimization( class FakeBatteryOptimization(
@ -18,7 +17,7 @@ class FakeBatteryOptimization(
return isIgnoringBatteryOptimizationsResult return isIgnoringBatteryOptimizationsResult
} }
override fun requestDisablingBatteryOptimization(activity: Activity?): Boolean { override fun requestDisablingBatteryOptimization(): Boolean {
return requestDisablingBatteryOptimizationResult() return requestDisablingBatteryOptimizationResult()
} }
} }