Add ability to open a chat incognito.

This commit is contained in:
Greyson Parrelli
2026-03-18 09:16:19 -04:00
committed by Michelle Tang
parent db5cced91b
commit b62b5ea8ef
18 changed files with 118 additions and 12 deletions

View File

@@ -37,7 +37,8 @@ data class ConversationArgs(
val isWithSearchOpen: Boolean,
val giftBadge: Badge?,
val shareDataTimestamp: Long,
val conversationScreenType: ConversationScreenType
val conversationScreenType: ConversationScreenType,
val isIncognito: Boolean = false
) : Parcelable {
@IgnoredOnParcel
val draftMediaType: SlideFactory.MediaType? = SlideFactory.MediaType.from(draftContentType)

View File

@@ -47,6 +47,7 @@ public class ConversationIntents {
private static final String EXTRA_GIFT_BADGE = "gift_badge";
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 INTENT_DATA = "intent_data";
private static final String INTENT_TYPE = "intent_type";
@@ -152,7 +153,8 @@ public class ConversationIntents {
false,
null,
-1L,
ConversationScreenType.BUBBLE);
ConversationScreenType.BUBBLE,
false);
}
return new ConversationArgs(RecipientId.from(Objects.requireNonNull(arguments.getString(EXTRA_RECIPIENT))),
@@ -169,7 +171,8 @@ public class ConversationIntents {
arguments.getBoolean(EXTRA_WITH_SEARCH_OPEN, false),
arguments.getParcelable(EXTRA_GIFT_BADGE),
arguments.getLong(EXTRA_SHARE_DATA_TIMESTAMP, -1L),
ConversationScreenType.from(arguments.getInt(EXTRA_CONVERSATION_TYPE, 0)));
ConversationScreenType.from(arguments.getInt(EXTRA_CONVERSATION_TYPE, 0)),
arguments.getBoolean(EXTRA_INCOGNITO, false));
}
public final static class Builder {
@@ -191,6 +194,7 @@ public class ConversationIntents {
private boolean withSearchOpen;
private Badge giftBadge;
private long shareDataTimestamp = -1L;
private boolean incognito;
private Builder(@NonNull Context context,
@NonNull Class<? extends Activity> conversationActivityClass,
@@ -218,6 +222,7 @@ public class ConversationIntents {
withSearchOpen = args.isWithSearchOpen();
giftBadge = args.getGiftBadge();
shareDataTimestamp = args.getShareDataTimestamp();
incognito = args.isIncognito();
return this;
}
@@ -282,6 +287,11 @@ public class ConversationIntents {
return this;
}
public @NonNull Builder asIncognito(boolean incognito) {
this.incognito = incognito;
return this;
}
public @NonNull ConversationArgs toConversationArgs() {
return new ConversationArgs(
recipientId,
@@ -298,7 +308,8 @@ public class ConversationIntents {
withSearchOpen,
giftBadge,
shareDataTimestamp,
conversationScreenType
conversationScreenType,
incognito
);
}
@@ -329,6 +340,7 @@ public class ConversationIntents {
intent.putExtra(EXTRA_GIFT_BADGE, giftBadge);
intent.putExtra(EXTRA_SHARE_DATA_TIMESTAMP, shareDataTimestamp);
intent.putExtra(EXTRA_CONVERSATION_TYPE, conversationScreenType.code);
intent.putExtra(EXTRA_INCOGNITO, incognito);
if (draftText != null) {
intent.putExtra(EXTRA_TEXT, draftText);

View File

@@ -42,18 +42,24 @@ public class MarkReadHelper {
private final ConversationId conversationId;
private final Context context;
private final LifecycleOwner lifecycleOwner;
private final boolean incognito;
private final Debouncer debouncer = new Debouncer(DEBOUNCE_TIMEOUT);
private long latestTimestamp;
private boolean ignoreViewReveals = false;
public MarkReadHelper(@NonNull ConversationId conversationId, @NonNull Context context, @NonNull LifecycleOwner lifecycleOwner) {
this(conversationId, context, lifecycleOwner, false);
}
public MarkReadHelper(@NonNull ConversationId conversationId, @NonNull Context context, @NonNull LifecycleOwner lifecycleOwner, boolean incognito) {
this.conversationId = conversationId;
this.context = context.getApplicationContext();
this.lifecycleOwner = lifecycleOwner;
this.incognito = incognito;
}
public void onViewsRevealed(long timestamp) {
if (timestamp <= latestTimestamp || lifecycleOwner.getLifecycle().getCurrentState() != Lifecycle.State.RESUMED || ignoreViewReveals) {
if (incognito || timestamp <= latestTimestamp || lifecycleOwner.getLifecycle().getCurrentState() != Lifecycle.State.RESUMED || ignoreViewReveals) {
return;
}

View File

@@ -659,7 +659,7 @@ class ConversationFragment :
FullscreenHelper(requireActivity()).showSystemUI()
}
markReadHelper = MarkReadHelper(ConversationId.forConversation(args.threadId), requireContext(), viewLifecycleOwner)
markReadHelper = MarkReadHelper(ConversationId.forConversation(args.threadId), requireContext(), viewLifecycleOwner, args.isIncognito)
markReadHelper.ignoreViewReveals()
attachmentManager = AttachmentManager(requireContext(), requireView(), AttachmentManagerListener())
@@ -670,7 +670,8 @@ class ConversationFragment :
requireActivity(),
binding.toolbarBackground,
viewModel::wallpaperSnapshot,
viewLifecycleOwner
viewLifecycleOwner,
incognito = args.isIncognito
)
conversationToolbarOnScrollHelper.attach(binding.conversationItemRecycler)
presentWallpaper(args.wallpaper)
@@ -1482,6 +1483,7 @@ class ConversationFragment :
var inputDisabled = true
when {
inputReadyState.isClientExpired || inputReadyState.isUnauthorized -> disabledInputView.showAsExpiredOrUnauthorized(inputReadyState.isClientExpired, inputReadyState.isUnauthorized)
args.isIncognito -> disabledInputView.showAsIncognito()
!inputReadyState.messageRequestState.isAccepted -> disabledInputView.showAsMessageRequest(inputReadyState.conversationRecipient, inputReadyState.messageRequestState)
inputReadyState.isActiveGroup == false -> disabledInputView.showAsNoLongerAMember()
inputReadyState.isRequestingMember == true -> disabledInputView.showAsRequestingMember()

View File

@@ -16,7 +16,8 @@ class ConversationToolbarOnScrollHelper(
activity: FragmentActivity,
toolbarBackground: View,
private val wallpaperProvider: () -> ChatWallpaper?,
lifecycleOwner: LifecycleOwner
lifecycleOwner: LifecycleOwner,
private val incognito: Boolean = false
) : Material3OnScrollHelper(
activity = activity,
views = listOf(toolbarBackground),
@@ -24,10 +25,10 @@ class ConversationToolbarOnScrollHelper(
setStatusBarColor = {}
) {
override val activeColorSet: ColorSet
get() = ColorSet(getActiveToolbarColor(wallpaperProvider() != null))
get() = if (incognito) ColorSet(R.color.conversation_toolbar_color_incognito) else ColorSet(getActiveToolbarColor(wallpaperProvider() != null))
override val inactiveColorSet: ColorSet
get() = ColorSet(getInactiveToolbarColor(wallpaperProvider() != null))
get() = if (incognito) ColorSet(R.color.conversation_toolbar_color_incognito) else ColorSet(getInactiveToolbarColor(wallpaperProvider() != null))
@ColorRes
private fun getActiveToolbarColor(hasWallpaper: Boolean): Int {

View File

@@ -48,6 +48,7 @@ class DisabledInputView @JvmOverloads constructor(
private var announcementGroupOnly: TextView? = null
private var inviteToSignal: View? = null
private var releaseNoteChannel: View? = null
private var incognitoView: View? = null
private var currentView: View? = null
@@ -76,6 +77,13 @@ class DisabledInputView @JvmOverloads constructor(
)
}
fun showAsIncognito() {
incognitoView = show(
existingView = incognitoView,
create = { inflater.inflate(R.layout.conversation_incognito_mode, this, false) }
)
}
fun showAsMessageRequest(recipient: Recipient, messageRequestState: MessageRequestState) {
messageRequestView = show(
existingView = messageRequestView,
@@ -210,6 +218,7 @@ class DisabledInputView @JvmOverloads constructor(
noLongerAMember = null
requestingGroup = null
announcementGroupOnly = null
incognitoView = null
}
private fun <V : View> show(existingView: V?, create: () -> V, bind: V.() -> Unit = {}): V {