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
import androidx.activity.compose.LocalActivity
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import io.element.android.libraries.designsystem.components.Announcement
import io.element.android.libraries.designsystem.components.AnnouncementType
import io.element.android.libraries.designsystem.preview.ElementPreview
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.aBatteryOptimizationState
@@ -22,7 +22,6 @@ internal fun BatteryOptimizationBanner(
state: BatteryOptimizationState,
modifier: Modifier = Modifier,
) {
val activity = LocalActivity.current
Announcement(
modifier = modifier.roomListBannerPadding(),
// 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.",
type = AnnouncementType.Actionable(
actionText = "Yes, disable",
onActionClick = { state.openSettings(activity) },
onDismissClick = state.dismiss,
onActionClick = { state.eventSink(BatteryOptimizationEvents.DoAction) },
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
import android.app.Activity
data class BatteryOptimizationState(
val shouldDisplayBanner: Boolean,
val dismiss: () -> Unit,
val openSettings: (Activity?) -> Unit,
val eventSink: (BatteryOptimizationEvents) -> Unit,
)

View File

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

View File

@@ -8,7 +8,6 @@
package io.element.android.libraries.push.impl.battery
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
@@ -19,6 +18,7 @@ import androidx.core.net.toUri
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import io.element.android.services.toolbox.api.intent.ExternalIntentLauncher
import timber.log.Timber
import javax.inject.Inject
@@ -39,16 +39,16 @@ interface BatteryOptimization {
* 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
*
* @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
*/
fun requestDisablingBatteryOptimization(activity: Activity?): Boolean
fun requestDisablingBatteryOptimization(): Boolean
}
@ContributesBinding(AppScope::class)
class AndroidBatteryOptimization @Inject constructor(
@ApplicationContext
private val context: Context,
private val externalIntentLauncher: ExternalIntentLauncher,
) : BatteryOptimization {
override fun isIgnoringBatteryOptimizations(): Boolean {
return context.getSystemService<PowerManager>()
@@ -56,13 +56,12 @@ class AndroidBatteryOptimization @Inject constructor(
}
@SuppressLint("BatteryLife")
override fun requestDisablingBatteryOptimization(activity: Activity?): Boolean {
activity ?: return false
override fun requestDisablingBatteryOptimization(): Boolean {
val intent = Intent()
intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
intent.data = ("package:" + context.packageName).toUri()
return try {
activity.startActivity(intent)
externalIntentLauncher.launch(intent)
true
} catch (exception: ActivityNotFoundException) {
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.lifecycle.compose.LifecycleResumeEffect
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.impl.push.MutableBatteryOptimizationStore
import io.element.android.libraries.push.impl.store.PushDataStore
@@ -45,22 +46,26 @@ class BatteryOptimizationPresenter @Inject constructor(
onPauseOrDispose {}
}
return BatteryOptimizationState(
shouldDisplayBanner = localShouldDisplayBanner && storeShouldDisplayBanner && !isSystemIgnoringBatteryOptimizations,
dismiss = {
coroutineScope.launch {
fun handleEvents(event: BatteryOptimizationEvents) {
when (event) {
BatteryOptimizationEvents.Dismiss -> coroutineScope.launch {
mutableBatteryOptimizationStore.onOptimizationBannerDismissed()
}
},
openSettings = { activity ->
isRequestSent = true
if (batteryOptimization.requestDisablingBatteryOptimization(activity).not()) {
// If not able to perform the request, ensure that we do not display the banner again
coroutineScope.launch {
mutableBatteryOptimizationStore.onOptimizationBannerDismissed()
BatteryOptimizationEvents.DoAction -> {
isRequestSent = true
if (batteryOptimization.requestDisablingBatteryOptimization().not()) {
// If not able to perform the request, ensure that we do not display the banner again
coroutineScope.launch {
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 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.MutableBatteryOptimizationStore
import io.element.android.libraries.push.impl.store.InMemoryPushDataStore
@@ -96,7 +97,7 @@ class BatteryOptimizationPresenterTest {
assertThat(initialState.shouldDisplayBanner).isFalse()
val displayedItem = awaitItem()
assertThat(displayedItem.shouldDisplayBanner).isTrue()
displayedItem.dismiss()
displayedItem.eventSink(BatteryOptimizationEvents.Dismiss)
onOptimizationBannerDismissedResult.assertions().isCalledOnce()
}
}
@@ -122,7 +123,7 @@ class BatteryOptimizationPresenterTest {
assertThat(initialState.shouldDisplayBanner).isFalse()
val displayedItem = awaitItem()
assertThat(displayedItem.shouldDisplayBanner).isTrue()
displayedItem.openSettings(null)
displayedItem.eventSink(BatteryOptimizationEvents.DoAction)
requestDisablingBatteryOptimizationResult.assertions().isCalledOnce()
onOptimizationBannerDismissedResult.assertions().isCalledOnce()
}
@@ -148,7 +149,7 @@ class BatteryOptimizationPresenterTest {
assertThat(initialState.shouldDisplayBanner).isFalse()
val displayedItem = awaitItem()
assertThat(displayedItem.shouldDisplayBanner).isTrue()
displayedItem.openSettings(null)
displayedItem.eventSink(BatteryOptimizationEvents.DoAction)
requestDisablingBatteryOptimizationResult.assertions().isCalledOnce()
batteryOptimization.isIgnoringBatteryOptimizationsResult = true
lifeCycleOwner.givenState(Lifecycle.State.RESUMED)

View File

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