Always prefetch wallpaper before opening a conversation.

This commit is contained in:
jeffrey-signal
2026-04-07 10:33:34 -04:00
committed by Greyson Parrelli
parent 9b4a13a491
commit b8b9a632b5
5 changed files with 47 additions and 47 deletions
@@ -13,12 +13,9 @@ import org.signal.core.models.UriSerializer
import org.signal.core.models.media.Media
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.conversation.ConversationIntents.ConversationScreenType
import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.mms.SlideFactory
import org.thoughtcrime.securesms.recipients.Recipient.Companion.resolved
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stickers.StickerLocator
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper
@Serializable
@Parcelize
@@ -43,14 +40,6 @@ data class ConversationArgs(
@IgnoredOnParcel
val draftMediaType: SlideFactory.MediaType? = SlideFactory.MediaType.from(draftContentType)
@IgnoredOnParcel
val wallpaper: ChatWallpaper?
get() = resolved(recipientId).wallpaper
@IgnoredOnParcel
val chatColors: ChatColors
get() = resolved(recipientId).chatColors
fun canInitializeFromDatabase(): Boolean {
return draftText == null && (draftMedia == null || ConversationIntents.isBubbleIntentUri(draftMedia) || ConversationIntents.isNotificationIntentUri(draftMedia)) && draftMediaType == null
}
@@ -93,6 +93,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -479,8 +480,7 @@ class ConversationFragment :
repository = ConversationRepository(localContext = requireContext(), isInBubble = args.conversationScreenType == ConversationScreenType.BUBBLE),
recipientRepository = conversationRecipientRepository,
messageRequestRepository = messageRequestRepository,
scheduledMessagesRepository = ScheduledMessagesRepository(),
initialChatColors = args.chatColors
scheduledMessagesRepository = ScheduledMessagesRepository()
)
}
@@ -685,8 +685,6 @@ class ConversationFragment :
incognito = args.isIncognito
)
conversationToolbarOnScrollHelper.attach(binding.conversationItemRecycler)
presentWallpaper(args.wallpaper)
presentChatColors(args.chatColors)
presentConversationTitle(viewModel.recipientSnapshot)
presentGroupConversationSubtitle(createGroupSubtitleString(viewModel.titleViewParticipantsSnapshot))
presentActionBarMenu()
@@ -1324,10 +1322,11 @@ class ConversationFragment :
lifecycleScope.launch {
viewModel
.pinnedMessages
.combine(viewModel.wallpaper) { messages, wallpaper -> messages to wallpaper }
.flowWithLifecycle(viewLifecycleOwner.lifecycle)
.flowOn(Dispatchers.Main)
.collect {
presentPinnedMessage(it, args.wallpaper != null)
.collect { (messages, wallpaper) ->
presentPinnedMessage(pinnedMessages = messages, hasWallpaper = wallpaper != null)
}
}
@@ -1679,8 +1678,12 @@ class ConversationFragment :
presentConversationTitle(recipient)
presentChatColors(recipient.chatColors)
invalidateOptionsMenu()
updateMessageRequestAcceptedState(!viewModel.hasMessageRequestState)
recyclerViewColorizer.setChatColors(recipient.chatColors)
if (adapter.onHasWallpaperChanged(hasWallpaper = recipient.wallpaper != null)) {
conversationItemDecorations.hasWallpaper = recipient.wallpaper != null
}
}
@MainThread
@@ -2147,7 +2150,7 @@ class ConversationFragment :
lifecycleOwner = viewLifecycleOwner,
requestManager = Glide.with(this),
clickListener = ConversationItemClickListener(),
hasWallpaper = args.wallpaper != null,
hasWallpaper = viewModel.wallpaperSnapshot != null,
colorizer = colorizer,
startExpirationTimeout = viewModel::startExpirationTimeout,
chatColorsDataProvider = viewModel::chatColorsSnapshot,
@@ -2164,7 +2167,7 @@ class ConversationFragment :
adapter.setPagingController(viewModel.pagingController)
recyclerViewColorizer = RecyclerViewColorizer(binding.conversationItemRecycler)
recyclerViewColorizer.setChatColors(args.chatColors)
viewModel.recipientSnapshot?.chatColors?.let { recyclerViewColorizer.setChatColors(it) }
binding.conversationItemRecycler.adapter = ConcatAdapter(typingIndicatorAdapter, adapter)
multiselectItemDecoration = MultiselectItemDecoration(
@@ -2201,7 +2204,7 @@ class ConversationFragment :
threadHeaderMarginDecoration.toolbarMargin = statusBarInset + resources.getDimensionPixelSize(R.dimen.signal_m3_toolbar_height) + 16.dp
binding.conversationItemRecycler.addItemDecoration(threadHeaderMarginDecoration)
conversationItemDecorations = ConversationItemDecorations(hasWallpaper = args.wallpaper != null)
conversationItemDecorations = ConversationItemDecorations(hasWallpaper = viewModel.wallpaperSnapshot != null)
binding.conversationItemRecycler.addItemDecoration(conversationItemDecorations, 0)
}
@@ -35,6 +35,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -58,7 +59,6 @@ import org.thoughtcrime.securesms.banner.banners.UnauthorizedBanner
import org.thoughtcrime.securesms.contactshare.Contact
import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.ScheduledMessagesRepository
import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
import org.thoughtcrime.securesms.conversation.plaintext.PlaintextExportRepository
import org.thoughtcrime.securesms.conversation.v2.data.ConversationElementKey
@@ -102,6 +102,7 @@ import org.thoughtcrime.securesms.util.hasGiftBadge
import org.thoughtcrime.securesms.util.rx.RxStore
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.time.Duration
/**
@@ -110,7 +111,6 @@ import kotlin.time.Duration
class ConversationViewModel(
val threadId: Long,
requestedStartingPosition: Int,
initialChatColors: ChatColors,
private val repository: ConversationRepository,
recipientRepository: ConversationRecipientRepository,
messageRequestRepository: MessageRequestRepository,
@@ -158,7 +158,7 @@ class ConversationViewModel(
.observeOn(AndroidSchedulers.mainThread())
private val chatBounds: BehaviorSubject<Rect> = BehaviorSubject.create()
private val chatColors: RxStore<ChatColorsDrawable.ChatColorsData> = RxStore(ChatColorsDrawable.ChatColorsData(initialChatColors, null))
private val chatColors: RxStore<ChatColorsDrawable.ChatColorsData> = RxStore(ChatColorsDrawable.ChatColorsData(null, null))
val chatColorsSnapshot: ChatColorsDrawable.ChatColorsData get() = chatColors.state
@Volatile
@@ -172,6 +172,8 @@ class ConversationViewModel(
val isPushAvailable: Boolean
get() = recipientSnapshot?.isRegistered == true && Recipient.self().isRegistered
val wallpaper: Flow<ChatWallpaper?> = recipient.asFlow().map { it.wallpaper }.distinctUntilChanged()
val wallpaperSnapshot: ChatWallpaper?
get() = recipientSnapshot?.wallpaper
@@ -216,7 +218,7 @@ class ConversationViewModel(
private val _plaintextExportState = MutableStateFlow<PlaintextExportState>(PlaintextExportState.None)
val plaintextExportState: StateFlow<PlaintextExportState> = _plaintextExportState
private val plaintextExportCancelled = java.util.concurrent.atomic.AtomicBoolean(false)
private val plaintextExportCancelled = AtomicBoolean(false)
init {
disposables += recipient
@@ -168,7 +168,6 @@ import org.thoughtcrime.securesms.util.SnapToTopDataObserver;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.adapter.mapping.PagingMappingAdapter;
import org.thoughtcrime.securesms.verify.SelfVerificationFailureSheet;
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
import org.signal.core.ui.WindowSizeClassExtensionsKt;
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState;
@@ -1305,15 +1304,7 @@ public class ConversationListFragment extends MainFragment implements Conversati
}
private void handleCreateConversation(long threadId, Recipient recipient, int distributionType) {
SimpleTask.run(getLifecycle(), () -> {
ChatWallpaper wallpaper = recipient.resolve().getWallpaper();
if (wallpaper != null && !wallpaper.prefetch(requireContext(), 250)) {
Log.w(TAG, "Failed to prefetch wallpaper.");
}
return null;
}, (nothing) -> {
getNavigator().goToConversation(recipient.getId(), threadId, distributionType, -1);
});
getNavigator().goToConversation(recipient.getId(), threadId, distributionType, -1);
}
private void handleOpenIncognito(@NonNull Conversation conversation) {
@@ -1321,15 +1312,7 @@ public class ConversationListFragment extends MainFragment implements Conversati
Recipient recipient = conversation.getThreadRecord().getRecipient();
int distributionType = conversation.getThreadRecord().getDistributionType();
SimpleTask.run(getLifecycle(), () -> {
ChatWallpaper wallpaper = recipient.resolve().getWallpaper();
if (wallpaper != null && !wallpaper.prefetch(requireContext(), 250)) {
Log.w(TAG, "Failed to prefetch wallpaper.");
}
return null;
}, (nothing) -> {
getNavigator().goToConversation(recipient.getId(), threadId, distributionType, -1, true);
});
getNavigator().goToConversation(recipient.getId(), threadId, distributionType, -1, true);
}
private void startActionModeIfNotActive() {
@@ -17,6 +17,7 @@ import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.CreationExtras
import io.reactivex.rxjava3.core.Observable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -29,14 +30,18 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.reactive.asFlow
import kotlinx.coroutines.rx3.asObservable
import kotlinx.coroutines.withContext
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.calls.log.CallLogRow
import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.NotificationProfilesRepository
import org.thoughtcrime.securesms.components.snackbars.SnackbarStateConsumerRegistry
import org.thoughtcrime.securesms.conversation.ConversationArgs
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.megaphone.Megaphone
import org.thoughtcrime.securesms.megaphone.Megaphones
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.stories.Stories
import org.thoughtcrime.securesms.util.delegate
import org.thoughtcrime.securesms.window.AppScaffoldNavigator
@@ -44,11 +49,12 @@ import java.util.Optional
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
class MainNavigationViewModel(
private val savedStateHandle: SavedStateHandle,
savedStateHandle: SavedStateHandle,
initialListLocation: MainNavigationListLocation = MainNavigationListLocation.CHATS
) : ViewModel(), MainNavigationRouter {
companion object {
private val TAG = Log.tag(MainNavigationViewModel::class)
private const val LOCK_PANE_TO_SECONDARY = "lock_pane_to_secondary"
}
@@ -141,9 +147,11 @@ class MainNavigationViewModel(
is MainNavigationDetailLocation.Chats.Conversation -> {
internalActiveChatThreadId.update { location.conversationArgs.threadId }
}
is MainNavigationDetailLocation.Calls -> {
internalActiveCallId.update { location.controllerKey }
}
else -> Unit
}
}
@@ -221,8 +229,13 @@ class MainNavigationViewModel(
return
}
viewModelScope.launch {
internalDetailLocation.emit(location)
when (location) {
is MainNavigationDetailLocation.Chats.Conversation -> goToConversation(location.conversationArgs)
else -> {
viewModelScope.launch {
internalDetailLocation.emit(location)
}
}
}
}
@@ -239,6 +252,16 @@ class MainNavigationViewModel(
}
}
private fun goToConversation(args: ConversationArgs) = viewModelScope.launch {
withContext(Dispatchers.IO) {
val wallpaper = Recipient.resolved(args.recipientId).wallpaper
if (wallpaper?.prefetch(AppDependencies.application, 250) == false) {
Log.w(TAG, "goToConversation: Failed to prefetch wallpaper.")
}
}
internalDetailLocation.emit(MainNavigationDetailLocation.Chats.Conversation(args))
}
fun goToCameraFirstStoryCapture() {
viewModelScope.launch {
internalNavigationEvents.emit(NavigationEvent.STORY_CAMERA_FIRST)