Fix stretchy chat colors on Android 12.
This commit is contained in:
parent
e637f15a43
commit
bad382e2f3
10 changed files with 173 additions and 90 deletions
|
@ -100,6 +100,10 @@ public final class RotatableGradientDrawable extends Drawable {
|
||||||
fillPaint.setShader(new LinearGradient(fillRect.left, fillRect.top, fillRect.right, fillRect.bottom, colors, positions, Shader.TileMode.CLAMP));
|
fillPaint.setShader(new LinearGradient(fillRect.left, fillRect.top, fillRect.right, fillRect.bottom, colors, positions, Shader.TileMode.CLAMP));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @Nullable Shader getShader() {
|
||||||
|
return fillPaint.getShader();
|
||||||
|
}
|
||||||
|
|
||||||
private static Point cornerPrime(@NonNull Point origin, @NonNull Point corner, float degrees) {
|
private static Point cornerPrime(@NonNull Point origin, @NonNull Point corner, float degrees) {
|
||||||
return new Point(xPrime(origin, corner, Math.toRadians(degrees)), yPrime(origin, corner, Math.toRadians(degrees)));
|
return new Point(xPrime(origin, corner, Math.toRadians(degrees)), yPrime(origin, corner, Math.toRadians(degrees)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ import org.thoughtcrime.securesms.contactshare.SharedContactDetailsActivity;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener;
|
import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationAdapter.StickyHeaderViewHolder;
|
import org.thoughtcrime.securesms.conversation.ConversationAdapter.StickyHeaderViewHolder;
|
||||||
import org.thoughtcrime.securesms.conversation.colors.Colorizer;
|
import org.thoughtcrime.securesms.conversation.colors.Colorizer;
|
||||||
import org.thoughtcrime.securesms.conversation.colors.ColorizerView;
|
import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer;
|
||||||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectItemAnimator;
|
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectItemAnimator;
|
||||||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectItemDecoration;
|
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectItemDecoration;
|
||||||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart;
|
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart;
|
||||||
|
@ -218,7 +218,6 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
||||||
private OnScrollListener conversationScrollListener;
|
private OnScrollListener conversationScrollListener;
|
||||||
private int pulsePosition = -1;
|
private int pulsePosition = -1;
|
||||||
private View toolbarShadow;
|
private View toolbarShadow;
|
||||||
private ColorizerView colorizerView;
|
|
||||||
private Stopwatch startupStopwatch;
|
private Stopwatch startupStopwatch;
|
||||||
|
|
||||||
private GiphyMp4ProjectionRecycler giphyMp4ProjectionRecycler;
|
private GiphyMp4ProjectionRecycler giphyMp4ProjectionRecycler;
|
||||||
|
@ -256,10 +255,8 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
||||||
scrollToMentionButton = view.findViewById(R.id.scroll_to_mention);
|
scrollToMentionButton = view.findViewById(R.id.scroll_to_mention);
|
||||||
scrollDateHeader = view.findViewById(R.id.scroll_date_header);
|
scrollDateHeader = view.findViewById(R.id.scroll_date_header);
|
||||||
toolbarShadow = requireActivity().findViewById(R.id.conversation_toolbar_shadow);
|
toolbarShadow = requireActivity().findViewById(R.id.conversation_toolbar_shadow);
|
||||||
colorizerView = view.findViewById(R.id.conversation_colorizer_view);
|
|
||||||
|
|
||||||
ConversationIntents.Args args = ConversationIntents.Args.from(requireActivity().getIntent());
|
ConversationIntents.Args args = ConversationIntents.Args.from(requireActivity().getIntent());
|
||||||
colorizerView.setBackground(args.getChatColors().getChatBubbleMask());
|
|
||||||
|
|
||||||
final LinearLayoutManager layoutManager = new SmoothScrollingLinearLayoutManager(getActivity(), true);
|
final LinearLayoutManager layoutManager = new SmoothScrollingLinearLayoutManager(getActivity(), true);
|
||||||
final MultiselectItemAnimator multiselectItemAnimator = new MultiselectItemAnimator(() -> {
|
final MultiselectItemAnimator multiselectItemAnimator = new MultiselectItemAnimator(() -> {
|
||||||
|
@ -351,10 +348,10 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
||||||
|
|
||||||
updateToolbarDependentMargins();
|
updateToolbarDependentMargins();
|
||||||
|
|
||||||
colorizer = new Colorizer(colorizerView);
|
colorizer = new Colorizer();
|
||||||
colorizer.attachToRecyclerView(list);
|
RecyclerViewColorizer recyclerViewColorizer = new RecyclerViewColorizer(list);
|
||||||
|
|
||||||
conversationViewModel.getChatColors().observe(getViewLifecycleOwner(), chatColors -> colorizer.onChatColorsChanged(chatColors));
|
conversationViewModel.getChatColors().observe(getViewLifecycleOwner(), recyclerViewColorizer::setChatColors);
|
||||||
conversationViewModel.getNameColorsMap().observe(getViewLifecycleOwner(), nameColorsMap -> {
|
conversationViewModel.getNameColorsMap().observe(getViewLifecycleOwner(), nameColorsMap -> {
|
||||||
colorizer.onNameColorsChanged(nameColorsMap);
|
colorizer.onNameColorsChanged(nameColorsMap);
|
||||||
|
|
||||||
|
@ -412,12 +409,10 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
||||||
private void setListVerticalTranslation() {
|
private void setListVerticalTranslation() {
|
||||||
if (list.canScrollVertically(1) || list.canScrollVertically(-1) || list.getChildCount() == 0) {
|
if (list.canScrollVertically(1) || list.canScrollVertically(-1) || list.getChildCount() == 0) {
|
||||||
list.setTranslationY(0);
|
list.setTranslationY(0);
|
||||||
colorizerView.setTranslationY(0);
|
|
||||||
list.setOverScrollMode(RecyclerView.OVER_SCROLL_IF_CONTENT_SCROLLS);
|
list.setOverScrollMode(RecyclerView.OVER_SCROLL_IF_CONTENT_SCROLLS);
|
||||||
} else {
|
} else {
|
||||||
int chTop = list.getChildAt(list.getChildCount() - 1).getTop();
|
int chTop = list.getChildAt(list.getChildCount() - 1).getTop();
|
||||||
list.setTranslationY(Math.min(0, -chTop));
|
list.setTranslationY(Math.min(0, -chTop));
|
||||||
colorizerView.setTranslationY(Math.min(0, -chTop));
|
|
||||||
list.setOverScrollMode(RecyclerView.OVER_SCROLL_NEVER);
|
list.setOverScrollMode(RecyclerView.OVER_SCROLL_NEVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -539,10 +534,6 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
||||||
if (viewHolder instanceof GiphyMp4Playable) {
|
if (viewHolder instanceof GiphyMp4Playable) {
|
||||||
giphyMp4ProjectionRecycler.updateVideoDisplayPositionAndSize(recyclerView, (GiphyMp4Playable) viewHolder);
|
giphyMp4ProjectionRecycler.updateVideoDisplayPositionAndSize(recyclerView, (GiphyMp4Playable) viewHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (colorizer != null) {
|
|
||||||
colorizer.applyClipPathsToMaskedGradient(recyclerView);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getStartPosition() {
|
private int getStartPosition() {
|
||||||
|
|
|
@ -1718,7 +1718,8 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||||
if (messageRecord.isOutgoing() &&
|
if (messageRecord.isOutgoing() &&
|
||||||
!hasNoBubble(messageRecord) &&
|
!hasNoBubble(messageRecord) &&
|
||||||
!messageRecord.isRemoteDelete() &&
|
!messageRecord.isRemoteDelete() &&
|
||||||
bodyBubbleCorners != null)
|
bodyBubbleCorners != null &&
|
||||||
|
bodyBubble.getProjections().isEmpty())
|
||||||
{
|
{
|
||||||
projections.add(Projection.relativeToViewRoot(bodyBubble, bodyBubbleCorners).translateX(bodyBubble.getTranslationX()));
|
projections.add(Projection.relativeToViewRoot(bodyBubble, bodyBubbleCorners).translateX(bodyBubble.getTranslationX()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.graphics.ColorFilter
|
||||||
import android.graphics.Path
|
import android.graphics.Path
|
||||||
import android.graphics.PorterDuff
|
import android.graphics.PorterDuff
|
||||||
import android.graphics.PorterDuffColorFilter
|
import android.graphics.PorterDuffColorFilter
|
||||||
|
import android.graphics.Shader
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.drawable.ShapeDrawable
|
import android.graphics.drawable.ShapeDrawable
|
||||||
|
@ -53,6 +54,18 @@ class ChatColors private constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun asShader(left: Int, top: Int, right: Int, bottom: Int): Shader? {
|
||||||
|
return linearGradient?.let {
|
||||||
|
RotatableGradientDrawable(
|
||||||
|
linearGradient.degrees,
|
||||||
|
linearGradient.colors,
|
||||||
|
linearGradient.positions
|
||||||
|
).apply {
|
||||||
|
setBounds(left, top, right, bottom)
|
||||||
|
}
|
||||||
|
}?.shader
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the ColorFilter to apply to a conversation bubble or other relevant piece of UI.
|
* Returns the ColorFilter to apply to a conversation bubble or other relevant piece of UI.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -2,16 +2,11 @@ package org.thoughtcrime.securesms.conversation.colors
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Build
|
|
||||||
import android.view.View
|
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
import org.thoughtcrime.securesms.util.Projection
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class for all things ChatColors.
|
* Helper class for all things ChatColors.
|
||||||
|
@ -20,7 +15,7 @@ import org.thoughtcrime.securesms.util.Projection
|
||||||
* - Gives easy access to different bubble colors
|
* - Gives easy access to different bubble colors
|
||||||
* - Watches and responds to RecyclerView scroll and layout changes to update a ColorizerView
|
* - Watches and responds to RecyclerView scroll and layout changes to update a ColorizerView
|
||||||
*/
|
*/
|
||||||
class Colorizer(private val colorizerView: ColorizerView) : RecyclerView.OnScrollListener(), View.OnLayoutChangeListener {
|
class Colorizer {
|
||||||
|
|
||||||
private var colorsHaveBeenSet = false
|
private var colorsHaveBeenSet = false
|
||||||
private val groupSenderColors: MutableMap<RecipientId, NameColor> = mutableMapOf()
|
private val groupSenderColors: MutableMap<RecipientId, NameColor> = mutableMapOf()
|
||||||
|
@ -43,55 +38,12 @@ class Colorizer(private val colorizerView: ColorizerView) : RecyclerView.OnScrol
|
||||||
@ColorInt
|
@ColorInt
|
||||||
fun getIncomingGroupSenderColor(context: Context, recipient: Recipient): Int = groupSenderColors[recipient.id]?.getColor(context) ?: getDefaultColor(context, recipient.id)
|
fun getIncomingGroupSenderColor(context: Context, recipient: Recipient): Int = groupSenderColors[recipient.id]?.getColor(context) ?: getDefaultColor(context, recipient.id)
|
||||||
|
|
||||||
fun attachToRecyclerView(recyclerView: RecyclerView) {
|
|
||||||
recyclerView.addOnScrollListener(this)
|
|
||||||
recyclerView.addOnLayoutChangeListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onNameColorsChanged(nameColorMap: Map<RecipientId, NameColor>) {
|
fun onNameColorsChanged(nameColorMap: Map<RecipientId, NameColor>) {
|
||||||
groupSenderColors.clear()
|
groupSenderColors.clear()
|
||||||
groupSenderColors.putAll(nameColorMap)
|
groupSenderColors.putAll(nameColorMap)
|
||||||
colorsHaveBeenSet = true
|
colorsHaveBeenSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onChatColorsChanged(chatColors: ChatColors) {
|
|
||||||
colorizerView.background = chatColors.chatBubbleMask
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
|
||||||
applyClipPathsToMaskedGradient(recyclerView)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLayoutChange(v: View?, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
|
|
||||||
applyClipPathsToMaskedGradient(v as RecyclerView)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun applyClipPathsToMaskedGradient(recyclerView: RecyclerView) {
|
|
||||||
if (Build.VERSION.SDK_INT < 21) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
|
|
||||||
|
|
||||||
val firstVisibleItemPosition: Int = layoutManager.findFirstVisibleItemPosition()
|
|
||||||
val lastVisibleItemPosition: Int = layoutManager.findLastVisibleItemPosition()
|
|
||||||
|
|
||||||
val projections: List<Projection> = (firstVisibleItemPosition..lastVisibleItemPosition)
|
|
||||||
.mapNotNull { recyclerView.findViewHolderForAdapterPosition(it) as? Colorizable }
|
|
||||||
.map {
|
|
||||||
it.colorizerProjections
|
|
||||||
.map { p -> Projection.translateFromRootToDescendantCoords(p, colorizerView) }
|
|
||||||
}
|
|
||||||
.flatten()
|
|
||||||
|
|
||||||
if (projections.isNotEmpty()) {
|
|
||||||
colorizerView.visibility = View.VISIBLE
|
|
||||||
colorizerView.setProjections(projections)
|
|
||||||
} else {
|
|
||||||
colorizerView.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ColorInt
|
@ColorInt
|
||||||
private fun getDefaultColor(context: Context, recipientId: RecipientId): Int {
|
private fun getDefaultColor(context: Context, recipientId: RecipientId): Int {
|
||||||
return if (colorsHaveBeenSet) {
|
return if (colorsHaveBeenSet) {
|
||||||
|
|
|
@ -0,0 +1,142 @@
|
||||||
|
package org.thoughtcrime.securesms.conversation.colors
|
||||||
|
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.graphics.PorterDuffXfermode
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.EdgeEffect
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the ChatColors color or gradient following this procedure:
|
||||||
|
*
|
||||||
|
* 1. Have the RecyclerView's ItemDecoration#onDraw method, fill the bounds of the RecyclerView with the background color or drawable
|
||||||
|
* 2. Have each child item draw the bubble shape with the "clear" blend mode to "hole punch" a region within the background already drawn by the RecyclerView
|
||||||
|
* 3. In the RecyclerView's ItemDecoration#onDrawOver method, draw the gradient with the full bounds of the RecyclerView using the DST_OVER blend mode. This will draw the gradient "underneath" the background rendered in step 1 however will show portions of the gradient in the areas "cleared" by the rendering in step 2
|
||||||
|
*/
|
||||||
|
class RecyclerViewColorizer(private val recyclerView: RecyclerView) {
|
||||||
|
|
||||||
|
private var topEdgeEffect: EdgeEffect? = null
|
||||||
|
private var bottomEdgeEffect: EdgeEffect? = null
|
||||||
|
|
||||||
|
private fun getLayoutManager(): LinearLayoutManager = recyclerView.layoutManager as LinearLayoutManager
|
||||||
|
|
||||||
|
private var useLayer = false
|
||||||
|
|
||||||
|
private val noLayerXfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OVER)
|
||||||
|
private val layerXfermode = PorterDuffXfermode(PorterDuff.Mode.XOR)
|
||||||
|
|
||||||
|
private var chatColors: ChatColors? = null
|
||||||
|
|
||||||
|
fun setChatColors(chatColors: ChatColors) {
|
||||||
|
this.chatColors = chatColors
|
||||||
|
recyclerView.invalidateItemDecorations()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val edgeEffectFactory = object : RecyclerView.EdgeEffectFactory() {
|
||||||
|
override fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect {
|
||||||
|
val edgeEffect = super.createEdgeEffect(view, direction)
|
||||||
|
when (direction) {
|
||||||
|
DIRECTION_TOP -> topEdgeEffect = edgeEffect
|
||||||
|
DIRECTION_BOTTOM -> bottomEdgeEffect = edgeEffect
|
||||||
|
DIRECTION_LEFT -> Unit
|
||||||
|
DIRECTION_RIGHT -> Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
return edgeEffect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val scrollListener = object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
|
||||||
|
val firstItemPos = getLayoutManager().findFirstVisibleItemPosition()
|
||||||
|
val lastItemPos = getLayoutManager().findLastVisibleItemPosition()
|
||||||
|
val itemCount = getLayoutManager().itemCount
|
||||||
|
val firstVisible = firstItemPos == 0 && itemCount >= 1
|
||||||
|
val lastVisible = lastItemPos == itemCount - 1 && itemCount >= 1
|
||||||
|
|
||||||
|
if (firstVisible || lastVisible || isOverscrolled()) {
|
||||||
|
useLayer = true
|
||||||
|
recyclerView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
|
||||||
|
} else {
|
||||||
|
useLayer = false
|
||||||
|
recyclerView.setLayerType(View.LAYER_TYPE_NONE, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val itemDecoration = object : RecyclerView.ItemDecoration() {
|
||||||
|
private val holePunchPaint = Paint().apply {
|
||||||
|
xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
|
||||||
|
color = Color.BLACK
|
||||||
|
}
|
||||||
|
|
||||||
|
private val shaderPaint = Paint()
|
||||||
|
private val colorPaint = Paint()
|
||||||
|
|
||||||
|
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
|
||||||
|
outRect.setEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
||||||
|
super.onDraw(c, parent, state)
|
||||||
|
|
||||||
|
val colors = chatColors ?: return
|
||||||
|
|
||||||
|
if (useLayer) {
|
||||||
|
c.drawColor(Color.WHITE)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in 0 until parent.childCount) {
|
||||||
|
val child = parent.getChildAt(i)
|
||||||
|
if (child != null && child is Colorizable) {
|
||||||
|
child.colorizerProjections.forEach {
|
||||||
|
c.drawPath(it.path, holePunchPaint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawShaderMask(c, parent, colors)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawShaderMask(canvas: Canvas, parent: RecyclerView, chatColors: ChatColors) {
|
||||||
|
if (useLayer) {
|
||||||
|
shaderPaint.xfermode = layerXfermode
|
||||||
|
colorPaint.xfermode = layerXfermode
|
||||||
|
} else {
|
||||||
|
shaderPaint.xfermode = noLayerXfermode
|
||||||
|
colorPaint.xfermode = noLayerXfermode
|
||||||
|
}
|
||||||
|
|
||||||
|
val shader = chatColors.asShader(0, 0, parent.width, parent.height)
|
||||||
|
shaderPaint.shader = shader
|
||||||
|
colorPaint.color = chatColors.asSingleColor()
|
||||||
|
|
||||||
|
canvas.drawRect(
|
||||||
|
0f,
|
||||||
|
0f,
|
||||||
|
parent.width.toFloat(),
|
||||||
|
parent.height.toFloat(),
|
||||||
|
if (shader == null) colorPaint else shaderPaint
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
recyclerView.edgeEffectFactory = edgeEffectFactory
|
||||||
|
recyclerView.addOnScrollListener(scrollListener)
|
||||||
|
recyclerView.addItemDecoration(itemDecoration)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isOverscrolled(): Boolean {
|
||||||
|
val topFinished = topEdgeEffect?.isFinished ?: true
|
||||||
|
val bottomFinished = bottomEdgeEffect?.isFinished ?: true
|
||||||
|
return !topFinished || !bottomFinished
|
||||||
|
}
|
||||||
|
}
|
|
@ -101,7 +101,7 @@ class ChatColorPreviewView @JvmOverloads constructor(
|
||||||
wallpaper = findViewById(R.id.wallpaper)
|
wallpaper = findViewById(R.id.wallpaper)
|
||||||
wallpaperDim = findViewById(R.id.wallpaper_dim)
|
wallpaperDim = findViewById(R.id.wallpaper_dim)
|
||||||
colorizerView = findViewById(R.id.colorizer)
|
colorizerView = findViewById(R.id.colorizer)
|
||||||
colorizer = Colorizer(colorizerView)
|
colorizer = Colorizer()
|
||||||
} finally {
|
} finally {
|
||||||
typedArray?.recycle()
|
typedArray?.recycle()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import android.view.View;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
import androidx.lifecycle.ViewModelProviders;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
@ -16,7 +15,7 @@ import org.thoughtcrime.securesms.PassphraseRequiredActivity;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.components.recyclerview.ToolbarShadowAnimationHelper;
|
import org.thoughtcrime.securesms.components.recyclerview.ToolbarShadowAnimationHelper;
|
||||||
import org.thoughtcrime.securesms.conversation.colors.Colorizer;
|
import org.thoughtcrime.securesms.conversation.colors.Colorizer;
|
||||||
import org.thoughtcrime.securesms.conversation.colors.ColorizerView;
|
import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer;
|
||||||
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog;
|
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog;
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
|
@ -46,6 +45,7 @@ public final class MessageDetailsActivity extends PassphraseRequiredActivity {
|
||||||
private MessageDetailsViewModel viewModel;
|
private MessageDetailsViewModel viewModel;
|
||||||
private MessageDetailsAdapter adapter;
|
private MessageDetailsAdapter adapter;
|
||||||
private Colorizer colorizer;
|
private Colorizer colorizer;
|
||||||
|
private RecyclerViewColorizer recyclerViewColorizer;
|
||||||
|
|
||||||
private DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
private DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||||
|
|
||||||
|
@ -100,16 +100,15 @@ public final class MessageDetailsActivity extends PassphraseRequiredActivity {
|
||||||
|
|
||||||
private void initializeList() {
|
private void initializeList() {
|
||||||
RecyclerView list = findViewById(R.id.message_details_list);
|
RecyclerView list = findViewById(R.id.message_details_list);
|
||||||
ColorizerView colorizerView = findViewById(R.id.message_details_colorizer);
|
|
||||||
View toolbarShadow = findViewById(R.id.toolbar_shadow);
|
View toolbarShadow = findViewById(R.id.toolbar_shadow);
|
||||||
|
|
||||||
colorizer = new Colorizer(colorizerView);
|
colorizer = new Colorizer();
|
||||||
adapter = new MessageDetailsAdapter(this, glideRequests, colorizer, this::onErrorClicked);
|
adapter = new MessageDetailsAdapter(this, glideRequests, colorizer, this::onErrorClicked);
|
||||||
|
recyclerViewColorizer = new RecyclerViewColorizer(list);
|
||||||
|
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
list.setItemAnimator(null);
|
list.setItemAnimator(null);
|
||||||
list.addOnScrollListener(new ToolbarShadowAnimationHelper(toolbarShadow));
|
list.addOnScrollListener(new ToolbarShadowAnimationHelper(toolbarShadow));
|
||||||
colorizer.attachToRecyclerView(list);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeViewModel() {
|
private void initializeViewModel() {
|
||||||
|
@ -126,7 +125,7 @@ public final class MessageDetailsActivity extends PassphraseRequiredActivity {
|
||||||
adapter.submitList(convertToRows(details));
|
adapter.submitList(convertToRows(details));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
viewModel.getRecipient().observe(this, recipient -> colorizer.onChatColorsChanged(recipient.getChatColors()));
|
viewModel.getRecipient().observe(this, recipient -> recyclerViewColorizer.setChatColors(recipient.getChatColors()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeVideoPlayer() {
|
private void initializeVideoPlayer() {
|
||||||
|
|
|
@ -6,16 +6,6 @@
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.colors.ColorizerView
|
|
||||||
android:id="@+id/conversation_colorizer_view"
|
|
||||||
android:background="@drawable/test_gradient"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@android:id/list"
|
|
||||||
app:layout_constraintEnd_toEndOf="@android:id/list"
|
|
||||||
app:layout_constraintStart_toStartOf="@android:id/list"
|
|
||||||
app:layout_constraintTop_toTopOf="@android:id/list" />
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/video_container"
|
android:id="@+id/video_container"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
|
|
@ -19,15 +19,6 @@
|
||||||
app:navigationContentDescription="@string/DSLSettingsToolbar__navigate_up"
|
app:navigationContentDescription="@string/DSLSettingsToolbar__navigate_up"
|
||||||
tools:title="Message Details" />
|
tools:title="Message Details" />
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.colors.ColorizerView
|
|
||||||
android:id="@+id/message_details_colorizer"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/toolbar" />
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/video_container"
|
android:id="@+id/video_container"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
|
Loading…
Add table
Reference in a new issue