fetch vcard avatars
This commit is contained in:
parent
417e801811
commit
9819ef7d05
|
@ -16,6 +16,7 @@ import im.conversations.android.ui.graphics.drawable.AvatarDrawable;
|
|||
import im.conversations.android.xmpp.ConnectionPool;
|
||||
import im.conversations.android.xmpp.manager.AvatarManager;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -66,6 +67,10 @@ public class AvatarFetcher {
|
|||
|
||||
@Override
|
||||
public void onFailure(@NonNull final Throwable throwable) {
|
||||
if (throwable instanceof CancellationException) {
|
||||
return;
|
||||
}
|
||||
// TODO on IqTimeout remove tag?
|
||||
final var imageView = imageViewWeakReference.get();
|
||||
if (imageView == null) {
|
||||
LOGGER.info("ImageView reference was gone after avatar fetch failed");
|
||||
|
@ -79,7 +84,13 @@ public class AvatarFetcher {
|
|||
public static void fetchInto(final ImageView imageView, final AvatarWithAccount avatar) {
|
||||
final var tag = imageView.getTag();
|
||||
if (tag instanceof AvatarFetcher avatarFetcher) {
|
||||
if (avatar.equals(avatarFetcher.avatar)) {
|
||||
if (avatar.equals(avatarFetcher.avatar) && !avatarFetcher.future.isCancelled()) {
|
||||
if (avatarFetcher.future.isDone()) {
|
||||
Futures.addCallback(
|
||||
avatarFetcher.future,
|
||||
new Callback(imageView, avatar.addressWithName),
|
||||
ContextCompat.getMainExecutor(imageView.getContext()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
avatarFetcher.future.cancel(true);
|
||||
|
|
|
@ -43,6 +43,7 @@ public class ChatOverviewAdapter
|
|||
chatOverviewItem == null ? null : chatOverviewItem.getAddressWithName();
|
||||
final var avatar = chatOverviewItem == null ? null : chatOverviewItem.getAvatar();
|
||||
if (avatar != null) {
|
||||
holder.binding.avatar.setVisibility(View.VISIBLE);
|
||||
AvatarFetcher.fetchInto(holder.binding.avatar, avatar);
|
||||
} else if (addressWithName != null) {
|
||||
holder.binding.avatar.setVisibility(View.VISIBLE);
|
||||
|
|
|
@ -86,6 +86,7 @@ public class AvatarDrawable extends ColorDrawable {
|
|||
canvas.getClipBounds(r);
|
||||
final int cHeight = r.height();
|
||||
final int cWidth = r.width();
|
||||
// TODO if we ever want to do rounded corners we can use drawRoundRect()
|
||||
canvas.drawCircle(midX, midY, radius, paint);
|
||||
if (letter == null) {
|
||||
return;
|
||||
|
|
|
@ -17,6 +17,10 @@ import im.conversations.android.xmpp.model.avatar.Data;
|
|||
import im.conversations.android.xmpp.model.avatar.Info;
|
||||
import im.conversations.android.xmpp.model.avatar.Metadata;
|
||||
import im.conversations.android.xmpp.model.pubsub.Items;
|
||||
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||
import im.conversations.android.xmpp.model.vcard.BinaryValue;
|
||||
import im.conversations.android.xmpp.model.vcard.Photo;
|
||||
import im.conversations.android.xmpp.model.vcard.VCard;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
@ -70,7 +74,8 @@ public class AvatarManager extends AbstractManager {
|
|||
}
|
||||
}
|
||||
|
||||
public ListenableFuture<byte[]> getAvatar(final Jid address, final String id) {
|
||||
public ListenableFuture<byte[]> getAvatar(
|
||||
final Jid address, final AvatarType type, final String id) {
|
||||
final var fetch = new Fetch(address, id);
|
||||
final SettableFuture<byte[]> future;
|
||||
synchronized (avatarFetches) {
|
||||
|
@ -81,7 +86,7 @@ public class AvatarManager extends AbstractManager {
|
|||
future = SettableFuture.create();
|
||||
avatarFetches.put(fetch, future);
|
||||
}
|
||||
future.setFuture(getCachedOrFetch(address, id));
|
||||
future.setFuture(getCachedOrFetch(address, type, id));
|
||||
future.addListener(
|
||||
() -> {
|
||||
synchronized (this.avatarFetches) {
|
||||
|
@ -94,15 +99,8 @@ public class AvatarManager extends AbstractManager {
|
|||
|
||||
public ListenableFuture<Bitmap> getAvatarBitmap(
|
||||
final Jid address, final AvatarType type, final String id) {
|
||||
final ListenableFuture<byte[]> avatar;
|
||||
if (type == AvatarType.PEP) {
|
||||
avatar = getAvatar(address, id);
|
||||
} else {
|
||||
return Futures.immediateFailedFuture(
|
||||
new Exception(String.format("Can not load type %s avatar", type)));
|
||||
}
|
||||
return Futures.transform(
|
||||
avatar,
|
||||
getAvatar(address, type, id),
|
||||
bytes -> {
|
||||
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
||||
},
|
||||
|
@ -120,26 +118,55 @@ public class AvatarManager extends AbstractManager {
|
|||
}
|
||||
}
|
||||
|
||||
private ListenableFuture<byte[]> getCachedOrFetch(final Jid address, final String id) {
|
||||
private ListenableFuture<byte[]> getCachedOrFetch(
|
||||
final Jid address, final AvatarType type, final String id) {
|
||||
final var cachedFuture =
|
||||
Futures.submit(() -> getCachedAvatar(address, id), FILE_IO_EXECUTOR);
|
||||
return Futures.catchingAsync(
|
||||
cachedFuture,
|
||||
Exception.class,
|
||||
exception -> fetchAndCacheAvatar(address, id),
|
||||
exception -> fetchAndCacheAvatar(address, type, id),
|
||||
MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
private ListenableFuture<byte[]> fetchAvatar(final Jid address, final String id) {
|
||||
private ListenableFuture<byte[]> fetchPepAvatar(final Jid address, final String id) {
|
||||
return Futures.transform(
|
||||
getManager(PubSubManager.class).fetchItem(address, id, Data.class),
|
||||
Data::asBytes,
|
||||
MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
private ListenableFuture<byte[]> fetchAndCacheAvatar(final Jid address, final String id) {
|
||||
private ListenableFuture<byte[]> fetchVcardAvatar(final Jid address, final String id) {
|
||||
final var iq = new Iq(Iq.Type.GET);
|
||||
iq.setTo(address);
|
||||
iq.addExtension(new VCard());
|
||||
final var iqFuture = connection.sendIqPacket(iq);
|
||||
return Futures.transform(
|
||||
fetchAvatar(address, id),
|
||||
iqFuture,
|
||||
result -> {
|
||||
final var vcard = result.getExtension(VCard.class);
|
||||
if (vcard == null) {
|
||||
throw new IllegalStateException("No vCard in response");
|
||||
}
|
||||
final var photo = vcard.getExtension(Photo.class);
|
||||
final var binary = photo == null ? null : photo.getExtension(BinaryValue.class);
|
||||
if (binary == null) {
|
||||
throw new IllegalStateException("vCard did not have embedded photo");
|
||||
}
|
||||
return binary.asBytes();
|
||||
},
|
||||
MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
private ListenableFuture<byte[]> fetchAndCacheAvatar(
|
||||
final Jid address, final AvatarType avatarType, final String id) {
|
||||
final ListenableFuture<byte[]> avatarFetchFuture =
|
||||
switch (avatarType) {
|
||||
case PEP -> fetchPepAvatar(address, id);
|
||||
case VCARD -> fetchVcardAvatar(address, id);
|
||||
};
|
||||
return Futures.transform(
|
||||
avatarFetchFuture,
|
||||
avatar -> {
|
||||
final var sha1Hash = Hashing.sha1().hashBytes(avatar).toString();
|
||||
if (sha1Hash.equalsIgnoreCase(id)) {
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package im.conversations.android.xmpp.model.vcard;
|
||||
|
||||
import im.conversations.android.annotation.XmlElement;
|
||||
import im.conversations.android.xmpp.model.ByteContent;
|
||||
import im.conversations.android.xmpp.model.Extension;
|
||||
|
||||
@XmlElement(name = "BINVAL")
|
||||
public class BinaryValue extends Extension implements ByteContent {
|
||||
|
||||
public BinaryValue() {
|
||||
super(BinaryValue.class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package im.conversations.android.xmpp.model.vcard;
|
||||
|
||||
import im.conversations.android.annotation.XmlElement;
|
||||
import im.conversations.android.xmpp.model.Extension;
|
||||
|
||||
@XmlElement(name = "PHOTO")
|
||||
public class Photo extends Extension {
|
||||
public Photo() {
|
||||
super(Photo.class);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ package im.conversations.android.xmpp.model.vcard;
|
|||
import im.conversations.android.annotation.XmlElement;
|
||||
import im.conversations.android.xmpp.model.Extension;
|
||||
|
||||
@XmlElement
|
||||
@XmlElement(name = "vCard")
|
||||
public class VCard extends Extension {
|
||||
|
||||
public VCard() {
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
app:layout_constraintEnd_toStartOf="@+id/sentAt"
|
||||
app:layout_constraintStart_toEndOf="@+id/avatar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.5"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="The City of Verona" />
|
||||
|
||||
|
|
Loading…
Reference in a new issue