That fuck shit the fascists are using
at master 399 lines 16 kB view raw
1package org.tm.archive.components; 2 3import android.content.Context; 4import android.content.res.TypedArray; 5import android.graphics.Bitmap; 6import android.graphics.Rect; 7import android.graphics.drawable.Drawable; 8import android.util.AttributeSet; 9 10import androidx.annotation.DrawableRes; 11import androidx.annotation.IntRange; 12import androidx.annotation.NonNull; 13import androidx.annotation.Nullable; 14import androidx.annotation.Px; 15import androidx.appcompat.widget.AppCompatImageView; 16import androidx.fragment.app.FragmentActivity; 17 18import com.bumptech.glide.Glide; 19import com.bumptech.glide.RequestBuilder; 20import com.bumptech.glide.RequestManager; 21import com.bumptech.glide.load.DataSource; 22import com.bumptech.glide.load.MultiTransformation; 23import com.bumptech.glide.load.Transformation; 24import com.bumptech.glide.load.engine.DiskCacheStrategy; 25import com.bumptech.glide.load.engine.GlideException; 26import com.bumptech.glide.load.resource.bitmap.CircleCrop; 27import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy; 28import com.bumptech.glide.request.RequestListener; 29import com.bumptech.glide.request.target.SimpleTarget; 30import com.bumptech.glide.request.target.Target; 31import com.bumptech.glide.request.transition.Transition; 32 33import org.signal.core.util.logging.Log; 34import org.tm.archive.R; 35import org.tm.archive.components.settings.conversation.ConversationSettingsActivity; 36import org.tm.archive.contacts.avatars.ContactPhoto; 37import org.tm.archive.contacts.avatars.FallbackContactPhoto; 38import org.tm.archive.contacts.avatars.ProfileContactPhoto; 39import org.tm.archive.contacts.avatars.ResourceContactPhoto; 40import org.tm.archive.conversation.colors.AvatarColor; 41import org.tm.archive.conversation.colors.ChatColors; 42import org.tm.archive.dependencies.ApplicationDependencies; 43import org.tm.archive.jobs.RetrieveProfileAvatarJob; 44import org.tm.archive.recipients.Recipient; 45import org.tm.archive.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment; 46import org.tm.archive.util.AvatarUtil; 47import org.tm.archive.util.BlurTransformation; 48import org.tm.archive.util.Util; 49import org.tm.archive.util.ViewUtil; 50 51import java.util.ArrayList; 52import java.util.List; 53import java.util.Objects; 54 55public final class AvatarImageView extends AppCompatImageView { 56 57 private static final int SIZE_LARGE = 1; 58 private static final int SIZE_SMALL = 2; 59 60 @SuppressWarnings("unused") 61 private static final String TAG = Log.tag(AvatarImageView.class); 62 63 private final RequestListener<Drawable> redownloadRequestListener = new RedownloadRequestListener(); 64 65 private int size; 66 private boolean inverted; 67 private OnClickListener listener; 68 private Recipient.FallbackPhotoProvider fallbackPhotoProvider; 69 private boolean blurred; 70 private ChatColors chatColors; 71 private FixedSizeTarget fixedSizeTarget; 72 73 private @Nullable RecipientContactPhoto recipientContactPhoto; 74 private @NonNull Drawable unknownRecipientDrawable; 75 private @Nullable AvatarColor fallbackPhotoColor; 76 77 public AvatarImageView(Context context) { 78 super(context); 79 initialize(context, null); 80 } 81 82 public AvatarImageView(Context context, AttributeSet attrs) { 83 super(context, attrs); 84 initialize(context, attrs); 85 } 86 87 public void initialize(@NonNull Context context, @Nullable AttributeSet attrs) { 88 setScaleType(ScaleType.CENTER_CROP); 89 90 if (attrs != null) { 91 TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AvatarImageView, 0, 0); 92 inverted = typedArray.getBoolean(R.styleable.AvatarImageView_inverted, false); 93 size = typedArray.getInt(R.styleable.AvatarImageView_fallbackImageSize, SIZE_LARGE); 94 typedArray.recycle(); 95 } 96 97 unknownRecipientDrawable = new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20).asDrawable(context, AvatarColor.UNKNOWN, inverted); 98 blurred = false; 99 chatColors = null; 100 } 101 102 @Override 103 public void setClipBounds(Rect clipBounds) { 104 super.setClipBounds(clipBounds); 105 } 106 107 @Override 108 public void setOnClickListener(OnClickListener listener) { 109 this.listener = listener; 110 super.setOnClickListener(listener); 111 } 112 113 public void setFallbackPhotoProvider(Recipient.FallbackPhotoProvider fallbackPhotoProvider) { 114 this.fallbackPhotoProvider = fallbackPhotoProvider; 115 } 116 117 public void setFallbackPhotoColor(@Nullable AvatarColor fallbackPhotoColor) { 118 this.fallbackPhotoColor = fallbackPhotoColor; 119 } 120 121 /** 122 * Shows self as the actual profile picture. 123 */ 124 public void setRecipient(@NonNull Recipient recipient) { 125 setRecipient(recipient, false); 126 } 127 128 /** 129 * Shows self as the actual profile picture. 130 */ 131 public void setRecipient(@NonNull Recipient recipient, boolean quickContactEnabled) { 132 if (recipient.isSelf()) { 133 setAvatar(Glide.with(this), null, quickContactEnabled); 134 AvatarUtil.loadIconIntoImageView(recipient, this); 135 } else { 136 setAvatar(Glide.with(this), recipient, quickContactEnabled); 137 } 138 } 139 140 public AvatarOptions.Builder buildOptions() { 141 return new AvatarOptions.Builder(this); 142 } 143 144 /** 145 * Shows self as the note to self icon. 146 */ 147 public void setAvatar(@Nullable Recipient recipient) { 148 setAvatar(Glide.with(this), recipient, false); 149 } 150 151 /** 152 * Shows self as the profile avatar. 153 */ 154 public void setAvatarUsingProfile(@Nullable Recipient recipient) { 155 setAvatar(Glide.with(this), recipient, false, true); 156 } 157 158 public void setAvatar(@NonNull RequestManager requestManager, @Nullable Recipient recipient, boolean quickContactEnabled) { 159 setAvatar(requestManager, recipient, quickContactEnabled, false); 160 } 161 162 public void setAvatar(@NonNull RequestManager requestManager, @Nullable Recipient recipient, boolean quickContactEnabled, boolean useSelfProfileAvatar) { 163 setAvatar(requestManager, recipient, new AvatarOptions.Builder(this) 164 .withUseSelfProfileAvatar(useSelfProfileAvatar) 165 .withQuickContactEnabled(quickContactEnabled) 166 .build()); 167 } 168 169 private void setAvatar(@Nullable Recipient recipient, @NonNull AvatarOptions avatarOptions) { 170 setAvatar(Glide.with(this), recipient, avatarOptions); 171 } 172 173 private void setAvatar(@NonNull RequestManager requestManager, @Nullable Recipient recipient, @NonNull AvatarOptions avatarOptions) { 174 if (recipient != null) { 175 RecipientContactPhoto photo = (recipient.isSelf() && avatarOptions.useSelfProfileAvatar) ? new RecipientContactPhoto(recipient, 176 new ProfileContactPhoto(Recipient.self())) 177 : new RecipientContactPhoto(recipient); 178 179 boolean shouldBlur = recipient.shouldBlurAvatar(); 180 ChatColors chatColors = recipient.getChatColors(); 181 182 if (!photo.equals(recipientContactPhoto) || shouldBlur != blurred || !Objects.equals(chatColors, this.chatColors)) { 183 requestManager.clear(this); 184 this.chatColors = chatColors; 185 recipientContactPhoto = photo; 186 187 Recipient.FallbackPhotoProvider activeFallbackPhotoProvider = this.fallbackPhotoProvider; 188 if (recipient.isSelf() && avatarOptions.useSelfProfileAvatar) { 189 activeFallbackPhotoProvider = new Recipient.FallbackPhotoProvider() { 190 @Override 191 public @NonNull FallbackContactPhoto getPhotoForLocalNumber() { 192 return super.getPhotoForRecipientWithName(recipient.getDisplayName(getContext()), ViewUtil.getWidth(AvatarImageView.this)); 193 } 194 }; 195 } 196 197 Drawable fallbackContactPhotoDrawable = size == SIZE_SMALL ? photo.recipient.getSmallFallbackContactPhotoDrawable(getContext(), inverted, activeFallbackPhotoProvider, ViewUtil.getWidth(this)) 198 : photo.recipient.getFallbackContactPhotoDrawable(getContext(), inverted, activeFallbackPhotoProvider, ViewUtil.getWidth(this)); 199 200 if (fixedSizeTarget != null) { 201 requestManager.clear(fixedSizeTarget); 202 } 203 204 if (photo.contactPhoto != null) { 205 206 List<Transformation<Bitmap>> transforms = new ArrayList<>(); 207 if (shouldBlur) { 208 transforms.add(new BlurTransformation(ApplicationDependencies.getApplication(), 0.25f, BlurTransformation.MAX_RADIUS)); 209 } 210 transforms.add(new CircleCrop()); 211 blurred = shouldBlur; 212 213 RequestBuilder<Drawable> request = requestManager.load(photo.contactPhoto) 214 .dontAnimate() 215 .fallback(fallbackContactPhotoDrawable) 216 .error(fallbackContactPhotoDrawable) 217 .diskCacheStrategy(DiskCacheStrategy.ALL) 218 .downsample(DownsampleStrategy.CENTER_INSIDE) 219 .transform(new MultiTransformation<>(transforms)) 220 .addListener(redownloadRequestListener); 221 222 if (avatarOptions.fixedSize > 0) { 223 fixedSizeTarget = new FixedSizeTarget(avatarOptions.fixedSize); 224 request.into(fixedSizeTarget); 225 } else { 226 request.into(this); 227 } 228 229 } else { 230 setImageDrawable(fallbackContactPhotoDrawable); 231 } 232 } 233 234 setAvatarClickHandler(recipient, avatarOptions.quickContactEnabled); 235 } else { 236 recipientContactPhoto = null; 237 requestManager.clear(this); 238 if (fallbackPhotoProvider != null) { 239 setImageDrawable(fallbackPhotoProvider.getPhotoForRecipientWithoutName() 240 .asDrawable(getContext(), Util.firstNonNull(fallbackPhotoColor, AvatarColor.UNKNOWN), inverted)); 241 } else { 242 setImageDrawable(unknownRecipientDrawable); 243 } 244 245 disableQuickContact(); 246 } 247 } 248 249 private void setAvatarClickHandler(@NonNull final Recipient recipient, boolean quickContactEnabled) { 250 if (quickContactEnabled) { 251 super.setOnClickListener(v -> { 252 Context context = getContext(); 253 if (recipient.isPushGroup()) { 254 context.startActivity(ConversationSettingsActivity.forGroup(context, recipient.requireGroupId().requirePush()), 255 ConversationSettingsActivity.createTransitionBundle(context, this)); 256 } else { 257 if (context instanceof FragmentActivity) { 258 RecipientBottomSheetDialogFragment.show(((FragmentActivity) context).getSupportFragmentManager(), recipient.getId(), null); 259 } else { 260 context.startActivity(ConversationSettingsActivity.forRecipient(context, recipient.getId()), 261 ConversationSettingsActivity.createTransitionBundle(context, this)); 262 } 263 } 264 }); 265 } else { 266 disableQuickContact(); 267 } 268 } 269 270 public void setImageBytesForGroup(@Nullable byte[] avatarBytes, 271 @Nullable Recipient.FallbackPhotoProvider fallbackPhotoProvider, 272 @NonNull AvatarColor color) 273 { 274 Drawable fallback = Util.firstNonNull(fallbackPhotoProvider, Recipient.DEFAULT_FALLBACK_PHOTO_PROVIDER) 275 .getPhotoForGroup() 276 .asDrawable(getContext(), color); 277 278 Glide.with(this) 279 .load(avatarBytes) 280 .dontAnimate() 281 .fallback(fallback) 282 .error(fallback) 283 .diskCacheStrategy(DiskCacheStrategy.ALL) 284 .circleCrop() 285 .into(this); 286 } 287 288 public void setNonAvatarImageResource(@DrawableRes int imageResource) { 289 recipientContactPhoto = null; 290 setImageResource(imageResource); 291 } 292 293 public void disableQuickContact() { 294 super.setOnClickListener(listener); 295 setClickable(listener != null); 296 } 297 298 private static class RecipientContactPhoto { 299 300 private final @NonNull Recipient recipient; 301 private final @Nullable ContactPhoto contactPhoto; 302 private final boolean ready; 303 304 RecipientContactPhoto(@NonNull Recipient recipient) { 305 this(recipient, recipient.getContactPhoto()); 306 } 307 308 RecipientContactPhoto(@NonNull Recipient recipient, @Nullable ContactPhoto contactPhoto) { 309 this.recipient = recipient; 310 this.ready = !recipient.isResolving(); 311 this.contactPhoto = contactPhoto; 312 } 313 314 public boolean equals(@Nullable RecipientContactPhoto other) { 315 if (other == null) return false; 316 317 return other.recipient.equals(recipient) && 318 other.recipient.getChatColors().equals(recipient.getChatColors()) && 319 other.ready == ready && 320 Objects.equals(other.contactPhoto, contactPhoto); 321 } 322 } 323 324 private final class FixedSizeTarget extends SimpleTarget<Drawable> { 325 326 FixedSizeTarget(int size) { 327 super(size, size); 328 } 329 330 @Override 331 public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) { 332 setImageDrawable(resource); 333 } 334 } 335 336 public static final class AvatarOptions { 337 338 private final boolean quickContactEnabled; 339 private final boolean useSelfProfileAvatar; 340 private final int fixedSize; 341 342 private AvatarOptions(@NonNull Builder builder) { 343 this.quickContactEnabled = builder.quickContactEnabled; 344 this.useSelfProfileAvatar = builder.useSelfProfileAvatar; 345 this.fixedSize = builder.fixedSize; 346 } 347 348 public static final class Builder { 349 350 private final AvatarImageView avatarImageView; 351 352 private boolean quickContactEnabled = false; 353 private boolean useSelfProfileAvatar = false; 354 private int fixedSize = -1; 355 356 private Builder(@NonNull AvatarImageView avatarImageView) { 357 this.avatarImageView = avatarImageView; 358 } 359 360 public @NonNull Builder withQuickContactEnabled(boolean quickContactEnabled) { 361 this.quickContactEnabled = quickContactEnabled; 362 return this; 363 } 364 365 public @NonNull Builder withUseSelfProfileAvatar(boolean useSelfProfileAvatar) { 366 this.useSelfProfileAvatar = useSelfProfileAvatar; 367 return this; 368 } 369 370 public @NonNull Builder withFixedSize(@Px @IntRange(from = 1) int fixedSize) { 371 this.fixedSize = fixedSize; 372 return this; 373 } 374 375 public AvatarOptions build() { 376 return new AvatarOptions(this); 377 } 378 379 public void load(@Nullable Recipient recipient) { 380 avatarImageView.setAvatar(recipient, build()); 381 } 382 } 383 } 384 385 private static class RedownloadRequestListener implements RequestListener<Drawable> { 386 @Override 387 public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) { 388 if (model instanceof ProfileContactPhoto) { 389 RetrieveProfileAvatarJob.enqueueForceUpdate(((ProfileContactPhoto) model).getRecipient()); 390 } 391 return false; 392 } 393 394 @Override 395 public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) { 396 return false; 397 } 398 } 399}