That fuck shit the fascists are using
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}