mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-08-08 14:26:02 +01:00
Allow banners to reactively update their enabled state.
This commit is contained in:
committed by
Alex Hart
parent
cd5ead4981
commit
b462ef2149
@@ -8,6 +8,7 @@ package org.thoughtcrime.securesms.banner
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.runtime.Composable
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
|
||||
/**
|
||||
* This class represents a banner across the top of the screen.
|
||||
@@ -28,6 +29,12 @@ abstract class Banner<Model> {
|
||||
*/
|
||||
abstract val dataFlow: Flow<Model>
|
||||
|
||||
/**
|
||||
* Emits whenever this banner's [enabled] state may have changed.
|
||||
*/
|
||||
open val stateUpdates: Flow<Unit>
|
||||
get() = emptyFlow()
|
||||
|
||||
/**
|
||||
* Composable function to display the content emitted from [dataFlow].
|
||||
* You likely want to use [org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner].
|
||||
|
||||
@@ -15,6 +15,11 @@ import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import org.signal.core.ui.compose.theme.SignalTheme
|
||||
import org.signal.core.util.logging.Log
|
||||
|
||||
@@ -32,6 +37,22 @@ class BannerManager @JvmOverloads constructor(
|
||||
val TAG = Log.tag(BannerManager::class)
|
||||
}
|
||||
|
||||
private fun selectEnabledBanner(): Banner<Any>? = banners.firstOrNull { it.enabled } as Banner<Any>?
|
||||
|
||||
/**
|
||||
* Reactively selects which [Banner] should be shown. Re-evaluates whenever any banner signals via
|
||||
* [Banner.stateUpdates] that its eligibility may have changed.
|
||||
*/
|
||||
private val selectedBanner: Flow<Banner<Any>?> =
|
||||
if (banners.isEmpty()) {
|
||||
flowOf(null)
|
||||
} else {
|
||||
merge(*banners.map { it.stateUpdates }.toTypedArray())
|
||||
.onStart { emit(Unit) }
|
||||
.map { selectEnabledBanner() }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-evaluates the [Banner]s, choosing one to render (if any) and updating the view.
|
||||
*/
|
||||
@@ -39,19 +60,20 @@ class BannerManager @JvmOverloads constructor(
|
||||
composeView.apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
val banner: Banner<Any>? = banners.firstOrNull { it.enabled } as Banner<Any>?
|
||||
if (banner == null) {
|
||||
val banner: Banner<Any>? by selectedBanner.collectAsStateWithLifecycle(initialValue = selectEnabledBanner())
|
||||
val selected = banner
|
||||
if (selected == null) {
|
||||
onNoBannerShownListener()
|
||||
return@setContent
|
||||
}
|
||||
|
||||
key(banner) {
|
||||
val bannerState by banner.dataFlow.collectAsStateWithLifecycle(initialValue = null)
|
||||
key(selected) {
|
||||
val bannerState by selected.dataFlow.collectAsStateWithLifecycle(initialValue = null)
|
||||
|
||||
bannerState?.let { model ->
|
||||
SignalTheme {
|
||||
Box {
|
||||
banner.DisplayBanner(model, PaddingValues(horizontal = 12.dp, vertical = 8.dp))
|
||||
selected.DisplayBanner(model, PaddingValues(horizontal = 12.dp, vertical = 8.dp))
|
||||
}
|
||||
}
|
||||
onNewBannerShownListener()
|
||||
@@ -66,16 +88,14 @@ class BannerManager @JvmOverloads constructor(
|
||||
*/
|
||||
@Composable
|
||||
fun Banner() {
|
||||
val banner: Banner<Any>? = banners.firstOrNull { it.enabled } as Banner<Any>?
|
||||
if (banner == null) {
|
||||
return
|
||||
}
|
||||
val banner: Banner<Any>? by selectedBanner.collectAsStateWithLifecycle(initialValue = selectEnabledBanner())
|
||||
val selected = banner ?: return
|
||||
|
||||
key(banner) {
|
||||
val bannerState by banner.dataFlow.collectAsStateWithLifecycle(initialValue = null)
|
||||
key(selected) {
|
||||
val bannerState by selected.dataFlow.collectAsStateWithLifecycle(initialValue = null)
|
||||
|
||||
bannerState?.let { model ->
|
||||
banner.DisplayBanner(model, PaddingValues(horizontal = 12.dp, vertical = 8.dp))
|
||||
selected.DisplayBanner(model, PaddingValues(horizontal = 12.dp, vertical = 8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-2
@@ -9,7 +9,9 @@ import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.runtime.Composable
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import org.thoughtcrime.securesms.backup.v2.ArchiveRestoreProgress
|
||||
import org.thoughtcrime.securesms.backup.v2.ArchiveRestoreProgressState
|
||||
@@ -32,6 +34,12 @@ class ArchiveRestoreStatusBanner(private val listener: RestoreProgressBannerList
|
||||
}
|
||||
}
|
||||
|
||||
override val stateUpdates: Flow<Unit>
|
||||
get() = ArchiveRestoreProgress.stateFlow
|
||||
.map { enabled }
|
||||
.distinctUntilChanged()
|
||||
.map { }
|
||||
|
||||
@Composable
|
||||
override fun DisplayBanner(model: ArchiveRestoreProgressState, contentPadding: PaddingValues) {
|
||||
ArchiveRestoreStatusBanner(
|
||||
@@ -40,7 +48,6 @@ class ArchiveRestoreStatusBanner(private val listener: RestoreProgressBannerList
|
||||
onActionClick = listener::onActionClick,
|
||||
onDismissClick = {
|
||||
ArchiveRestoreProgress.clearFinishedStatus()
|
||||
listener.onDismissComplete()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -48,6 +55,5 @@ class ArchiveRestoreStatusBanner(private val listener: RestoreProgressBannerList
|
||||
interface RestoreProgressBannerListener {
|
||||
fun onBannerClick()
|
||||
fun onActionClick(data: ArchiveRestoreProgressState)
|
||||
fun onDismissComplete()
|
||||
}
|
||||
}
|
||||
|
||||
+8
-2
@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.runtime.Composable
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.signal.core.util.bytes
|
||||
import org.thoughtcrime.securesms.backup.ArchiveUploadProgress
|
||||
@@ -78,6 +79,12 @@ class ArchiveUploadStatusBanner(private val listener: UploadProgressBannerListen
|
||||
}
|
||||
}
|
||||
|
||||
override val stateUpdates: Flow<Unit>
|
||||
get() = ArchiveUploadProgress.progress
|
||||
.map { enabled }
|
||||
.distinctUntilChanged()
|
||||
.map { }
|
||||
|
||||
@Composable
|
||||
override fun DisplayBanner(model: ArchiveUploadStatusBannerViewState, contentPadding: PaddingValues) {
|
||||
ArchiveUploadStatusBannerView(
|
||||
@@ -92,7 +99,7 @@ class ArchiveUploadStatusBanner(private val listener: UploadProgressBannerListen
|
||||
}
|
||||
ArchiveUploadStatusBannerViewEvents.HideClicked -> {
|
||||
SignalStore.backup.uploadBannerVisible = false
|
||||
listener.onHidden()
|
||||
ArchiveUploadProgress.triggerUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,6 +112,5 @@ class ArchiveUploadStatusBanner(private val listener: UploadProgressBannerListen
|
||||
interface UploadProgressBannerListener {
|
||||
fun onBannerClick()
|
||||
fun onCancelClicked()
|
||||
fun onHidden()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,10 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.rx3.asFlow
|
||||
import org.signal.core.ui.compose.DayNightPreviews
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.thoughtcrime.securesms.R
|
||||
@@ -22,6 +25,7 @@ import org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Importance
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.registration.ui.RegistrationActivity
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
|
||||
@@ -36,6 +40,15 @@ class UnauthorizedBanner(val context: Context) : Banner<Unit>() {
|
||||
override val dataFlow: Flow<Unit>
|
||||
get() = flowOf(Unit)
|
||||
|
||||
override val stateUpdates: Flow<Unit>
|
||||
get() = Recipient.self()
|
||||
.live()
|
||||
.observable()
|
||||
.asFlow()
|
||||
.map { enabled }
|
||||
.distinctUntilChanged()
|
||||
.map { }
|
||||
|
||||
@Composable
|
||||
override fun DisplayBanner(model: Unit, contentPadding: PaddingValues) {
|
||||
Banner(contentPadding, SignalStore.account.isLinkedDevice)
|
||||
|
||||
-11
@@ -874,11 +874,6 @@ public class ConversationListFragment extends MainFragment implements Conversati
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismissComplete() {
|
||||
bannerManager.updateContent(bannerView.get());
|
||||
}
|
||||
}),
|
||||
new ArchiveUploadStatusBanner(new ArchiveUploadStatusBanner.UploadProgressBannerListener() {
|
||||
@Override
|
||||
@@ -894,15 +889,9 @@ public class ConversationListFragment extends MainFragment implements Conversati
|
||||
.setNegativeButton(R.string.CancelBackupDialog_continue_action, null)
|
||||
.setPositiveButton(R.string.CancelBackupDialog_cancel_action, (d, w) -> {
|
||||
ArchiveUploadProgress.INSTANCE.cancel();
|
||||
bannerManager.updateContent(bannerView.get());
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHidden() {
|
||||
bannerManager.updateContent(bannerView.get());
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user