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..527ab8f06c 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 @@ -38,19 +35,12 @@ data class ConversationArgs( val giftBadge: Badge?, val shareDataTimestamp: Long, val conversationScreenType: ConversationScreenType, - val isIncognito: Boolean = false + val isIncognito: Boolean = false, + val hasWallpaper: Boolean = false ) : Parcelable { @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/ConversationIntents.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationIntents.java index 37462d0a5e..42b4b4039a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationIntents.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationIntents.java @@ -10,11 +10,11 @@ import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.thoughtcrime.securesms.badges.models.Badge; +import org.signal.core.models.media.Media; import org.thoughtcrime.securesms.MainActivity; +import org.thoughtcrime.securesms.badges.models.Badge; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.ThreadTable; -import org.signal.core.models.media.Media; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.stickers.StickerLocator; @@ -48,6 +48,7 @@ public class ConversationIntents { private static final String EXTRA_SHARE_DATA_TIMESTAMP = "share_data_timestamp"; private static final String EXTRA_CONVERSATION_TYPE = "conversation_type"; private static final String EXTRA_INCOGNITO = "incognito"; + private static final String EXTRA_HAS_WALLPAPER = "has_wallpaper"; private static final String INTENT_DATA = "intent_data"; private static final String INTENT_TYPE = "intent_type"; @@ -75,12 +76,15 @@ public class ConversationIntents { } } - public static @NonNull Builder createPopUpBuilder(@NonNull Context context, @NonNull RecipientId recipientId, long threadId) { - return new Builder(context, ConversationPopupActivity.class, recipientId, threadId, ConversationScreenType.POPUP); + public static @NonNull Builder createPopUpBuilder(@NonNull Context context, @NonNull RecipientId recipientId, long threadId, boolean hasWallpaper) { + return new Builder(context, ConversationPopupActivity.class, recipientId, threadId, ConversationScreenType.POPUP) + .withHasWallpaper(hasWallpaper); } - public static @NonNull Intent createBubbleIntent(@NonNull Context context, @NonNull RecipientId recipientId, long threadId) { - return new Builder(context, BubbleConversationActivity.class, recipientId, threadId, ConversationScreenType.BUBBLE).build(); + public static @NonNull Intent createBubbleIntent(@NonNull Context context, @NonNull RecipientId recipientId, long threadId, boolean hasWallpaper) { + return new Builder(context, BubbleConversationActivity.class, recipientId, threadId, ConversationScreenType.BUBBLE) + .withHasWallpaper(hasWallpaper) + .build(); } /** @@ -156,7 +160,9 @@ public class ConversationIntents { null, -1L, ConversationScreenType.BUBBLE, - false); + false, + Boolean.parseBoolean(intentDataUri.getQueryParameter(EXTRA_HAS_WALLPAPER)) + ); } return new ConversationArgs(RecipientId.from(Objects.requireNonNull(arguments.getString(EXTRA_RECIPIENT))), @@ -174,7 +180,8 @@ public class ConversationIntents { arguments.getParcelable(EXTRA_GIFT_BADGE), arguments.getLong(EXTRA_SHARE_DATA_TIMESTAMP, -1L), ConversationScreenType.from(arguments.getInt(EXTRA_CONVERSATION_TYPE, 0)), - arguments.getBoolean(EXTRA_INCOGNITO, false)); + arguments.getBoolean(EXTRA_INCOGNITO, false), + arguments.getBoolean(EXTRA_HAS_WALLPAPER, false)); } public final static class Builder { @@ -197,6 +204,7 @@ public class ConversationIntents { private Badge giftBadge; private long shareDataTimestamp = -1L; private boolean incognito; + private boolean hasWallpaper; private int flags; private Builder(@NonNull Context context, @@ -226,6 +234,7 @@ public class ConversationIntents { giftBadge = args.getGiftBadge(); shareDataTimestamp = args.getShareDataTimestamp(); incognito = args.isIncognito(); + hasWallpaper = args.getHasWallpaper(); return this; } @@ -295,6 +304,11 @@ public class ConversationIntents { return this; } + public @NonNull Builder withHasWallpaper(boolean hasWallpaper) { + this.hasWallpaper = hasWallpaper; + return this; + } + public @NonNull Builder withFlags(int flags) { this.flags = flags; return this; @@ -317,7 +331,8 @@ public class ConversationIntents { giftBadge, shareDataTimestamp, conversationScreenType, - incognito + incognito, + hasWallpaper ); } @@ -337,6 +352,7 @@ public class ConversationIntents { intent.setData(new Uri.Builder().authority(BUBBLE_AUTHORITY) .appendQueryParameter(EXTRA_RECIPIENT, recipientId.serialize()) .appendQueryParameter(EXTRA_THREAD_ID, String.valueOf(threadId)) + .appendQueryParameter(EXTRA_HAS_WALLPAPER, String.valueOf(hasWallpaper)) .build()); return intent; @@ -353,6 +369,7 @@ public class ConversationIntents { intent.putExtra(EXTRA_SHARE_DATA_TIMESTAMP, shareDataTimestamp); intent.putExtra(EXTRA_CONVERSATION_TYPE, conversationScreenType.code); intent.putExtra(EXTRA_INCOGNITO, incognito); + intent.putExtra(EXTRA_HAS_WALLPAPER, hasWallpaper); if (draftText != null) { intent.putExtra(EXTRA_TEXT, draftText); 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 8e59e1cc87..87fb28e90d 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 @@ -480,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() ) } @@ -687,8 +686,6 @@ class ConversationFragment : incognito = args.isIncognito ) conversationToolbarOnScrollHelper.attach(binding.conversationItemRecycler) - presentWallpaper(args.wallpaper) - presentChatColors(args.chatColors) presentConversationTitle(viewModel.recipientSnapshot) presentGroupConversationSubtitle(createGroupSubtitleString(viewModel.titleViewParticipantsSnapshot)) presentActionBarMenu() @@ -1683,6 +1680,11 @@ class ConversationFragment : presentChatColors(recipient.chatColors) invalidateOptionsMenu() updateMessageRequestAcceptedState(!viewModel.hasMessageRequestState) + + recyclerViewColorizer.setChatColors(recipient.chatColors) + if (adapter.onHasWallpaperChanged(hasWallpaper = recipient.wallpaper != null)) { + conversationItemDecorations.hasWallpaper = recipient.wallpaper != null + } } @MainThread @@ -2149,7 +2151,7 @@ class ConversationFragment : lifecycleOwner = viewLifecycleOwner, requestManager = Glide.with(this), clickListener = ConversationItemClickListener(), - hasWallpaper = args.wallpaper != null, + hasWallpaper = args.hasWallpaper, colorizer = colorizer, startExpirationTimeout = viewModel::startExpirationTimeout, chatColorsDataProvider = viewModel::chatColorsSnapshot, @@ -2166,7 +2168,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( @@ -2203,7 +2205,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 = args.hasWallpaper) 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 c3d17e2b9e..790cbc9a90 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 @@ -59,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 @@ -112,7 +111,6 @@ import kotlin.time.Duration class ConversationViewModel( val threadId: Long, requestedStartingPosition: Int, - initialChatColors: ChatColors, private val repository: ConversationRepository, recipientRepository: ConversationRecipientRepository, messageRequestRepository: MessageRequestRepository, @@ -160,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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt index fb5075db60..bc53b91f88 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt @@ -252,7 +252,7 @@ open class V2ConversationItemTextOnlyViewHolder>( hasProcessedSupportedPayload = true } - if (hasProcessedSupportedPayload) { + if (hasProcessedSupportedPayload && V2Payload.WALLPAPER !in payload) { return } 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 c41f03e9c4..7a46e625eb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/main/MainNavigationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/main/MainNavigationViewModel.kt @@ -253,13 +253,14 @@ class MainNavigationViewModel( } private fun goToConversation(args: ConversationArgs) = viewModelScope.launch { - withContext(Dispatchers.IO) { + val updatedArgs = 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.") } + args.copy(hasWallpaper = wallpaper != null) } - internalDetailLocation.emit(MainNavigationDetailLocation.Chats.Conversation(args)) + internalDetailLocation.emit(MainNavigationDetailLocation.Chats.Conversation(updatedArgs)) } fun goToCameraFirstStoryCapture() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/MessageNotifier.java index d46dc637a9..60fd42f9ec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MessageNotifier.java @@ -6,6 +6,7 @@ import android.content.Intent; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; import org.signal.core.util.concurrent.SignalExecutors; import org.thoughtcrime.securesms.dependencies.AppDependencies; @@ -28,6 +29,8 @@ public interface MessageNotifier { void cancelDelayedNotifications(); void updateNotification(@NonNull Context context); void updateNotification(@NonNull Context context, @NonNull ConversationId conversationId); + + @WorkerThread void forceBubbleNotification(@NonNull Context context, @NonNull ConversationId conversationId); void addStickyThread(@NonNull ConversationId conversationId, long earliestTimestamp); void removeStickyThread(@NonNull ConversationId conversationId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/DefaultMessageNotifier.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/DefaultMessageNotifier.kt index 3e4b016f25..5d8acb2f28 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/DefaultMessageNotifier.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/DefaultMessageNotifier.kt @@ -8,6 +8,7 @@ import android.content.Context import android.content.Intent import android.os.Build import android.service.notification.StatusBarNotification +import androidx.annotation.WorkerThread import androidx.appcompat.view.ContextThemeWrapper import androidx.core.content.ContextCompat import me.leolin.shortcutbadger.ShortcutBadger @@ -114,10 +115,12 @@ class DefaultMessageNotifier(context: Application) : MessageNotifier { executor.cancel() } + @WorkerThread override fun updateNotification(context: Context) { updateNotification(context, null, BubbleState.HIDDEN) } + @WorkerThread override fun updateNotification(context: Context, conversationId: ConversationId) { if (System.currentTimeMillis() - lastDesktopActivityTimestamp < DESKTOP_ACTIVITY_PERIOD) { Log.i(TAG, "Scheduling delayed notification...") @@ -127,10 +130,12 @@ class DefaultMessageNotifier(context: Application) : MessageNotifier { } } + @WorkerThread override fun forceBubbleNotification(context: Context, conversationId: ConversationId) { updateNotification(context, conversationId, BubbleState.SHOWN) } + @WorkerThread private fun updateNotification( context: Context, conversationId: ConversationId?, diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt index 1d1347134c..75a271b146 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt @@ -11,6 +11,7 @@ import android.text.TextUtils import androidx.annotation.ColorInt import androidx.annotation.DrawableRes import androidx.annotation.StringRes +import androidx.annotation.WorkerThread import androidx.core.app.NotificationCompat import androidx.core.app.RemoteInput import androidx.core.content.LocusIdCompat @@ -83,6 +84,8 @@ sealed class NotificationBuilder(protected val context: Context) { protected abstract fun addMarkAsReadActionActual(state: NotificationState) protected abstract fun addMessagesActual(conversation: NotificationConversation, includeShortcut: Boolean) protected abstract fun addMessagesActual(state: NotificationState) + + @WorkerThread protected abstract fun setBubbleMetadataActual(conversation: NotificationConversation, bubbleState: BubbleUtil.BubbleState) protected abstract fun setLights(@ColorInt color: Int, onTime: Int, offTime: Int) @@ -154,6 +157,7 @@ sealed class NotificationBuilder(protected val context: Context) { addMessagesActual(state) } + @WorkerThread fun setBubbleMetadata(conversation: NotificationConversation, bubbleState: BubbleUtil.BubbleState) { if (privacy.isDisplayContact && isNotLocked) { setBubbleMetadataActual(conversation, bubbleState) @@ -360,15 +364,18 @@ sealed class NotificationBuilder(protected val context: Context) { } } + @WorkerThread override fun setBubbleMetadataActual(conversation: NotificationConversation, bubbleState: BubbleUtil.BubbleState) { if (Build.VERSION.SDK_INT < ConversationUtil.CONVERSATION_SUPPORT_VERSION) { return } + val wallpaper = conversation.recipient.wallpaper + wallpaper?.prefetch(context, 250) val intent: PendingIntent? = NotificationPendingIntentHelper.getActivity( context, 0, - ConversationIntents.createBubbleIntent(context, conversation.recipient.id, conversation.thread.threadId), + ConversationIntents.createBubbleIntent(context, conversation.recipient.id, conversation.thread.threadId, wallpaper != null), mutable() ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt index 4b52d24ea3..0c7ea10f94 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt @@ -176,7 +176,7 @@ data class NotificationConversation( } fun getQuickReplyIntent(context: Context): PendingIntent? { - val intent: Intent = ConversationIntents.createPopUpBuilder(context, recipient.id, mostRecentNotification.thread.threadId) + val intent: Intent = ConversationIntents.createPopUpBuilder(context, recipient.id, mostRecentNotification.thread.threadId, recipient.wallpaper != null) .build() .makeUniqueToPreventMerging() diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt index 95d5176e02..7c5cd9be1c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt @@ -11,6 +11,7 @@ import android.media.RingtoneManager import android.net.Uri import android.os.Build import android.os.TransactionTooLargeException +import androidx.annotation.WorkerThread import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat @@ -50,6 +51,7 @@ object NotificationFactory { private val STILL_DECRYPTING_INDIVIDUAL_THROTTLE: Duration = 5.seconds private val GROUP_THROTTLE: Duration = 20.seconds + @WorkerThread fun notify( context: Context, state: NotificationState, @@ -97,6 +99,7 @@ object NotificationFactory { } } + @WorkerThread private fun notify19( context: Context, state: NotificationState, @@ -144,6 +147,7 @@ object NotificationFactory { return threadsThatNewlyAlerted } + @WorkerThread @TargetApi(24) private fun notify24( context: Context, @@ -213,6 +217,7 @@ object NotificationFactory { return ((conversation.hasNewNotifications() && canAlertBasedOnTime) || alertOverride) && !conversation.mostRecentNotification.authorRecipient.isSelf } + @WorkerThread private fun notifyForConversation( context: Context, conversation: NotificationConversation, @@ -446,6 +451,7 @@ object NotificationFactory { NotificationManagerCompat.from(context).safelyNotify(recipient, NotificationIds.getNotificationIdForMessageDeliveryFailed(thread), builder.build()) } + @WorkerThread @JvmStatic fun notifyToBubbleConversation(context: Context, recipient: Recipient, threadId: Long) { val builder: NotificationBuilder = NotificationBuilder.create(context)