diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationArgs.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationArgs.kt index bffa34aaa1..e3c2c988a5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationArgs.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationArgs.kt @@ -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 } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt index 824b041a5f..c5bd9f0b7e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt @@ -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) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index d77ec8dd51..a8df99ef38 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -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 = BehaviorSubject.create() - private val chatColors: RxStore = RxStore(ChatColorsDrawable.ChatColorsData(initialChatColors, null)) + private val chatColors: RxStore = 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 = recipient.asFlow().map { it.wallpaper }.distinctUntilChanged() + val wallpaperSnapshot: ChatWallpaper? get() = recipientSnapshot?.wallpaper @@ -216,7 +218,7 @@ class ConversationViewModel( private val _plaintextExportState = MutableStateFlow(PlaintextExportState.None) val plaintextExportState: StateFlow = _plaintextExportState - private val plaintextExportCancelled = java.util.concurrent.atomic.AtomicBoolean(false) + private val plaintextExportCancelled = AtomicBoolean(false) init { disposables += recipient diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index 7281e907e4..3ca0894a94 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -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() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/main/MainNavigationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/main/MainNavigationViewModel.kt index 9c1bd4f5b7..c41f03e9c4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/main/MainNavigationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/main/MainNavigationViewModel.kt @@ -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)