session-android/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java
Harris 6ddefb7a2e
Performance improvements and bug fixes (#869)
* refactor: fail on testSnode instead of recursively using up snode list. add call timeout on http client

* refactor: refactoring batch message receives and pollers

* refactor: reduce thread utils pool count to a 2 thread fixed pool. Do a check against pubkey instead of room names for oxenHostedOpenGroup

* refactor: caching lib with potential loader fixes and no-cache for giphy

* refactor: remove store and instead use ConcurrentHashMap with a backing update coroutine

* refactor: queue trim thread jobs instead of add every message processed

* fix: wrapping auth token and initial sync for open groups in a threadutils queued runnable, getting initial sync times down

* fix: fixing the user contacts cache in ConversationAdapter.kt

* refactor: improve polling and initial sync, move group joins from config messages into a background job fetching image.

* refactor: improving the job queuing for open groups, replacing placeholder avatar generation with a custom glide loader and archiving initial sync of open groups

* feat: add OpenGroupDeleteJob.kt

* feat: add open group delete job to process deletions after batch adding

* feat: add vacuum and fix job queue re-adding jobs forever, only try to set message hash values in DB if they have changed

* refactor: remove redundant inflation for profile image views throughout app

* refactor(wip): reducing layout inflation and starting to refactor the open group deletion issues taking a long time

* refactor(wip): refactoring group deletion to not iterate through and delete messages individually

* refactor(wip): refactoring group deletion to not iterate through and delete messages individually

* fix: group deletion optimisation

* build: bump build number

* build: bump build number and fix batch message receive retry logic

* fix: clear out open group deletes

* fix: update visible ConversationAdapter.kt binding for initial contact fetching and better traces for debugging background jobs

* fix: add in check for / force sync latest encryption key pair from linked devices if we already have that closed group

* Rename .java to .kt

* refactor: change MmsDatabase to kotlin to make list operations easier

* fix: nullable type

* fix: compilation issues and constants in .kt instead of .java

* fix: bug fix expiration timer on closed group recipient

* feat: use the job queue properly across executors

* feat: start on open group dispatcher-specific logic, probably a queue factory based on openGroupId if that is the same across new message and deletion jobs to ensure consistent entry and removal

* refactor: removing redundant code and fixing jobqueue per opengroup

* fix: allow attachments in note to self

* fix: make the minWidth in quote view bind max of text / title and body, wrapped ?

* fix: fixing up layouts and code view layouts

* fix: remove TODO, remove timestamp binding

* feat: fix view logic, avatars and padding, downloading attachments lazily (on bind), fixing potential crash, add WindowDebouncer.kt

* fix: NPE on viewModel recipient from removed thread while tearing down the Recipient observer in ConversationActivityV2.kt

* refactor: replace conversation notification debouncer handler with handlerthread, same as conversation list debouncer

* refactor: UI for groups and poller improvements

* fix: revert some changes in poller

* feat: add header back in for message requests

* refactor: remove Trace calls, add more conditions to the HomeDiffUtil for updating more efficiently

* feat: try update the home adapter if we get a profile picture modified event

* feat: bump build numbers

* fix: try to start with list in homeViewModel if we don't have already, render quotes to be width of attachment slide view instead of fixed

* fix: set channel to be conflated instead of no buffer

* fix: set unreads based off last local user message vs incrementing unreads to be all amount

* feat: add profile update flag, update build number

* fix: link preview thumbnails download on bind

* fix: centercrop placeholder in glide request

* feat: recycle the contact selection list and profile image in unbind

* fix: try to prevent user KP crash at weird times

* fix: remove additional log, improve attachment download success rate, fix share logs dialog issue
2022-06-08 17:12:34 +10:00

199 lines
7.2 KiB
Java

package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.provider.ContactsContract;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.session.libsession.avatars.ContactColors;
import org.session.libsession.avatars.ContactPhoto;
import org.session.libsession.avatars.ResourceContactPhoto;
import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.ThemeUtil;
import org.session.libsession.utilities.recipients.Recipient;
import org.session.libsession.utilities.recipients.RecipientExporter;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator;
import java.util.Objects;
import network.loki.messenger.R;
public class AvatarImageView extends AppCompatImageView {
private static final String TAG = AvatarImageView.class.getSimpleName();
private static final Paint LIGHT_THEME_OUTLINE_PAINT = new Paint();
private static final Paint DARK_THEME_OUTLINE_PAINT = new Paint();
static {
LIGHT_THEME_OUTLINE_PAINT.setColor(Color.argb((int) (255 * 0.2), 0, 0, 0));
LIGHT_THEME_OUTLINE_PAINT.setStyle(Paint.Style.STROKE);
LIGHT_THEME_OUTLINE_PAINT.setStrokeWidth(1f);
LIGHT_THEME_OUTLINE_PAINT.setAntiAlias(true);
DARK_THEME_OUTLINE_PAINT.setColor(Color.argb((int) (255 * 0.2), 255, 255, 255));
DARK_THEME_OUTLINE_PAINT.setStyle(Paint.Style.STROKE);
DARK_THEME_OUTLINE_PAINT.setStrokeWidth(1f);
DARK_THEME_OUTLINE_PAINT.setAntiAlias(true);
}
private boolean inverted;
private Paint outlinePaint;
private OnClickListener listener;
private @Nullable RecipientContactPhoto recipientContactPhoto;
private @NonNull Drawable unknownRecipientDrawable;
public AvatarImageView(Context context) {
super(context);
initialize(context, null);
}
public AvatarImageView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs);
}
private void initialize(@NonNull Context context, @Nullable AttributeSet attrs) {
setScaleType(ScaleType.CENTER_CROP);
if (attrs != null) {
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AvatarImageView, 0, 0);
inverted = typedArray.getBoolean(0, false);
typedArray.recycle();
}
outlinePaint = ThemeUtil.isDarkTheme(getContext()) ? DARK_THEME_OUTLINE_PAINT : LIGHT_THEME_OUTLINE_PAINT;
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setOval(0, 0, view.getWidth(), view.getHeight());
}
});
setClipToOutline(true);
unknownRecipientDrawable = new ResourceContactPhoto(R.drawable.ic_profile_default).asDrawable(getContext(), ContactColors.UNKNOWN_COLOR.toConversationColor(getContext()), inverted);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float width = getWidth() - getPaddingRight() - getPaddingLeft();
float height = getHeight() - getPaddingBottom() - getPaddingTop();
float cx = width / 2f;
float cy = height / 2f;
float radius = Math.min(cx, cy) - (outlinePaint.getStrokeWidth() / 2f);
canvas.translate(getPaddingLeft(), getPaddingTop());
canvas.drawCircle(cx, cy, radius, outlinePaint);
}
@Override
public void setOnClickListener(OnClickListener listener) {
this.listener = listener;
super.setOnClickListener(listener);
}
public void update(String hexEncodedPublicKey) {
Address address = Address.fromSerialized(hexEncodedPublicKey);
Recipient recipient = Recipient.from(getContext(), address, false);
updateAvatar(recipient);
}
private void updateAvatar(Recipient recipient) {
setAvatar(GlideApp.with(getContext()), recipient, false);
}
public void setAvatar(@NonNull GlideRequests requestManager, @Nullable Recipient recipient, boolean quickContactEnabled) {
if (recipient != null) {
if (recipient.isLocalNumber()) {
setImageDrawable(new ResourceContactPhoto(R.drawable.ic_note_to_self).asDrawable(getContext(), recipient.getColor().toAvatarColor(getContext()), inverted));
} else {
RecipientContactPhoto photo = new RecipientContactPhoto(recipient);
if (!photo.equals(recipientContactPhoto)) {
requestManager.clear(this);
recipientContactPhoto = photo;
Drawable photoPlaceholderDrawable = AvatarPlaceholderGenerator.generate(
getContext(), 128, recipient.getAddress().serialize(), recipient.getName());
if (photo.contactPhoto != null) {
requestManager.load(photo.contactPhoto)
.fallback(photoPlaceholderDrawable)
.error(photoPlaceholderDrawable)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.circleCrop()
.into(this);
} else {
requestManager.load(photoPlaceholderDrawable)
.circleCrop()
.into(this);
// setImageDrawable(photoPlaceholderDrawable);
}
}
}
} else {
recipientContactPhoto = null;
requestManager.clear(this);
setImageDrawable(unknownRecipientDrawable);
super.setOnClickListener(listener);
}
}
public void clear(@NonNull GlideRequests glideRequests) {
glideRequests.clear(this);
}
private void setAvatarClickHandler(final Recipient recipient, boolean quickContactEnabled) {
if (!recipient.isGroupRecipient() && quickContactEnabled) {
super.setOnClickListener(v -> {
if (recipient.getContactUri() != null) {
ContactsContract.QuickContact.showQuickContact(getContext(), AvatarImageView.this, recipient.getContactUri(), ContactsContract.QuickContact.MODE_LARGE, null);
} else {
getContext().startActivity(RecipientExporter.export(recipient).asAddContactIntent());
}
});
} else {
super.setOnClickListener(listener);
}
}
private static class RecipientContactPhoto {
private final @NonNull Recipient recipient;
private final @Nullable ContactPhoto contactPhoto;
private final boolean ready;
RecipientContactPhoto(@NonNull Recipient recipient) {
this.recipient = recipient;
this.ready = !recipient.isResolving();
this.contactPhoto = recipient.getContactPhoto();
}
public boolean equals(@Nullable RecipientContactPhoto other) {
if (other == null) return false;
return other.recipient.equals(recipient) &&
other.recipient.getColor().equals(recipient.getColor()) &&
other.ready == ready &&
Objects.equals(other.contactPhoto, contactPhoto);
}
}
}