Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2017 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License |
| 15 | */ |
| 16 | |
| 17 | package com.android.internal.widget; |
| 18 | |
| 19 | import android.annotation.AttrRes; |
| 20 | import android.annotation.NonNull; |
| 21 | import android.annotation.Nullable; |
| 22 | import android.annotation.StyleRes; |
Selim Cinek | 9acd673 | 2018-03-23 16:39:02 -0700 | [diff] [blame] | 23 | import android.app.Person; |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 24 | import android.content.Context; |
Kenny Guy | a0f6de8 | 2018-04-06 16:20:16 +0100 | [diff] [blame] | 25 | import android.content.res.ColorStateList; |
| 26 | import android.graphics.Color; |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 27 | import android.graphics.Point; |
| 28 | import android.graphics.Rect; |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 29 | import android.graphics.drawable.Icon; |
Selim Cinek | 1d6b50e | 2017-10-27 16:10:57 -0700 | [diff] [blame] | 30 | import android.text.TextUtils; |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 31 | import android.util.AttributeSet; |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 32 | import android.util.DisplayMetrics; |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 33 | import android.util.Pools; |
Kenny Guy | a0f6de8 | 2018-04-06 16:20:16 +0100 | [diff] [blame] | 34 | import android.util.TypedValue; |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 35 | import android.view.LayoutInflater; |
| 36 | import android.view.View; |
| 37 | import android.view.ViewGroup; |
| 38 | import android.view.ViewParent; |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 39 | import android.view.ViewTreeObserver; |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 40 | import android.widget.ImageView; |
| 41 | import android.widget.LinearLayout; |
Kenny Guy | a0f6de8 | 2018-04-06 16:20:16 +0100 | [diff] [blame] | 42 | import android.widget.ProgressBar; |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 43 | import android.widget.RemoteViews; |
| 44 | |
| 45 | import com.android.internal.R; |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 46 | |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 47 | import java.util.ArrayList; |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 48 | import java.util.List; |
| 49 | |
| 50 | /** |
| 51 | * A message of a {@link MessagingLayout}. |
| 52 | */ |
| 53 | @RemoteViews.RemoteView |
| 54 | public class MessagingGroup extends LinearLayout implements MessagingLinearLayout.MessagingChild { |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 55 | private static Pools.SimplePool<MessagingGroup> sInstancePool |
| 56 | = new Pools.SynchronizedPool<>(10); |
| 57 | private MessagingLinearLayout mMessageContainer; |
| 58 | private ImageFloatingTextView mSenderName; |
| 59 | private ImageView mAvatarView; |
| 60 | private String mAvatarSymbol = ""; |
| 61 | private int mLayoutColor; |
| 62 | private CharSequence mAvatarName = ""; |
| 63 | private Icon mAvatarIcon; |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 64 | private int mTextColor; |
Kenny Guy | a0f6de8 | 2018-04-06 16:20:16 +0100 | [diff] [blame] | 65 | private int mSendingTextColor; |
Selim Cinek | 1d6b50e | 2017-10-27 16:10:57 -0700 | [diff] [blame] | 66 | private List<MessagingMessage> mMessages; |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 67 | private ArrayList<MessagingMessage> mAddedMessages = new ArrayList<>(); |
| 68 | private boolean mFirstLayout; |
| 69 | private boolean mIsHidingAnimated; |
Selim Cinek | afeed29 | 2017-12-12 17:32:44 -0800 | [diff] [blame] | 70 | private boolean mNeedsGeneratedAvatar; |
Selim Cinek | 9acd673 | 2018-03-23 16:39:02 -0700 | [diff] [blame] | 71 | private Person mSender; |
Selim Cinek | 85d0e6e | 2018-03-23 18:08:32 -0700 | [diff] [blame] | 72 | private boolean mImagesAtEnd; |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 73 | private ViewGroup mImageContainer; |
| 74 | private MessagingImageMessage mIsolatedMessage; |
| 75 | private boolean mTransformingImages; |
| 76 | private Point mDisplaySize = new Point(); |
Kenny Guy | a0f6de8 | 2018-04-06 16:20:16 +0100 | [diff] [blame] | 77 | private ProgressBar mSendingSpinner; |
| 78 | private View mSendingSpinnerContainer; |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 79 | |
| 80 | public MessagingGroup(@NonNull Context context) { |
| 81 | super(context); |
| 82 | } |
| 83 | |
| 84 | public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs) { |
| 85 | super(context, attrs); |
| 86 | } |
| 87 | |
| 88 | public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs, |
| 89 | @AttrRes int defStyleAttr) { |
| 90 | super(context, attrs, defStyleAttr); |
| 91 | } |
| 92 | |
| 93 | public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs, |
| 94 | @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { |
| 95 | super(context, attrs, defStyleAttr, defStyleRes); |
| 96 | } |
| 97 | |
| 98 | @Override |
| 99 | protected void onFinishInflate() { |
| 100 | super.onFinishInflate(); |
| 101 | mMessageContainer = findViewById(R.id.group_message_container); |
| 102 | mSenderName = findViewById(R.id.message_name); |
| 103 | mAvatarView = findViewById(R.id.message_icon); |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 104 | mImageContainer = findViewById(R.id.messaging_group_icon_container); |
Kenny Guy | a0f6de8 | 2018-04-06 16:20:16 +0100 | [diff] [blame] | 105 | mSendingSpinner = findViewById(R.id.messaging_group_sending_progress); |
| 106 | mSendingSpinnerContainer = findViewById(R.id.messaging_group_sending_progress_container); |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 107 | DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); |
| 108 | mDisplaySize.x = displayMetrics.widthPixels; |
| 109 | mDisplaySize.y = displayMetrics.heightPixels; |
| 110 | } |
| 111 | |
| 112 | public void updateClipRect() { |
| 113 | // We want to clip to the senderName if it's available, otherwise our images will come |
| 114 | // from a weird position |
| 115 | Rect clipRect; |
| 116 | if (mSenderName.getVisibility() != View.GONE && !mTransformingImages) { |
| 117 | ViewGroup parent = (ViewGroup) mSenderName.getParent(); |
| 118 | int top = getDistanceFromParent(mSenderName, parent) - getDistanceFromParent( |
| 119 | mMessageContainer, parent) + mSenderName.getHeight(); |
Selim Cinek | 1110fd7 | 2018-05-01 10:53:26 -0700 | [diff] [blame] | 120 | int size = Math.max(mDisplaySize.x, mDisplaySize.y); |
| 121 | clipRect = new Rect(0, top, size, size); |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 122 | } else { |
| 123 | clipRect = null; |
| 124 | } |
| 125 | mMessageContainer.setClipBounds(clipRect); |
| 126 | } |
| 127 | |
| 128 | private int getDistanceFromParent(View searchedView, ViewGroup parent) { |
| 129 | int position = 0; |
| 130 | View view = searchedView; |
| 131 | while(view != parent) { |
| 132 | position += view.getTop() + view.getTranslationY(); |
| 133 | view = (View) view.getParent(); |
| 134 | } |
| 135 | return position; |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 136 | } |
| 137 | |
Selim Cinek | 9acd673 | 2018-03-23 16:39:02 -0700 | [diff] [blame] | 138 | public void setSender(Person sender, CharSequence nameOverride) { |
Selim Cinek | 1397ea3 | 2018-01-16 17:34:52 -0800 | [diff] [blame] | 139 | mSender = sender; |
Selim Cinek | 2dd3e72 | 2018-01-19 11:06:06 -0800 | [diff] [blame] | 140 | if (nameOverride == null) { |
| 141 | nameOverride = sender.getName(); |
| 142 | } |
| 143 | mSenderName.setText(nameOverride); |
Selim Cinek | afeed29 | 2017-12-12 17:32:44 -0800 | [diff] [blame] | 144 | mNeedsGeneratedAvatar = sender.getIcon() == null; |
| 145 | if (!mNeedsGeneratedAvatar) { |
| 146 | setAvatar(sender.getIcon()); |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 147 | } |
Selim Cinek | afeed29 | 2017-12-12 17:32:44 -0800 | [diff] [blame] | 148 | mAvatarView.setVisibility(VISIBLE); |
Selim Cinek | 0f6a0d0 | 2018-05-04 16:22:05 -0700 | [diff] [blame] | 149 | mSenderName.setVisibility(TextUtils.isEmpty(nameOverride) ? GONE : VISIBLE); |
Kenny Guy | a0f6de8 | 2018-04-06 16:20:16 +0100 | [diff] [blame] | 150 | } |
| 151 | |
| 152 | public void setSending(boolean sending) { |
| 153 | int visibility = sending ? View.VISIBLE : View.GONE; |
| 154 | if (mSendingSpinnerContainer.getVisibility() != visibility) { |
| 155 | mSendingSpinnerContainer.setVisibility(visibility); |
| 156 | updateMessageColor(); |
| 157 | } |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 158 | } |
| 159 | |
Kenny Guy | a0f6de8 | 2018-04-06 16:20:16 +0100 | [diff] [blame] | 160 | private int calculateSendingTextColor() { |
| 161 | TypedValue alphaValue = new TypedValue(); |
| 162 | mContext.getResources().getValue( |
| 163 | R.dimen.notification_secondary_text_disabled_alpha, alphaValue, true); |
| 164 | float alpha = alphaValue.getFloat(); |
| 165 | return Color.valueOf( |
| 166 | Color.red(mTextColor), |
| 167 | Color.green(mTextColor), |
| 168 | Color.blue(mTextColor), |
| 169 | alpha).toArgb(); |
| 170 | } |
| 171 | |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 172 | public void setAvatar(Icon icon) { |
| 173 | mAvatarIcon = icon; |
| 174 | mAvatarView.setImageIcon(icon); |
| 175 | mAvatarSymbol = ""; |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 176 | mAvatarName = ""; |
| 177 | } |
| 178 | |
| 179 | static MessagingGroup createGroup(MessagingLinearLayout layout) {; |
| 180 | MessagingGroup createdGroup = sInstancePool.acquire(); |
| 181 | if (createdGroup == null) { |
| 182 | createdGroup = (MessagingGroup) LayoutInflater.from(layout.getContext()).inflate( |
| 183 | R.layout.notification_template_messaging_group, layout, |
| 184 | false); |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 185 | createdGroup.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR); |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 186 | } |
| 187 | layout.addView(createdGroup); |
| 188 | return createdGroup; |
| 189 | } |
| 190 | |
| 191 | public void removeMessage(MessagingMessage messagingMessage) { |
Selim Cinek | f68af9b | 2018-05-24 16:37:22 -0700 | [diff] [blame] | 192 | View view = messagingMessage.getView(); |
| 193 | boolean wasShown = view.isShown(); |
| 194 | ViewGroup messageParent = (ViewGroup) view.getParent(); |
| 195 | if (messageParent == null) { |
| 196 | return; |
| 197 | } |
| 198 | messageParent.removeView(view); |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 199 | Runnable recycleRunnable = () -> { |
Selim Cinek | f68af9b | 2018-05-24 16:37:22 -0700 | [diff] [blame] | 200 | messageParent.removeTransientView(view); |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 201 | messagingMessage.recycle(); |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 202 | }; |
Selim Cinek | f68af9b | 2018-05-24 16:37:22 -0700 | [diff] [blame] | 203 | if (wasShown && !MessagingLinearLayout.isGone(view)) { |
| 204 | messageParent.addTransientView(view, 0); |
| 205 | performRemoveAnimation(view, recycleRunnable); |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 206 | } else { |
| 207 | recycleRunnable.run(); |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 208 | } |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 209 | } |
| 210 | |
Selim Cinek | f68af9b | 2018-05-24 16:37:22 -0700 | [diff] [blame] | 211 | public void recycle() { |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 212 | if (mIsolatedMessage != null) { |
Selim Cinek | f68af9b | 2018-05-24 16:37:22 -0700 | [diff] [blame] | 213 | mImageContainer.removeView(mIsolatedMessage); |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 214 | } |
Selim Cinek | f68af9b | 2018-05-24 16:37:22 -0700 | [diff] [blame] | 215 | for (int i = 0; i < mMessages.size(); i++) { |
| 216 | MessagingMessage message = mMessages.get(i); |
| 217 | mMessageContainer.removeView(message.getView()); |
| 218 | message.recycle(); |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 219 | } |
Selim Cinek | f68af9b | 2018-05-24 16:37:22 -0700 | [diff] [blame] | 220 | setAvatar(null); |
| 221 | mAvatarView.setAlpha(1.0f); |
| 222 | mAvatarView.setTranslationY(0.0f); |
| 223 | mSenderName.setAlpha(1.0f); |
| 224 | mSenderName.setTranslationY(0.0f); |
| 225 | setAlpha(1.0f); |
| 226 | mIsolatedMessage = null; |
| 227 | mMessages = null; |
| 228 | mAddedMessages.clear(); |
| 229 | mFirstLayout = true; |
| 230 | MessagingPropertyAnimator.recycle(this); |
| 231 | sInstancePool.release(MessagingGroup.this); |
| 232 | } |
| 233 | |
| 234 | public void removeGroupAnimated(Runnable endAction) { |
| 235 | performRemoveAnimation(this, () -> { |
| 236 | setAlpha(1.0f); |
| 237 | MessagingPropertyAnimator.setToLaidOutPosition(this); |
| 238 | if (endAction != null) { |
| 239 | endAction.run(); |
| 240 | } |
| 241 | }); |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 242 | } |
| 243 | |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 244 | public void performRemoveAnimation(View message, Runnable endAction) { |
Selim Cinek | f68af9b | 2018-05-24 16:37:22 -0700 | [diff] [blame] | 245 | performRemoveAnimation(message, -message.getHeight(), endAction); |
| 246 | } |
| 247 | |
| 248 | private void performRemoveAnimation(View view, int disappearTranslation, Runnable endAction) { |
| 249 | MessagingPropertyAnimator.startLocalTranslationTo(view, disappearTranslation, |
| 250 | MessagingLayout.FAST_OUT_LINEAR_IN); |
| 251 | MessagingPropertyAnimator.fadeOut(view, endAction); |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 252 | } |
| 253 | |
| 254 | public CharSequence getSenderName() { |
| 255 | return mSenderName.getText(); |
| 256 | } |
| 257 | |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 258 | public static void dropCache() { |
| 259 | sInstancePool = new Pools.SynchronizedPool<>(10); |
| 260 | } |
| 261 | |
| 262 | @Override |
| 263 | public int getMeasuredType() { |
Selim Cinek | 02fde73 | 2018-05-22 16:30:56 -0700 | [diff] [blame] | 264 | if (mIsolatedMessage != null) { |
| 265 | // We only want to show one group if we have an inline image, so let's return shortened |
| 266 | // to avoid displaying the other ones. |
| 267 | return MEASURED_SHORTENED; |
| 268 | } |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 269 | boolean hasNormal = false; |
| 270 | for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) { |
| 271 | View child = mMessageContainer.getChildAt(i); |
Selim Cinek | 5572657 | 2018-05-02 17:22:20 -0700 | [diff] [blame] | 272 | if (child.getVisibility() == GONE) { |
| 273 | continue; |
| 274 | } |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 275 | if (child instanceof MessagingLinearLayout.MessagingChild) { |
| 276 | int type = ((MessagingLinearLayout.MessagingChild) child).getMeasuredType(); |
Selim Cinek | 5572657 | 2018-05-02 17:22:20 -0700 | [diff] [blame] | 277 | boolean tooSmall = type == MEASURED_TOO_SMALL; |
| 278 | final MessagingLinearLayout.LayoutParams lp = |
| 279 | (MessagingLinearLayout.LayoutParams) child.getLayoutParams(); |
| 280 | tooSmall |= lp.hide; |
| 281 | if (tooSmall) { |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 282 | if (hasNormal) { |
| 283 | return MEASURED_SHORTENED; |
| 284 | } else { |
| 285 | return MEASURED_TOO_SMALL; |
| 286 | } |
| 287 | } else if (type == MEASURED_SHORTENED) { |
| 288 | return MEASURED_SHORTENED; |
| 289 | } else { |
| 290 | hasNormal = true; |
| 291 | } |
| 292 | } |
| 293 | } |
| 294 | return MEASURED_NORMAL; |
| 295 | } |
| 296 | |
| 297 | @Override |
| 298 | public int getConsumedLines() { |
| 299 | int result = 0; |
| 300 | for (int i = 0; i < mMessageContainer.getChildCount(); i++) { |
| 301 | View child = mMessageContainer.getChildAt(i); |
| 302 | if (child instanceof MessagingLinearLayout.MessagingChild) { |
| 303 | result += ((MessagingLinearLayout.MessagingChild) child).getConsumedLines(); |
| 304 | } |
| 305 | } |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 306 | result = mIsolatedMessage != null ? Math.max(result, 1) : result; |
Selim Cinek | e62255c | 2017-09-28 18:23:23 -0700 | [diff] [blame] | 307 | // A group is usually taking up quite some space with the padding and the name, let's add 1 |
| 308 | return result + 1; |
| 309 | } |
| 310 | |
| 311 | @Override |
| 312 | public void setMaxDisplayedLines(int lines) { |
| 313 | mMessageContainer.setMaxDisplayedLines(lines); |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 314 | } |
| 315 | |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 316 | @Override |
| 317 | public void hideAnimated() { |
| 318 | setIsHidingAnimated(true); |
| 319 | removeGroupAnimated(() -> setIsHidingAnimated(false)); |
| 320 | } |
| 321 | |
| 322 | @Override |
| 323 | public boolean isHidingAnimated() { |
| 324 | return mIsHidingAnimated; |
| 325 | } |
| 326 | |
| 327 | private void setIsHidingAnimated(boolean isHiding) { |
| 328 | ViewParent parent = getParent(); |
| 329 | mIsHidingAnimated = isHiding; |
| 330 | invalidate(); |
| 331 | if (parent instanceof ViewGroup) { |
| 332 | ((ViewGroup) parent).invalidate(); |
| 333 | } |
| 334 | } |
| 335 | |
Selim Cinek | f68af9b | 2018-05-24 16:37:22 -0700 | [diff] [blame] | 336 | @Override |
| 337 | public boolean hasOverlappingRendering() { |
| 338 | return false; |
| 339 | } |
| 340 | |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 341 | public Icon getAvatarSymbolIfMatching(CharSequence avatarName, String avatarSymbol, |
| 342 | int layoutColor) { |
| 343 | if (mAvatarName.equals(avatarName) && mAvatarSymbol.equals(avatarSymbol) |
| 344 | && layoutColor == mLayoutColor) { |
| 345 | return mAvatarIcon; |
| 346 | } |
| 347 | return null; |
| 348 | } |
| 349 | |
| 350 | public void setCreatedAvatar(Icon cachedIcon, CharSequence avatarName, String avatarSymbol, |
| 351 | int layoutColor) { |
| 352 | if (!mAvatarName.equals(avatarName) || !mAvatarSymbol.equals(avatarSymbol) |
| 353 | || layoutColor != mLayoutColor) { |
| 354 | setAvatar(cachedIcon); |
| 355 | mAvatarSymbol = avatarSymbol; |
Kenny Guy | a0f6de8 | 2018-04-06 16:20:16 +0100 | [diff] [blame] | 356 | setLayoutColor(layoutColor); |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 357 | mAvatarName = avatarName; |
| 358 | } |
| 359 | } |
| 360 | |
Kenny Guy | 14d035c | 2018-05-02 19:10:36 +0100 | [diff] [blame] | 361 | public void setTextColors(int senderTextColor, int messageTextColor) { |
| 362 | mTextColor = messageTextColor; |
| 363 | mSendingTextColor = calculateSendingTextColor(); |
| 364 | updateMessageColor(); |
| 365 | mSenderName.setTextColor(senderTextColor); |
| 366 | } |
| 367 | |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 368 | public void setLayoutColor(int layoutColor) { |
Kenny Guy | a0f6de8 | 2018-04-06 16:20:16 +0100 | [diff] [blame] | 369 | if (layoutColor != mLayoutColor){ |
| 370 | mLayoutColor = layoutColor; |
| 371 | mSendingSpinner.setIndeterminateTintList(ColorStateList.valueOf(mLayoutColor)); |
| 372 | } |
| 373 | } |
| 374 | |
| 375 | private void updateMessageColor() { |
| 376 | if (mMessages != null) { |
| 377 | int color = mSendingSpinnerContainer.getVisibility() == View.VISIBLE |
| 378 | ? mSendingTextColor : mTextColor; |
| 379 | for (MessagingMessage message : mMessages) { |
| 380 | message.setColor(message.getMessage().isRemoteInputHistory() ? color : mTextColor); |
| 381 | } |
| 382 | } |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 383 | } |
| 384 | |
| 385 | public void setMessages(List<MessagingMessage> group) { |
| 386 | // Let's now make sure all children are added and in the correct order |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 387 | int textMessageIndex = 0; |
| 388 | MessagingImageMessage isolatedMessage = null; |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 389 | for (int messageIndex = 0; messageIndex < group.size(); messageIndex++) { |
| 390 | MessagingMessage message = group.get(messageIndex); |
| 391 | if (message.getGroup() != this) { |
| 392 | message.setMessagingGroup(this); |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 393 | mAddedMessages.add(message); |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 394 | } |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 395 | boolean isImage = message instanceof MessagingImageMessage; |
Selim Cinek | 85d0e6e | 2018-03-23 18:08:32 -0700 | [diff] [blame] | 396 | if (mImagesAtEnd && isImage) { |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 397 | isolatedMessage = (MessagingImageMessage) message; |
| 398 | } else { |
| 399 | if (removeFromParentIfDifferent(message, mMessageContainer)) { |
| 400 | ViewGroup.LayoutParams layoutParams = message.getView().getLayoutParams(); |
| 401 | if (layoutParams != null |
| 402 | && !(layoutParams instanceof MessagingLinearLayout.LayoutParams)) { |
| 403 | message.getView().setLayoutParams( |
| 404 | mMessageContainer.generateDefaultLayoutParams()); |
| 405 | } |
| 406 | mMessageContainer.addView(message.getView(), textMessageIndex); |
| 407 | } |
| 408 | if (isImage) { |
| 409 | ((MessagingImageMessage) message).setIsolated(false); |
| 410 | } |
| 411 | // Let's sort them properly |
| 412 | if (textMessageIndex != mMessageContainer.indexOfChild(message.getView())) { |
| 413 | mMessageContainer.removeView(message.getView()); |
| 414 | mMessageContainer.addView(message.getView(), textMessageIndex); |
| 415 | } |
| 416 | textMessageIndex++; |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 417 | } |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 418 | } |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 419 | if (isolatedMessage != null) { |
| 420 | if (removeFromParentIfDifferent(isolatedMessage, mImageContainer)) { |
| 421 | mImageContainer.removeAllViews(); |
| 422 | mImageContainer.addView(isolatedMessage.getView()); |
| 423 | } |
| 424 | isolatedMessage.setIsolated(true); |
| 425 | } else if (mIsolatedMessage != null) { |
| 426 | mImageContainer.removeAllViews(); |
| 427 | } |
| 428 | mIsolatedMessage = isolatedMessage; |
Selim Cinek | 1c72fa0 | 2018-04-23 18:00:54 +0800 | [diff] [blame] | 429 | updateImageContainerVisibility(); |
Selim Cinek | 1d6b50e | 2017-10-27 16:10:57 -0700 | [diff] [blame] | 430 | mMessages = group; |
Kenny Guy | a0f6de8 | 2018-04-06 16:20:16 +0100 | [diff] [blame] | 431 | updateMessageColor(); |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 432 | } |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 433 | |
Selim Cinek | 1c72fa0 | 2018-04-23 18:00:54 +0800 | [diff] [blame] | 434 | private void updateImageContainerVisibility() { |
| 435 | mImageContainer.setVisibility(mIsolatedMessage != null && mImagesAtEnd |
| 436 | ? View.VISIBLE : View.GONE); |
| 437 | } |
| 438 | |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 439 | /** |
| 440 | * Remove the message from the parent if the parent isn't the one provided |
| 441 | * @return whether the message was removed |
| 442 | */ |
| 443 | private boolean removeFromParentIfDifferent(MessagingMessage message, ViewGroup newParent) { |
| 444 | ViewParent parent = message.getView().getParent(); |
| 445 | if (parent != newParent) { |
| 446 | if (parent instanceof ViewGroup) { |
| 447 | ((ViewGroup) parent).removeView(message.getView()); |
| 448 | } |
| 449 | return true; |
| 450 | } |
| 451 | return false; |
| 452 | } |
| 453 | |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 454 | @Override |
| 455 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { |
| 456 | super.onLayout(changed, left, top, right, bottom); |
| 457 | if (!mAddedMessages.isEmpty()) { |
Selim Cinek | f68af9b | 2018-05-24 16:37:22 -0700 | [diff] [blame] | 458 | final boolean firstLayout = mFirstLayout; |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 459 | getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { |
| 460 | @Override |
| 461 | public boolean onPreDraw() { |
| 462 | for (MessagingMessage message : mAddedMessages) { |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 463 | if (!message.getView().isShown()) { |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 464 | continue; |
| 465 | } |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 466 | MessagingPropertyAnimator.fadeIn(message.getView()); |
Selim Cinek | f68af9b | 2018-05-24 16:37:22 -0700 | [diff] [blame] | 467 | if (!firstLayout) { |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 468 | MessagingPropertyAnimator.startLocalTranslationFrom(message.getView(), |
| 469 | message.getView().getHeight(), |
| 470 | MessagingLayout.LINEAR_OUT_SLOW_IN); |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 471 | } |
| 472 | } |
| 473 | mAddedMessages.clear(); |
| 474 | getViewTreeObserver().removeOnPreDrawListener(this); |
| 475 | return true; |
| 476 | } |
| 477 | }); |
| 478 | } |
| 479 | mFirstLayout = false; |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 480 | updateClipRect(); |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 481 | } |
| 482 | |
Selim Cinek | 1d6b50e | 2017-10-27 16:10:57 -0700 | [diff] [blame] | 483 | /** |
| 484 | * Calculates the group compatibility between this and another group. |
| 485 | * |
| 486 | * @param otherGroup the other group to compare it with |
| 487 | * |
| 488 | * @return 0 if the groups are totally incompatible or 1 + the number of matching messages if |
| 489 | * they match. |
| 490 | */ |
| 491 | public int calculateGroupCompatibility(MessagingGroup otherGroup) { |
| 492 | if (TextUtils.equals(getSenderName(),otherGroup.getSenderName())) { |
| 493 | int result = 1; |
| 494 | for (int i = 0; i < mMessages.size() && i < otherGroup.mMessages.size(); i++) { |
| 495 | MessagingMessage ownMessage = mMessages.get(mMessages.size() - 1 - i); |
| 496 | MessagingMessage otherMessage = otherGroup.mMessages.get( |
| 497 | otherGroup.mMessages.size() - 1 - i); |
| 498 | if (!ownMessage.sameAs(otherMessage)) { |
| 499 | return result; |
| 500 | } |
| 501 | result++; |
| 502 | } |
| 503 | return result; |
| 504 | } |
| 505 | return 0; |
| 506 | } |
| 507 | |
Selim Cinek | 1397ea3 | 2018-01-16 17:34:52 -0800 | [diff] [blame] | 508 | public View getSenderView() { |
Selim Cinek | d380999 | 2017-10-27 16:09:20 -0700 | [diff] [blame] | 509 | return mSenderName; |
| 510 | } |
| 511 | |
| 512 | public View getAvatar() { |
| 513 | return mAvatarView; |
| 514 | } |
Selim Cinek | 1d6b50e | 2017-10-27 16:10:57 -0700 | [diff] [blame] | 515 | |
| 516 | public MessagingLinearLayout getMessageContainer() { |
| 517 | return mMessageContainer; |
| 518 | } |
Selim Cinek | afeed29 | 2017-12-12 17:32:44 -0800 | [diff] [blame] | 519 | |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 520 | public MessagingImageMessage getIsolatedMessage() { |
| 521 | return mIsolatedMessage; |
| 522 | } |
| 523 | |
Selim Cinek | afeed29 | 2017-12-12 17:32:44 -0800 | [diff] [blame] | 524 | public boolean needsGeneratedAvatar() { |
| 525 | return mNeedsGeneratedAvatar; |
| 526 | } |
Selim Cinek | 1397ea3 | 2018-01-16 17:34:52 -0800 | [diff] [blame] | 527 | |
Selim Cinek | 9acd673 | 2018-03-23 16:39:02 -0700 | [diff] [blame] | 528 | public Person getSender() { |
Selim Cinek | 1397ea3 | 2018-01-16 17:34:52 -0800 | [diff] [blame] | 529 | return mSender; |
| 530 | } |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 531 | |
| 532 | public void setTransformingImages(boolean transformingImages) { |
| 533 | mTransformingImages = transformingImages; |
| 534 | } |
| 535 | |
Selim Cinek | 85d0e6e | 2018-03-23 18:08:32 -0700 | [diff] [blame] | 536 | public void setDisplayImagesAtEnd(boolean atEnd) { |
| 537 | if (mImagesAtEnd != atEnd) { |
| 538 | mImagesAtEnd = atEnd; |
Selim Cinek | 1c72fa0 | 2018-04-23 18:00:54 +0800 | [diff] [blame] | 539 | updateImageContainerVisibility(); |
Selim Cinek | 7199ed9 | 2018-01-26 13:36:43 -0800 | [diff] [blame] | 540 | } |
| 541 | } |
| 542 | |
| 543 | public List<MessagingMessage> getMessages() { |
| 544 | return mMessages; |
| 545 | } |
Selim Cinek | 88188f2 | 2017-09-19 16:46:56 -0700 | [diff] [blame] | 546 | } |