Improve the Banner system.

This commit is contained in:
Greyson Parrelli
2024-09-17 14:59:47 -04:00
parent 24133c6dac
commit ba06efe35a
24 changed files with 769 additions and 606 deletions

View File

@@ -16,7 +16,6 @@ import android.view.View
import androidx.appcompat.widget.LinearLayoutCompat
import androidx.compose.ui.platform.ComposeView
import androidx.core.transition.addListener
import kotlinx.coroutines.flow.Flow
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.banner.Banner
import org.thoughtcrime.securesms.banner.BannerManager
@@ -58,10 +57,10 @@ class ConversationBannerView @JvmOverloads constructor(
orientation = VERTICAL
}
fun collectAndShowBanners(flows: Iterable<Flow<Banner>>) {
fun collectAndShowBanners(flows: List<Banner<*>>) {
val bannerManager = BannerManager(flows)
show(stub = bannerStub) {
bannerManager.setContent(this)
bannerManager.updateContent(this)
}
}

View File

@@ -63,6 +63,8 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.ConversationLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
@@ -80,6 +82,10 @@ import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.kotlin.subscribeBy
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@@ -106,8 +112,6 @@ import org.thoughtcrime.securesms.badges.gifts.OpenableGift
import org.thoughtcrime.securesms.badges.gifts.OpenableGiftItemDecoration
import org.thoughtcrime.securesms.badges.gifts.viewgift.received.ViewReceivedGiftBottomSheet
import org.thoughtcrime.securesms.badges.gifts.viewgift.sent.ViewSentGiftBottomSheet
import org.thoughtcrime.securesms.banner.banners.ServiceOutageBanner
import org.thoughtcrime.securesms.banner.banners.UnauthorizedBanner
import org.thoughtcrime.securesms.calls.YouAreAlreadyInACallSnackbar
import org.thoughtcrime.securesms.components.AnimatingToggle
import org.thoughtcrime.securesms.components.ComposeText
@@ -311,7 +315,6 @@ import org.thoughtcrime.securesms.util.MessageConstraintsUtil.isValidEditMessage
import org.thoughtcrime.securesms.util.PlayStoreUtil
import org.thoughtcrime.securesms.util.RemoteConfig
import org.thoughtcrime.securesms.util.SaveAttachmentUtil
import org.thoughtcrime.securesms.util.SharedPreferencesLifecycleObserver
import org.thoughtcrime.securesms.util.SignalLocalMetrics
import org.thoughtcrime.securesms.util.StorageUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences
@@ -1025,33 +1028,26 @@ class ConversationFragment :
val conversationBannerListener = ConversationBannerListener()
binding.conversationBanner.listener = conversationBannerListener
val unauthorizedProducer = UnauthorizedBanner.Producer(requireContext())
val serviceOutageProducer = ServiceOutageBanner.Producer(requireContext())
lifecycle.addObserver(
SharedPreferencesLifecycleObserver(
requireContext(),
mapOf(
TextSecurePreferences.UNAUTHORIZED_RECEIVED to { unauthorizedProducer.queryAndEmit() },
TextSecurePreferences.SERVICE_OUTAGE to { serviceOutageProducer.queryAndEmit() }
lifecycleScope.launch {
viewModel
.getBannerFlows(
context = requireContext(),
groupJoinClickListener = conversationBannerListener::reviewJoinRequestsAction,
onSuggestionAddMembers = {
conversationGroupViewModel.groupRecordSnapshot?.let { groupRecord ->
GroupsV1MigrationSuggestionsDialog.show(requireActivity(), groupRecord.id.requireV2(), groupRecord.gv1MigrationSuggestions)
}
},
onSuggestionNoThanks = conversationGroupViewModel::onSuggestedMembersBannerDismissed,
bubbleClickListener = conversationBannerListener::changeBubbleSettingAction
)
)
)
val bannerFlows = viewModel.getBannerFlows(
context = requireContext(),
unauthorizedFlow = unauthorizedProducer.flow,
serviceOutageStatusFlow = serviceOutageProducer.flow,
groupJoinClickListener = conversationBannerListener::reviewJoinRequestsAction,
onAddMembers = {
conversationGroupViewModel.groupRecordSnapshot?.let { groupRecord ->
GroupsV1MigrationSuggestionsDialog.show(requireActivity(), groupRecord.id.requireV2(), groupRecord.gv1MigrationSuggestions)
.distinctUntilChanged()
.flowWithLifecycle(viewLifecycleOwner.lifecycle)
.flowOn(Dispatchers.Main)
.collect {
binding.conversationBanner.collectAndShowBanners(it)
}
},
onNoThanks = conversationGroupViewModel::onSuggestedMembersBannerDismissed,
bubbleClickListener = conversationBannerListener::changeBubbleSettingAction
)
binding.conversationBanner.collectAndShowBanners(bannerFlows)
}
if (TextSecurePreferences.getServiceOutage(context)) {
AppDependencies.jobManager.add(ServiceOutageDetectionJob())

View File

@@ -29,12 +29,13 @@ import io.reactivex.rxjava3.schedulers.Schedulers
import io.reactivex.rxjava3.subjects.BehaviorSubject
import io.reactivex.rxjava3.subjects.PublishSubject
import io.reactivex.rxjava3.subjects.Subject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
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.orNull
import org.signal.paging.ProxyPagingController
@@ -309,30 +310,42 @@ class ConversationViewModel(
})
}
@OptIn(ExperimentalCoroutinesApi::class)
fun getBannerFlows(context: Context, unauthorizedFlow: Flow<UnauthorizedBanner>, serviceOutageStatusFlow: Flow<ServiceOutageBanner>, groupJoinClickListener: () -> Unit, onAddMembers: () -> Unit, onNoThanks: () -> Unit, bubbleClickListener: (Boolean) -> Unit): List<Flow<Banner>> {
val pendingGroupJoinFlow: Flow<PendingGroupJoinRequestsBanner> = merge(
flow {
emit(PendingGroupJoinRequestsBanner(false, 0, {}, {}))
},
groupRecordFlow.flatMapConcat { PendingGroupJoinRequestsBanner.createFlow(it.actionableRequestingMembersCount, groupJoinClickListener) }
)
fun getBannerFlows(
context: Context,
groupJoinClickListener: () -> Unit,
onSuggestionAddMembers: () -> Unit,
onSuggestionNoThanks: () -> Unit,
bubbleClickListener: (Boolean) -> Unit
): Flow<List<Banner<*>>> {
val pendingGroupJoinFlow: Flow<PendingGroupJoinRequestsBanner> = groupRecordFlow
.map {
PendingGroupJoinRequestsBanner(
suggestionsSize = it.actionableRequestingMembersCount,
onViewClicked = groupJoinClickListener
)
}
val groupV1SuggestionsFlow = merge(
flow {
emit(GroupsV1MigrationSuggestionsBanner(0, {}, {}))
},
groupRecordFlow.flatMapConcat { GroupsV1MigrationSuggestionsBanner.createFlow(it.gv1MigrationSuggestions.size, onAddMembers, onNoThanks) }
)
val groupV1SuggestionsFlow = groupRecordFlow
.map {
GroupsV1MigrationSuggestionsBanner(
suggestionsSize = it.gv1MigrationSuggestions.size,
onAddMembers = onSuggestionAddMembers,
onNoThanks = onSuggestionNoThanks
)
}
return listOf(
OutdatedBuildBanner.createFlow(context, OutdatedBuildBanner.ExpiryStatus.EXPIRED_ONLY),
unauthorizedFlow,
serviceOutageStatusFlow,
pendingGroupJoinFlow,
groupV1SuggestionsFlow,
BubbleOptOutBanner.createFlow(inBubble = repository.isInBubble, bubbleClickListener)
return combine(
listOf(
flowOf(OutdatedBuildBanner()),
flowOf(UnauthorizedBanner(context)),
flowOf(ServiceOutageBanner(context)),
pendingGroupJoinFlow,
groupV1SuggestionsFlow,
flowOf(BubbleOptOutBanner(inBubble = repository.isInBubble, actionListener = bubbleClickListener))
),
transform = { it.toList() }
)
.flowOn(Dispatchers.IO)
}
fun onChatBoundsChanged(bounds: Rect) {