Implement new Material3 spec.

This commit is contained in:
Alex Hart
2022-05-26 17:32:52 -03:00
committed by Greyson Parrelli
parent 556e480b06
commit 1b471e163d
374 changed files with 3219 additions and 3049 deletions

View File

@@ -0,0 +1,32 @@
package org.thoughtcrime.securesms.conversation
import androidx.core.view.doOnNextLayout
import androidx.recyclerview.widget.RecyclerView
import org.signal.core.util.DimensionUnit
/**
* Adds necessary padding to each side of the given RecyclerView in order to ensure that
* if all buttons can fit in the visible real-estate on screen, they are centered.
*/
class AttachmentButtonCenterHelper(private val recyclerView: RecyclerView) : RecyclerView.AdapterDataObserver() {
private val itemWidth: Float = DimensionUnit.DP.toPixels(88f)
private val defaultPadding: Float = DimensionUnit.DP.toPixels(16f)
override fun onChanged() {
val itemCount = recyclerView.adapter?.itemCount ?: return
val requiredSpace = itemWidth * itemCount
recyclerView.doOnNextLayout {
if (it.measuredWidth >= requiredSpace) {
val extraSpace = it.measuredWidth - requiredSpace
val availablePadding = extraSpace / 2f
it.post {
it.setPadding(availablePadding.toInt(), it.paddingTop, availablePadding.toInt(), it.paddingBottom)
}
} else {
it.setPadding(defaultPadding.toInt(), it.paddingTop, defaultPadding.toInt(), it.paddingBottom)
}
}
}
}

View File

@@ -78,6 +78,8 @@ public class AttachmentKeyboard extends FrameLayout implements InputAwareLayout.
mediaList.setAdapter(mediaAdapter);
buttonList.setAdapter(buttonAdapter);
buttonAdapter.registerAdapterDataObserver(new AttachmentButtonCenterHelper(buttonList));
mediaList.setLayoutManager(new GridLayoutManager(context, 1, GridLayoutManager.HORIZONTAL, false));
buttonList.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));

View File

@@ -7,11 +7,11 @@ import org.thoughtcrime.securesms.R;
public enum AttachmentKeyboardButton {
GALLERY(R.string.AttachmentKeyboard_gallery, R.drawable.ic_photo_album_outline_32),
FILE(R.string.AttachmentKeyboard_file, R.drawable.ic_file_outline_32),
PAYMENT(R.string.AttachmentKeyboard_payment, R.drawable.ic_payments_32),
CONTACT(R.string.AttachmentKeyboard_contact, R.drawable.ic_contact_circle_outline_32),
LOCATION(R.string.AttachmentKeyboard_location, R.drawable.ic_location_outline_32);
GALLERY(R.string.AttachmentKeyboard_gallery, R.drawable.ic_gallery_outline_24),
FILE(R.string.AttachmentKeyboard_file, R.drawable.ic_file_outline_24),
PAYMENT(R.string.AttachmentKeyboard_payment, R.drawable.ic_payments_24),
CONTACT(R.string.AttachmentKeyboard_contact, R.drawable.ic_contact_outline_24),
LOCATION(R.string.AttachmentKeyboard_location, R.drawable.ic_location_outline_24);
private final int titleRes;
private final int iconRes;

View File

@@ -79,8 +79,8 @@ class AttachmentKeyboardButtonAdapter extends RecyclerView.Adapter<AttachmentKey
public ButtonViewHolder(@NonNull View itemView) {
super(itemView);
this.image = itemView.findViewById(R.id.attachment_button_image);
this.title = itemView.findViewById(R.id.attachment_button_title);
this.image = itemView.findViewById(R.id.icon);
this.title = itemView.findViewById(R.id.label);
}
void bind(@NonNull AttachmentKeyboardButton button, boolean wallpaperEnabled, @NonNull Listener listener) {
@@ -88,12 +88,6 @@ class AttachmentKeyboardButtonAdapter extends RecyclerView.Adapter<AttachmentKey
title.setText(button.getTitleRes());
itemView.setOnClickListener(v -> listener.onClick(button));
if (wallpaperEnabled) {
itemView.setBackgroundResource(R.drawable.attachment_keyboard_button_wallpaper_background);
} else {
itemView.setBackgroundResource(R.drawable.attachment_keyboard_button_background);
}
}
void recycle() {

View File

@@ -25,7 +25,6 @@ import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.AnyThread;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.LayoutRes;
@@ -40,7 +39,6 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.exoplayer2.MediaItem;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
import org.signal.paging.PagingController;
import org.thoughtcrime.securesms.BindableConversationItem;
@@ -65,10 +63,8 @@ import org.thoughtcrime.securesms.util.ViewUtil;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@@ -348,22 +344,22 @@ public class ConversationAdapter
if (type == HEADER_TYPE_POPOVER_DATE) {
if (hasWallpaper) {
viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_8);
viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_18);
} else {
viewHolder.setBackgroundRes(R.drawable.sticky_date_header_background);
}
} else if (type == HEADER_TYPE_INLINE_DATE) {
if (hasWallpaper) {
viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_8);
viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_18);
} else {
viewHolder.clearBackground();
}
}
if (hasWallpaper && ThemeUtil.isDarkTheme(context)) {
viewHolder.setTextColor(ContextCompat.getColor(context, R.color.core_grey_15));
viewHolder.setTextColor(ContextCompat.getColor(context, R.color.signal_colorNeutralInverse));
} else {
viewHolder.setTextColor(ContextCompat.getColor(context, R.color.signal_text_secondary));
viewHolder.setTextColor(ContextCompat.getColor(context, R.color.signal_colorOnSurfaceVariant));
}
}
@@ -400,7 +396,7 @@ public class ConversationAdapter
viewHolder.setText(viewHolder.itemView.getContext().getResources().getQuantityString(R.plurals.ConversationAdapter_n_unread_messages, count, count));
if (hasWallpaper) {
viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_8);
viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_18);
viewHolder.setDividerColor(viewHolder.itemView.getResources().getColor(R.color.transparent_black_80));
} else {
viewHolder.clearBackground();

View File

@@ -49,10 +49,12 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.WindowDecorActionBar;
import androidx.appcompat.view.ActionMode;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.content.ContextCompat;
import androidx.core.text.HtmlCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.ViewKt;
@@ -132,6 +134,7 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceViewOnceOpenJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.longmessage.LongMessageFragment;
import org.thoughtcrime.securesms.main.Material3OnScrollHelperBinder;
import org.thoughtcrime.securesms.messagedetails.MessageDetailsFragment;
import org.thoughtcrime.securesms.messagerequests.MessageRequestState;
import org.thoughtcrime.securesms.messagerequests.MessageRequestViewModel;
@@ -239,7 +242,6 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
private Animation mentionButtonOutAnimation;
private OnScrollListener conversationScrollListener;
private int lastSeenScrollOffset;
private View toolbarShadow;
private Stopwatch startupStopwatch;
private LayoutTransition layoutTransition;
private TransitionListener transitionListener;
@@ -290,7 +292,6 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
scrollToBottomButton = view.findViewById(R.id.scroll_to_bottom);
scrollToMentionButton = view.findViewById(R.id.scroll_to_mention);
scrollDateHeader = view.findViewById(R.id.scroll_date_header);
toolbarShadow = requireActivity().findViewById(R.id.conversation_toolbar_shadow);
reactionsShade = view.findViewById(R.id.reactions_shade);
bottomActionBar = view.findViewById(R.id.conversation_bottom_action_bar);
@@ -321,6 +322,8 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
list.addItemDecoration(multiselectItemDecoration);
list.setItemAnimator(conversationItemAnimator);
((Material3OnScrollHelperBinder) requireParentFragment()).bindScrollHelper(list);
getViewLifecycleOwner().getLifecycle().addObserver(multiselectItemDecoration);
snapToTopDataObserver = new ConversationSnapToTopDataObserver(list, new ConversationScrollRequestValidator());
@@ -352,7 +355,12 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
this.messageCountsViewModel = new ViewModelProvider(getParentFragment()).get(MessageCountsViewModel.class);
this.conversationViewModel = new ViewModelProvider(getParentFragment(), new ConversationViewModel.Factory()).get(ConversationViewModel.class);
disposables.add(conversationViewModel.getChatColors().subscribe(recyclerViewColorizer::setChatColors));
disposables.add(conversationViewModel.getChatColors().subscribe(chatColors -> {
recyclerViewColorizer.setChatColors(chatColors);
scrollToMentionButton.setUnreadCountBackgroundTint(chatColors.asSingleColor());
scrollToBottomButton.setUnreadCountBackgroundTint(chatColors.asSingleColor());
}));
disposables.add(conversationViewModel.getMessageData().subscribe(messageData -> {
SignalLocalMetrics.ConversationOpen.onDataPostedToMain();
@@ -428,7 +436,6 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
getChildFragmentManager().setFragmentResultListener(ViewReceivedGiftBottomSheet.REQUEST_KEY, getViewLifecycleOwner(), (key, bundle) -> {
if (bundle.getBoolean(ViewReceivedGiftBottomSheet.RESULT_NOT_NOW, false)) {
Snackbar.make(view.getRootView(), R.string.ConversationFragment__you_can_redeem_your_badge_later, Snackbar.LENGTH_SHORT)
.setTextColor(Color.WHITE)
.show();
}
});
@@ -544,6 +551,13 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
}
public void onWallpaperChanged(@Nullable ChatWallpaper wallpaper) {
if (scrollDateHeader != null) {
scrollDateHeader.setBackgroundResource(wallpaper != null ? R.drawable.sticky_date_header_background_wallpaper
: R.drawable.sticky_date_header_background);
scrollDateHeader.setTextColor(ContextCompat.getColor(requireContext(), wallpaper != null ? R.color.sticky_header_foreground_wallpaper
: R.color.signal_colorOnSurfaceVariant));
}
if (list != null) {
ConversationAdapter adapter = getListAdapter();
@@ -686,7 +700,6 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
conversationScrollListener = new ConversationScrollListener(requireContext());
list.addOnScrollListener(conversationScrollListener);
list.addOnScrollListener(new ShadowScrollListener());
if (oldThreadId != threadId) {
ApplicationDependencies.getTypingStatusRepository().getTypists(oldThreadId).removeObservers(getViewLifecycleOwner());
@@ -694,10 +707,6 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
}
private void initializeListAdapter() {
if (threadId == -1) {
toolbarShadow.setVisibility(View.GONE);
}
if (this.recipient != null) {
if (getListAdapter() != null && getListAdapter().isForRecipientId(this.recipient.getId())) {
Log.d(TAG, "List adapter already initialized for " + this.recipient.getId());
@@ -1069,7 +1078,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
}
private void handleDisplayDetails(ConversationMessage message) {
MessageDetailsFragment.create(message.getMessageRecord(), recipient.getId()).show(getChildFragmentManager(), null);
MessageDetailsFragment.create(message.getMessageRecord(), recipient.getId()).show(getParentFragment().getChildFragmentManager(), null);
}
private void handleForwardMessageParts(Set<MultiselectPart> multiselectParts) {
@@ -1301,9 +1310,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
if (!TextSecurePreferences.hasSeenSwipeToReplyTooltip(requireContext())) {
int text = ViewUtil.isLtr(requireContext()) ? R.string.ConversationFragment_you_can_swipe_to_the_right_reply
: R.string.ConversationFragment_you_can_swipe_to_the_left_reply;
Snackbar.make(list, text, Snackbar.LENGTH_LONG)
.setTextColor(Color.WHITE)
.show();
Snackbar.make(list, text, Snackbar.LENGTH_LONG).show();
TextSecurePreferences.setHasSeenSwipeToReplyTooltip(requireContext(), true);
}
@@ -1409,6 +1416,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
void setThreadId(long threadId);
void handleReplyMessage(ConversationMessage conversationMessage);
void onMessageActionToolbarOpened();
void onMessageActionToolbarClosed();
void onBottomActionBarVisibilityChanged(int visibility);
void onForwardClicked();
void onMessageRequest(@NonNull MessageRequestViewModel viewModel);
@@ -1513,7 +1521,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
if (actionMode != null) return;
MessageRecord messageRecord = item.getConversationMessage().getMessageRecord();;
MessageRecord messageRecord = item.getConversationMessage().getMessageRecord();
if (messageRecord.isSecure() &&
!messageRecord.isRemoteDelete() &&
@@ -2191,6 +2199,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
list.invalidateItemDecorations();
setBottomActionBarVisibility(false);
actionMode = null;
listener.onMessageActionToolbarClosed();
}
@Override
@@ -2242,21 +2251,6 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
}
}
private class ShadowScrollListener extends RecyclerView.OnScrollListener {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
if (recyclerView.canScrollVertically(-1)) {
if (toolbarShadow.getVisibility() != View.VISIBLE) {
ViewUtil.fadeIn(toolbarShadow, 250);
}
} else {
if (toolbarShadow.getVisibility() != View.GONE) {
ViewUtil.fadeOut(toolbarShadow, 250);
}
}
}
}
private static final class TransitionListener implements Animator.AnimatorListener {
private final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);

View File

@@ -22,6 +22,7 @@ import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Typeface;
@@ -569,6 +570,10 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (conversationRecipient.getId().equals(modified.getId())) {
setBubbleState(messageRecord, modified, modified.hasWallpaper(), colorizer);
if (quoteView != null) {
quoteView.setWallpaperEnabled(modified.hasWallpaper());
}
if (audioViewStub.resolved()) {
setAudioViewTint(messageRecord);
}
@@ -606,7 +611,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
}
private void initializeAttributes() {
defaultBubbleColor = ContextCompat.getColor(context, R.color.signal_background_secondary);
defaultBubbleColor = ContextCompat.getColor(context, R.color.signal_colorSurfaceVariant);
defaultBubbleColorForWallpaper = ContextCompat.getColor(context, R.color.conversation_item_wallpaper_bubble_color);
}
@@ -728,8 +733,8 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
this.hasWallpaper = hasWallpaper;
ViewUtil.updateLayoutParams(bodyBubble, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
bodyText.setTextColor(ContextCompat.getColor(getContext(), R.color.signal_text_primary));
bodyText.setLinkTextColor(ContextCompat.getColor(getContext(), R.color.signal_text_primary));
bodyText.setTextColor(colorizer.getIncomingBodyTextColor(context, hasWallpaper));
bodyText.setLinkTextColor(colorizer.getIncomingBodyTextColor(context, hasWallpaper));
if (messageRecord.isOutgoing() && !messageRecord.isRemoteDelete()) {
bodyBubble.getBackground().setColorFilter(recipient.getChatColors().getChatBubbleColorFilter());
@@ -751,9 +756,9 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
footer.setOnlyShowSendingStatus(messageRecord.isRemoteDelete(), messageRecord);
} else {
bodyBubble.getBackground().setColorFilter(getDefaultBubbleColor(hasWallpaper), PorterDuff.Mode.SRC_IN);
footer.setTextColor(ContextCompat.getColor(context, R.color.signal_text_secondary));
footer.setIconColor(ContextCompat.getColor(context, R.color.signal_text_secondary));
footer.setRevealDotColor(ContextCompat.getColor(context, R.color.signal_text_secondary));
footer.setTextColor(colorizer.getIncomingFooterTextColor(context, hasWallpaper));
footer.setIconColor(colorizer.getIncomingFooterIconColor(context, hasWallpaper));
footer.setRevealDotColor(colorizer.getIncomingFooterIconColor(context, hasWallpaper));
footer.setOnlyShowSendingStatus(false, messageRecord);
}
@@ -788,15 +793,16 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
private void setAudioViewTint(MessageRecord messageRecord) {
if (hasAudio(messageRecord)) {
if (!messageRecord.isOutgoing()) {
audioViewStub.get().setTint(getContext().getResources().getColor(R.color.conversation_item_incoming_audio_foreground_tint));
if (hasWallpaper) {
audioViewStub.get().setTint(getContext().getResources().getColor(R.color.conversation_item_incoming_audio_foreground_tint_wallpaper));
audioViewStub.get().setProgressAndPlayBackgroundTint(getContext().getResources().getColor(R.color.conversation_item_incoming_audio_play_pause_background_tint_wallpaper));
} else {
audioViewStub.get().setTint(getContext().getResources().getColor(R.color.conversation_item_incoming_audio_foreground_tint_normal));
audioViewStub.get().setProgressAndPlayBackgroundTint(getContext().getResources().getColor(R.color.conversation_item_incoming_audio_play_pause_background_tint_normal));
}
} else {
audioViewStub.get().setTint(Color.WHITE);
audioViewStub.get().setProgressAndPlayBackgroundTint(getContext().getResources().getColor(R.color.transparent_white_20));
audioViewStub.get().setTint(getContext().getResources().getColor(R.color.conversation_item_outgoing_audio_foreground_tint));
audioViewStub.get().setProgressAndPlayBackgroundTint(getContext().getResources().getColor(R.color.signal_colorTransparent2));
}
}
}
@@ -1089,6 +1095,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
linkPreviewStub.get().setOnClickListener(linkPreviewClickListener);
linkPreviewStub.get().setOnLongClickListener(passthroughClickListener);
linkPreviewStub.get().setBackgroundColor(getDefaultBubbleColor(hasWallpaper));
footer.setVisibility(VISIBLE);
} else if (hasAudio(messageRecord)) {
@@ -1470,10 +1477,10 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
quote.getDisplayText(),
quote.isOriginalMissing(),
quote.getAttachment(),
chatColors,
isStoryReaction(current) ? current.getBody() : null,
quote.getQuoteType());
quoteView.setWallpaperEnabled(hasWallpaper);
quoteView.setVisibility(View.VISIBLE);
quoteView.setTextSize(TypedValue.COMPLEX_UNIT_SP, SignalStore.settings().getMessageFontSize());
quoteView.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -1951,6 +1958,10 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
@Override
public @NonNull ProjectionList getColorizerProjections(@NonNull ViewGroup coordinateRoot) {
return getSnapshotProjections(coordinateRoot, true);
}
public @NonNull ProjectionList getSnapshotProjections(@NonNull ViewGroup coordinateRoot, boolean clipOutMedia) {
colorizerProjections.clear();
if (messageRecord.isOutgoing() &&
@@ -1961,6 +1972,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
{
Projection bodyBubbleToRoot = Projection.relativeToParent(coordinateRoot, bodyBubble, bodyBubbleCorners).translateX(bodyBubble.getTranslationX());
Projection videoToBubble = bodyBubble.getVideoPlayerProjection();
Projection mediaThumb = clipOutMedia && mediaThumbnailStub.resolved() ? Projection.relativeToParent(coordinateRoot, mediaThumbnailStub.require(), null) : null;
float translationX = Util.halfOffsetFromScale(bodyBubble.getWidth(), bodyBubble.getScaleX());
float translationY = Util.halfOffsetFromScale(bodyBubble.getHeight(), bodyBubble.getScaleY());
@@ -1981,6 +1993,13 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
}
colorizerProjections.addAll(projections);
} else if (hasThumbnail(messageRecord) && mediaThumb != null) {
colorizerProjections.add(
bodyBubbleToRoot.insetTop(mediaThumb.getHeight())
.scale(bodyBubble.getScaleX())
.translateX(translationX)
.translateY(translationY)
);
} else {
colorizerProjections.add(
bodyBubbleToRoot.scale(bodyBubble.getScaleX())
@@ -1988,6 +2007,10 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
.translateY(translationY)
);
}
if (mediaThumb != null) {
mediaThumb.release();
}
}
if (messageRecord.isOutgoing() &&
@@ -2007,21 +2030,6 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
}
}
if (!messageRecord.isOutgoing() &&
hasQuote(messageRecord) &&
quoteView != null &&
bodyBubble.getVisibility() == VISIBLE)
{
bodyBubble.setQuoteViewProjection(quoteView.getProjection(bodyBubble));
float bubbleOffsetFromScale = Util.halfOffsetFromScale(bodyBubble.getHeight(), bodyBubble.getScaleY());
Projection cProj = quoteView.getProjection(coordinateRoot)
.translateX(bodyBubble.getTranslationX() + this.getTranslationX() + Util.halfOffsetFromScale(quoteView.getWidth(), bodyBubble.getScaleX()))
.translateY(bubbleOffsetFromScale - quoteView.getY() + (quoteView.getY() * bodyBubble.getScaleY()))
.scale(bodyBubble.getScaleX());
colorizerProjections.add(cProj);
}
for (int i = 0; i < colorizerProjections.size(); i++) {
colorizerProjections.get(i).translateY(getTranslationY());
}

View File

@@ -48,7 +48,7 @@ object ConversationItemSelection {
bodyBubble.scaleX = 1.0f
bodyBubble.scaleY = 1.0f
val projections = conversationItem.getColorizerProjections(list)
val projections = conversationItem.getSnapshotProjections(list, false)
val path = Path()

View File

@@ -27,6 +27,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Color;
@@ -68,6 +69,7 @@ import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.ColorInt;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -81,11 +83,14 @@ import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.graphics.drawable.IconCompat;
import androidx.core.view.MenuItemCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.RecyclerView;
import com.airbnb.lottie.SimpleColorFilter;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
@@ -212,6 +217,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel;
import org.thoughtcrime.securesms.main.Material3OnScrollHelperBinder;
import org.thoughtcrime.securesms.maps.PlacePickerActivity;
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity;
import org.thoughtcrime.securesms.mediasend.Media;
@@ -282,6 +288,7 @@ import org.thoughtcrime.securesms.util.DrawableUtil;
import org.thoughtcrime.securesms.util.FullscreenHelper;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.LifecycleDisposable;
import org.thoughtcrime.securesms.util.Material3OnScrollHelper;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.MessageRecordUtil;
import org.thoughtcrime.securesms.util.MessageUtil;
@@ -319,6 +326,8 @@ import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import kotlin.Unit;
import static org.thoughtcrime.securesms.TransportOption.Type;
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
@@ -349,7 +358,9 @@ public class ConversationParentFragment extends Fragment
GifKeyboardPageFragment.Host,
EmojiKeyboardPageFragment.Callback,
EmojiSearchFragment.Callback,
StickerKeyboardPageFragment.Callback
StickerKeyboardPageFragment.Callback,
Material3OnScrollHelperBinder,
MessageDetailsFragment.Callback
{
private static final int SHORTCUT_ICON_SIZE = Build.VERSION.SDK_INT >= 26 ? ViewUtil.dpToPx(72) : ViewUtil.dpToPx(48 + 16 * 2);
@@ -419,6 +430,7 @@ public class ConversationParentFragment extends Fragment
private ImageView wallpaper;
private View wallpaperDim;
private Toolbar toolbar;
private View toolbarBackground;
private BroadcastReceiver pinnedShortcutReceiver;
private LinkPreviewViewModel linkPreviewViewModel;
@@ -433,7 +445,7 @@ public class ConversationParentFragment extends Fragment
private DraftViewModel draftViewModel;
private VoiceNoteMediaController voiceNoteMediaController;
private VoiceNotePlayerView voiceNotePlayerView;
private Material3OnScrollHelper material3OnScrollHelper;
private LiveRecipient recipient;
private long threadId;
@@ -1114,6 +1126,9 @@ public class ConversationParentFragment extends Fragment
}
super.onCreateOptionsMenu(menu, inflater);
int toolbarTextAndIconColor = getResources().getColor(wallpaper.getDrawable() != null ? R.color.signal_colorNeutralInverse : R.color.signal_colorOnSurface);
setToolbarActionItemTint(toolbar, toolbarTextAndIconColor);
}
public void invalidateOptionsMenu() {
@@ -1743,7 +1758,10 @@ public class ConversationParentFragment extends Fragment
}
}
noLongerMemberBanner.setVisibility(leftGroup ? View.VISIBLE : View.GONE);
if (messageRequestBottomView.getVisibility() == View.GONE) {
noLongerMemberBanner.setVisibility(leftGroup ? View.VISIBLE : View.GONE);
}
requestingMemberBanner.setVisibility(canCancelRequest ? View.VISIBLE : View.GONE);
if (canCancelRequest) {
@@ -2092,6 +2110,7 @@ public class ConversationParentFragment extends Fragment
private void initializeViews(View view) {
toolbar = view.findViewById(R.id.toolbar);
toolbarBackground = view.findViewById(R.id.toolbar_background);
titleView = view.findViewById(R.id.conversation_title_view);
buttonToggle = view.findViewById(R.id.button_toggle);
sendButton = view.findViewById(R.id.send_button);
@@ -2124,7 +2143,6 @@ public class ConversationParentFragment extends Fragment
Stub<ConversationReactionOverlay> reactionOverlayStub = ViewUtil.findStubById(view, R.id.conversation_reaction_scrubber_stub);
reactionDelegate = new ConversationReactionDelegate(reactionOverlayStub);
noLongerMemberBanner = view.findViewById(R.id.conversation_no_longer_member_banner);
cannotSendInAnnouncementGroupBanner = ViewUtil.findStubById(view, R.id.conversation_cannot_send_announcement_stub);
requestingMemberBanner = view.findViewById(R.id.conversation_requesting_banner);
@@ -2156,7 +2174,7 @@ public class ConversationParentFragment extends Fragment
linkPreviewViewModel.onTransportChanged(newTransport.isSms());
composeText.setTransport(newTransport);
buttonToggle.getBackground().setColorFilter(newTransport.getBackgroundColor(), PorterDuff.Mode.MULTIPLY);
buttonToggle.getBackground().setColorFilter(getButtonToggleBackgroundColor(newTransport), PorterDuff.Mode.MULTIPLY);
buttonToggle.getBackground().invalidateSelf();
if (manuallySelected) recordTransportPreference(newTransport);
@@ -2200,6 +2218,18 @@ public class ConversationParentFragment extends Fragment
});
voiceNoteMediaController.getVoiceNotePlaybackState().observe(getViewLifecycleOwner(), inputPanel.getPlaybackStateObserver());
material3OnScrollHelper = new Material3OnScrollHelper(Collections.singletonList(toolbarBackground), Collections.emptyList(), this::updateStatusBarColor);
}
private @ColorInt int getButtonToggleBackgroundColor(TransportOption newTransport) {
if (newTransport.isSms()) {
return newTransport.getBackgroundColor();
} else if (recipient != null) {
return getRecipient().getChatColors().asSingleColor();
} else {
return newTransport.getBackgroundColor();
}
}
private @NonNull VoiceNotePlayerView requireVoiceNotePlayerView() {
@@ -2221,12 +2251,12 @@ public class ConversationParentFragment extends Fragment
attachmentKeyboardStub.get().setWallpaperEnabled(true);
}
int toolbarColor = getResources().getColor(R.color.conversation_toolbar_color_wallpaper);
toolbar.setBackgroundColor(toolbarColor);
// TODO [alex] LargeScreenSupport -- statusBarBox
if (Build.VERSION.SDK_INT > 23) {
WindowUtil.setStatusBarColor(requireActivity().getWindow(), toolbarColor);
}
toolbarBackground.setBackgroundResource(R.color.material3_toolbar_background_wallpaper);
updateStatusBarColor(toolbarBackground.isActivated());
int toolbarTextAndIconColor = getResources().getColor(R.color.signal_colorNeutralInverse);
toolbar.setTitleTextColor(toolbarTextAndIconColor);
setToolbarActionItemTint(toolbar, toolbarTextAndIconColor);
} else {
wallpaper.setImageDrawable(null);
wallpaperDim.setVisibility(View.GONE);
@@ -2235,14 +2265,56 @@ public class ConversationParentFragment extends Fragment
attachmentKeyboardStub.get().setWallpaperEnabled(false);
}
int toolbarColor = getResources().getColor(R.color.conversation_toolbar_color);
toolbar.setBackgroundColor(toolbarColor);
// TODO [alex] LargeScreenSupport -- statusBarBox
if (Build.VERSION.SDK_INT > 23) {
WindowUtil.setStatusBarColor(requireActivity().getWindow(), toolbarColor);
}
toolbarBackground.setBackgroundResource(R.color.material3_toolbar_background);
updateStatusBarColor(toolbarBackground.isActivated());
int toolbarTextAndIconColor = getResources().getColor(R.color.signal_colorOnSurface);
toolbar.setTitleTextColor(toolbarTextAndIconColor);
setToolbarActionItemTint(toolbar, toolbarTextAndIconColor);
}
fragment.onWallpaperChanged(chatWallpaper);
messageRequestBottomView.setWallpaperEnabled(chatWallpaper != null);
}
private Unit updateStatusBarColor(boolean isActive) {
// TODO [alex] LargeScreenSupport -- statusBarBox
if (Build.VERSION.SDK_INT > 23) {
boolean hasWallpaper = wallpaper.getDrawable() != null;
int toolbarColor = isActive ? getActiveToolbarColor(requireContext(), hasWallpaper)
: getInactiveToolbarColor(requireContext(), hasWallpaper);
WindowUtil.setStatusBarColor(requireActivity().getWindow(), toolbarColor);
}
return Unit.INSTANCE;
}
private static @ColorInt int getActiveToolbarColor(@NonNull Context context, boolean hasWallpaper) {
int colorRes = hasWallpaper ? R.color.conversation_toolbar_color_wallpaper_scrolled
: R.color.signal_colorSurface2;
return ContextCompat.getColor(context, colorRes);
}
private static @ColorInt int getInactiveToolbarColor(@NonNull Context context, boolean hasWallpaper) {
int colorRes = hasWallpaper ? R.color.conversation_toolbar_color_wallpaper
: R.color.signal_colorBackground;
return ContextCompat.getColor(context, colorRes);
}
private void setToolbarActionItemTint(@NonNull Toolbar toolbar, @ColorInt int tint) {
for (int i = 0; i < toolbar.getMenu().size(); i++) {
MenuItem menuItem = toolbar.getMenu().getItem(i);
MenuItemCompat.setIconTintList(menuItem, ColorStateList.valueOf(tint));
}
if (toolbar.getNavigationIcon() != null) {
toolbar.getNavigationIcon().setColorFilter(new SimpleColorFilter(tint));
}
if (toolbar.getOverflowIcon() != null) {
toolbar.getOverflowIcon().setColorFilter(new SimpleColorFilter(tint));
}
}
protected void initializeActionBar() {
@@ -3547,6 +3619,16 @@ public class ConversationParentFragment extends Fragment
StickerSearchDialogFragment.show(getChildFragmentManager());
}
@Override
public void bindScrollHelper(@NonNull RecyclerView recyclerView) {
recyclerView.addOnScrollListener(material3OnScrollHelper);
}
@Override
public void onMessageDetailsFragmentDismissed() {
updateStatusBarColor(toolbarBackground.isActivated());
}
// Listeners
private final class DeleteCanceledVoiceNoteListener implements ListenableFuture.Listener<VoiceNoteDraft> {
@@ -3950,6 +4032,12 @@ public class ConversationParentFragment extends Fragment
@Override
public void onMessageActionToolbarOpened() {
searchViewItem.collapseActionView();
toolbar.setVisibility(View.GONE);
}
@Override
public void onMessageActionToolbarClosed() {
toolbar.setVisibility(View.VISIBLE);
}
@Override
@@ -4206,18 +4294,23 @@ public class ConversationParentFragment extends Fragment
{
Log.d(TAG, "[presentMessageRequestState] Have extra, so ignoring provided state.");
messageRequestBottomView.setVisibility(View.GONE);
inputPanel.setVisibility(View.VISIBLE);
} else if (isPushGroupV1Conversation() && !isActiveGroup()) {
Log.d(TAG, "[presentMessageRequestState] Inactive push group V1, so ignoring provided state.");
messageRequestBottomView.setVisibility(View.GONE);
inputPanel.setVisibility(View.VISIBLE);
} else if (messageData == null) {
Log.d(TAG, "[presentMessageRequestState] Null messageData. Ignoring.");
} else if (messageData.getMessageState() == MessageRequestState.NONE) {
Log.d(TAG, "[presentMessageRequestState] No message request necessary.");
messageRequestBottomView.setVisibility(View.GONE);
inputPanel.setVisibility(View.VISIBLE);
} else {
Log.d(TAG, "[presentMessageRequestState] " + messageData.getMessageState());
messageRequestBottomView.setMessageData(messageData);
messageRequestBottomView.setVisibility(View.VISIBLE);
noLongerMemberBanner.setVisibility(View.GONE);
inputPanel.setVisibility(View.GONE);
}
invalidateOptionsMenu();

View File

@@ -13,8 +13,10 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.cardview.widget.CardView;
import androidx.core.content.ContextCompat;
import androidx.core.content.res.ColorStateListInflaterCompat;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
@@ -175,14 +177,6 @@ public final class ConversationUpdateItem extends FrameLayout
textColor = ContextCompat.getColor(getContext(), R.color.core_grey_15);
}
if (!ThemeUtil.isDarkTheme(getContext())) {
if (hasWallpaper) {
actionButton.setStrokeColor(ColorStateList.valueOf(getResources().getColor(R.color.core_grey_45)));
} else {
actionButton.setStrokeColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_button_secondary_stroke)));
}
}
UpdateDescription updateDescription = Objects.requireNonNull(messageRecord.getUpdateDisplayBody(getContext(), eventListener::onRecipientNameClicked));
LiveData<SpannableString> liveUpdateMessage = LiveUpdateMessage.fromMessageDescription(getContext(), updateDescription, textColor, true);
LiveData<SpannableString> spannableMessage = loading(liveUpdateMessage);
@@ -195,6 +189,8 @@ public final class ConversationUpdateItem extends FrameLayout
shouldCollapse(messageRecord, nextMessageRecord),
hasWallpaper);
presentActionButton(hasWallpaper);
updateSelectedState();
}
@@ -645,6 +641,16 @@ public final class ConversationUpdateItem extends FrameLayout
}
}
private void presentActionButton(boolean hasWallpaper) {
if (hasWallpaper) {
actionButton.setBackgroundTintList(AppCompatResources.getColorStateList(getContext(), R.color.conversation_update_item_button_background_wallpaper));
actionButton.setTextColor(AppCompatResources.getColorStateList(getContext(), R.color.conversation_update_item_button_text_color_wallpaper));
} else {
actionButton.setBackgroundTintList(AppCompatResources.getColorStateList(getContext(), R.color.conversation_update_item_button_background_normal));
actionButton.setTextColor(AppCompatResources.getColorStateList(getContext(), R.color.conversation_update_item_button_text_color_normal));
}
}
private static boolean isSameType(@NonNull MessageRecord current, @NonNull MessageRecord candidate) {
return (current.isGroupUpdate() && candidate.isGroupUpdate()) ||
(current.isProfileChange() && candidate.isProfileChange()) ||

View File

@@ -16,7 +16,6 @@ import androidx.annotation.ColorInt
import com.google.common.base.Objects
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import org.signal.core.util.ColorUtil
import org.thoughtcrime.securesms.components.RotatableGradientDrawable
import org.thoughtcrime.securesms.database.model.databaseprotos.ChatColor
import org.thoughtcrime.securesms.util.customizeOnDraw
@@ -77,10 +76,7 @@ class ChatColors(
}
if (linearGradient != null) {
val start = linearGradient.colors.first()
val end = linearGradient.colors.last()
return ColorUtil.blendARGB(start, end, 0.5f)
return linearGradient.colors.last()
}
throw AssertionError()

View File

@@ -9,13 +9,9 @@ object ChatColorsPalette {
// region Default
@JvmField
val ULTRAMARINE = ChatColors.forGradient(
val ULTRAMARINE = ChatColors.forColor(
ChatColors.Id.BuiltIn,
ChatColors.LinearGradient(
180.0f,
intArrayOf(0xFF0552F0.toInt(), 0xFF2C6BED.toInt()),
floatArrayOf(0f, 1f)
)
0xFF315FF4.toInt()
)
// endregion

View File

@@ -22,17 +22,44 @@ class Colorizer {
@ColorInt
fun getOutgoingBodyTextColor(context: Context): Int {
return ContextCompat.getColor(context, R.color.white)
return ContextCompat.getColor(context, R.color.conversation_outgoing_body_color)
}
@ColorInt
fun getOutgoingFooterTextColor(context: Context): Int {
return ContextCompat.getColor(context, R.color.conversation_item_outgoing_footer_fg)
return ContextCompat.getColor(context, R.color.conversation_outgoing_footer_color)
}
@ColorInt
fun getOutgoingFooterIconColor(context: Context): Int {
return ContextCompat.getColor(context, R.color.conversation_item_outgoing_footer_fg)
return ContextCompat.getColor(context, R.color.conversation_outgoing_footer_color)
}
@ColorInt
fun getIncomingBodyTextColor(context: Context, hasWallpaper: Boolean): Int {
return if (hasWallpaper) {
ContextCompat.getColor(context, R.color.signal_colorNeutralInverse)
} else {
ContextCompat.getColor(context, R.color.signal_colorOnSurface)
}
}
@ColorInt
fun getIncomingFooterTextColor(context: Context, hasWallpaper: Boolean): Int {
return if (hasWallpaper) {
ContextCompat.getColor(context, R.color.signal_colorNeutralVariantInverse)
} else {
ContextCompat.getColor(context, R.color.signal_colorOnSurfaceVariant)
}
}
@ColorInt
fun getIncomingFooterIconColor(context: Context, hasWallpaper: Boolean): Int {
return if (hasWallpaper) {
ContextCompat.getColor(context, R.color.signal_colorNeutralVariantInverse)
} else {
ContextCompat.getColor(context, R.color.signal_colorOnSurfaceVariant)
}
}
@ColorInt