Fix dismissible banners.
This commit is contained in:
parent
630875dae2
commit
560086a1c2
8 changed files with 100 additions and 47 deletions
|
@ -7,7 +7,6 @@ package org.thoughtcrime.securesms.banner
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import org.signal.core.util.logging.Log
|
||||
|
||||
|
@ -27,10 +26,10 @@ abstract class Banner {
|
|||
* @param bannerFactory a block the produces a [Banner], or null. Returning null will complete the [Flow] without emitting any values.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun <T : Banner> createAndEmit(bannerFactory: () -> T?): Flow<T> {
|
||||
return bannerFactory()?.let {
|
||||
fun <T : Banner> createAndEmit(bannerFactory: () -> T): Flow<T> {
|
||||
return bannerFactory().let {
|
||||
flow { emit(it) }
|
||||
} ?: emptyFlow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.banner
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
||||
abstract class DismissibleBannerProducer<T : Banner>(bannerProducer: (dismissListener: () -> Unit) -> T) {
|
||||
abstract fun createDismissedBanner(): T
|
||||
|
||||
private val mutableSharedFlow: MutableSharedFlow<T> = MutableSharedFlow(replay = 1)
|
||||
private val dismissListener = {
|
||||
mutableSharedFlow.tryEmit(createDismissedBanner())
|
||||
}
|
||||
|
||||
init {
|
||||
mutableSharedFlow.tryEmit(bannerProducer(dismissListener))
|
||||
}
|
||||
|
||||
val flow: Flow<T> = mutableSharedFlow
|
||||
}
|
|
@ -11,6 +11,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.banner.Banner
|
||||
import org.thoughtcrime.securesms.banner.DismissibleBannerProducer
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Action
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
|
@ -35,10 +36,20 @@ class BubbleOptOutBanner(inBubble: Boolean, private val actionListener: (Boolean
|
|||
)
|
||||
}
|
||||
|
||||
private class Producer(inBubble: Boolean, actionListener: (Boolean) -> Unit) : DismissibleBannerProducer<BubbleOptOutBanner>(bannerProducer = {
|
||||
BubbleOptOutBanner(inBubble) { turnOffBubbles ->
|
||||
actionListener(turnOffBubbles)
|
||||
it()
|
||||
}
|
||||
}) {
|
||||
override fun createDismissedBanner(): BubbleOptOutBanner {
|
||||
return BubbleOptOutBanner(false) {}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun createFlow(inBubble: Boolean, actionListener: (Boolean) -> Unit): Flow<BubbleOptOutBanner> = createAndEmit {
|
||||
BubbleOptOutBanner(inBubble, actionListener)
|
||||
fun createFlow(inBubble: Boolean, actionListener: (Boolean) -> Unit): Flow<BubbleOptOutBanner> {
|
||||
return Producer(inBubble, actionListener).flow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.banner.Banner
|
||||
import org.thoughtcrime.securesms.banner.DismissibleBannerProducer
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Action
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
|
@ -19,8 +20,8 @@ import org.thoughtcrime.securesms.util.PowerManagerCompat
|
|||
import org.thoughtcrime.securesms.util.ServiceUtil
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
|
||||
class DozeBanner(private val context: Context) : Banner() {
|
||||
override val enabled: Boolean =
|
||||
class DozeBanner(private val context: Context, val dismissed: Boolean, private val onDismiss: () -> Unit) : Banner() {
|
||||
override val enabled: Boolean = !dismissed &&
|
||||
Build.VERSION.SDK_INT >= 23 && !SignalStore.account.fcmEnabled && !TextSecurePreferences.hasPromptedOptimizeDoze(context) && !ServiceUtil.getPowerManager(context).isIgnoringBatteryOptimizations(context.packageName)
|
||||
|
||||
@Composable
|
||||
|
@ -39,15 +40,23 @@ class DozeBanner(private val context: Context) : Banner() {
|
|||
),
|
||||
onDismissListener = {
|
||||
TextSecurePreferences.setPromptedOptimizeDoze(context, true)
|
||||
onDismiss()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private class Producer(private val context: Context) : DismissibleBannerProducer<DozeBanner>(bannerProducer = {
|
||||
DozeBanner(context = context, dismissed = false, onDismiss = it)
|
||||
}) {
|
||||
override fun createDismissedBanner(): DozeBanner {
|
||||
return DozeBanner(context, true) {}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun createFlow(context: Context): Flow<DozeBanner> = createAndEmit {
|
||||
DozeBanner(context)
|
||||
fun createFlow(context: Context): Flow<DozeBanner> {
|
||||
return Producer(context).flow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,13 +10,11 @@ import androidx.compose.ui.res.pluralStringResource
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.banner.Banner
|
||||
import org.thoughtcrime.securesms.banner.DismissibleBannerProducer
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Action
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
|
||||
class GroupsV1MigrationSuggestionsBanner(private val suggestionsSize: Int, private val onAddMembers: () -> Unit, private val onNoThanks: () -> Unit) : Banner() {
|
||||
private val timeUntilUnblock = SignalStore.misc.cdsBlockedUtil - System.currentTimeMillis()
|
||||
|
||||
override val enabled: Boolean = suggestionsSize > 0
|
||||
|
||||
@Composable
|
||||
|
@ -35,11 +33,23 @@ class GroupsV1MigrationSuggestionsBanner(private val suggestionsSize: Int, priva
|
|||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private class Producer(suggestionsSize: Int, onAddMembers: () -> Unit, onNoThanks: () -> Unit) : DismissibleBannerProducer<GroupsV1MigrationSuggestionsBanner>(bannerProducer = {
|
||||
GroupsV1MigrationSuggestionsBanner(
|
||||
suggestionsSize,
|
||||
onAddMembers
|
||||
) {
|
||||
onNoThanks()
|
||||
it()
|
||||
}
|
||||
}) {
|
||||
override fun createDismissedBanner(): GroupsV1MigrationSuggestionsBanner {
|
||||
return GroupsV1MigrationSuggestionsBanner(0, {}, {})
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun createFlow(suggestionsSize: Int, onAddMembers: () -> Unit, onNoThanks: () -> Unit): Flow<GroupsV1MigrationSuggestionsBanner> = createAndEmit {
|
||||
GroupsV1MigrationSuggestionsBanner(suggestionsSize, onAddMembers, onNoThanks)
|
||||
companion object {
|
||||
fun createFlow(suggestionsSize: Int, onAddMembers: () -> Unit, onNoThanks: () -> Unit): Flow<GroupsV1MigrationSuggestionsBanner> {
|
||||
return Producer(suggestionsSize, onAddMembers, onNoThanks).flow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ package org.thoughtcrime.securesms.banner.banners
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.banner.Banner
|
||||
import org.thoughtcrime.securesms.banner.DismissibleBannerProducer
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Action
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner
|
||||
|
||||
|
@ -32,11 +32,17 @@ class PendingGroupJoinRequestsBanner(override val enabled: Boolean, private val
|
|||
)
|
||||
}
|
||||
|
||||
class Producer(suggestionsSize: Int, onViewClicked: () -> Unit) {
|
||||
private val dismissListener: () -> Unit = {
|
||||
mutableStateFlow.tryEmit(PendingGroupJoinRequestsBanner(false, suggestionsSize, onViewClicked, null))
|
||||
private class Producer(suggestionsSize: Int, onViewClicked: () -> Unit) : DismissibleBannerProducer<PendingGroupJoinRequestsBanner>(bannerProducer = {
|
||||
PendingGroupJoinRequestsBanner(suggestionsSize > 0, suggestionsSize, onViewClicked, it)
|
||||
}) {
|
||||
override fun createDismissedBanner(): PendingGroupJoinRequestsBanner {
|
||||
return PendingGroupJoinRequestsBanner(false, 0, {}, null)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun createFlow(suggestionsSize: Int, onViewClicked: () -> Unit): Flow<PendingGroupJoinRequestsBanner> {
|
||||
return Producer(suggestionsSize, onViewClicked).flow
|
||||
}
|
||||
private val mutableStateFlow: MutableStateFlow<PendingGroupJoinRequestsBanner> = MutableStateFlow(PendingGroupJoinRequestsBanner(suggestionsSize > 0, suggestionsSize, onViewClicked, dismissListener))
|
||||
val flow: Flow<PendingGroupJoinRequestsBanner> = mutableStateFlow
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,7 +108,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||
|
||||
class ConversationRepository(
|
||||
private val localContext: Context,
|
||||
private val isInBubble: Boolean
|
||||
val isInBubble: Boolean
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -31,11 +31,10 @@ import io.reactivex.rxjava3.subjects.PublishSubject
|
|||
import io.reactivex.rxjava3.subjects.Subject
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flatMap
|
||||
import kotlinx.coroutines.flow.flatMapConcat
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.rx3.asFlow
|
||||
import org.signal.core.util.concurrent.subscribeWithSubject
|
||||
import org.signal.core.util.orNull
|
||||
|
@ -45,7 +44,6 @@ import org.thoughtcrime.securesms.banner.banners.BubbleOptOutBanner
|
|||
import org.thoughtcrime.securesms.banner.banners.GroupsV1MigrationSuggestionsBanner
|
||||
import org.thoughtcrime.securesms.banner.banners.OutdatedBuildBanner
|
||||
import org.thoughtcrime.securesms.banner.banners.PendingGroupJoinRequestsBanner
|
||||
import org.thoughtcrime.securesms.banner.banners.PendingGroupJoinRequestsBanner.Producer
|
||||
import org.thoughtcrime.securesms.banner.banners.ServiceOutageBanner
|
||||
import org.thoughtcrime.securesms.banner.banners.UnauthorizedBanner
|
||||
import org.thoughtcrime.securesms.components.reminder.Reminder
|
||||
|
@ -172,7 +170,7 @@ class ConversationViewModel(
|
|||
private val refreshReminder: Subject<Unit> = PublishSubject.create()
|
||||
val reminder: Observable<Optional<Reminder>>
|
||||
|
||||
private val groupRecordFlow: Flow<GroupRecord?>
|
||||
private val groupRecordFlow: Flow<GroupRecord>
|
||||
|
||||
private val refreshIdentityRecords: Subject<Unit> = PublishSubject.create()
|
||||
private val identityRecordsStore: RxStore<IdentityRecordsState> = RxStore(IdentityRecordsState())
|
||||
|
@ -295,7 +293,7 @@ class ConversationViewModel(
|
|||
.flatMapMaybe { groupRecord -> repository.getReminder(groupRecord.orNull()) }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
groupRecordFlow = recipientRepository.groupRecord.subscribeOn(Schedulers.io()).asFlow().map { it.orNull() }
|
||||
groupRecordFlow = recipientRepository.groupRecord.subscribeOn(Schedulers.io()).asFlow().mapNotNull { it.orNull() }
|
||||
|
||||
Observable.combineLatest(
|
||||
refreshIdentityRecords.startWithItem(Unit).observeOn(Schedulers.io()),
|
||||
|
@ -322,23 +320,19 @@ class ConversationViewModel(
|
|||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun getBannerFlows(context: Context, groupJoinClickListener: () -> Unit, onAddMembers: () -> Unit, onNoThanks: () -> Unit, bubbleClickListener: (Boolean) -> Unit): List<Flow<Banner>> {
|
||||
val pendingGroupJoinFlow = groupRecordFlow.flatMapConcat {
|
||||
val pendingGroupJoinFlow: Flow<PendingGroupJoinRequestsBanner> = merge(
|
||||
flow {
|
||||
if (it == null) {
|
||||
emit(PendingGroupJoinRequestsBanner(false, 0, {}, {}))
|
||||
} else {
|
||||
emitAll(Producer(it.actionableRequestingMembersCount, groupJoinClickListener).flow)
|
||||
}
|
||||
}
|
||||
}
|
||||
emit(PendingGroupJoinRequestsBanner(false, 0, {}, {}))
|
||||
},
|
||||
groupRecordFlow.flatMapConcat { PendingGroupJoinRequestsBanner.createFlow(it.actionableRequestingMembersCount, groupJoinClickListener) }
|
||||
)
|
||||
|
||||
val groupV1SuggestionsFlow = groupRecordFlow.map {
|
||||
if (it == null) {
|
||||
val groupV1SuggestionsFlow = merge(
|
||||
flow {
|
||||
GroupsV1MigrationSuggestionsBanner(0, {}, {})
|
||||
} else {
|
||||
GroupsV1MigrationSuggestionsBanner(it.gv1MigrationSuggestions.size, onAddMembers, onNoThanks)
|
||||
}
|
||||
}
|
||||
},
|
||||
groupRecordFlow.flatMapConcat { GroupsV1MigrationSuggestionsBanner.createFlow(it.gv1MigrationSuggestions.size, onAddMembers, onNoThanks) }
|
||||
)
|
||||
|
||||
return listOf(
|
||||
OutdatedBuildBanner.createFlow(context, OutdatedBuildBanner.ExpiryStatus.EXPIRED_ONLY),
|
||||
|
@ -346,7 +340,7 @@ class ConversationViewModel(
|
|||
ServiceOutageBanner.createFlow(context),
|
||||
pendingGroupJoinFlow,
|
||||
groupV1SuggestionsFlow,
|
||||
BubbleOptOutBanner.createFlow(inBubble = true, bubbleClickListener)
|
||||
BubbleOptOutBanner.createFlow(inBubble = repository.isInBubble, bubbleClickListener)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue