diff --git a/res/layout/conversation_activity_attachment_editor_stub.xml b/res/layout/conversation_activity_attachment_editor_stub.xml
index 40b60e5f6..81c2b18c5 100644
--- a/res/layout/conversation_activity_attachment_editor_stub.xml
+++ b/res/layout/conversation_activity_attachment_editor_stub.xml
@@ -69,6 +69,17 @@
android:padding="8dp"
android:background="@drawable/message_bubble_background_sent_alone"/>
+
+
diff --git a/res/layout/conversation_item_received.xml b/res/layout/conversation_item_received.xml
index 5c9e8ad78..c5654a094 100644
--- a/res/layout/conversation_item_received.xml
+++ b/res/layout/conversation_item_received.xml
@@ -153,6 +153,16 @@
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
android:layout_marginRight="@dimen/message_bubble_horizontal_padding" />
+
+
+
+
+
diff --git a/res/layout/vcard_view.xml b/res/layout/vcard_view.xml
new file mode 100644
index 000000000..45b500c4d
--- /dev/null
+++ b/res/layout/vcard_view.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/com/b44t/messenger/DcMsg.java b/src/com/b44t/messenger/DcMsg.java
index 31999c1c6..52a4c110d 100644
--- a/src/com/b44t/messenger/DcMsg.java
+++ b/src/com/b44t/messenger/DcMsg.java
@@ -20,6 +20,7 @@ public class DcMsg {
public final static int DC_MSG_FILE = 60;
public final static int DC_MSG_VIDEOCHAT_INVITATION = 70;
public final static int DC_MSG_WEBXDC = 80;
+ public final static int DC_MSG_VCARD = 90;
public final static int DC_INFO_UNKNOWN = 0;
public final static int DC_INFO_GROUP_NAME_CHANGED = 2;
diff --git a/src/com/b44t/messenger/rpc/Rpc.java b/src/com/b44t/messenger/rpc/Rpc.java
index 31b6d3c43..88a65fbd4 100644
--- a/src/com/b44t/messenger/rpc/Rpc.java
+++ b/src/com/b44t/messenger/rpc/Rpc.java
@@ -8,6 +8,7 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
@@ -95,6 +96,20 @@ public class Rpc {
return gson.fromJson(getResult("get_system_info"), mapType.getType());
}
+ public List parseVcard(String path) throws RpcException {
+ TypeToken> listType = new TypeToken>(){};
+ return gson.fromJson(getResult("parse_vcard", path), listType.getType());
+ }
+
+ public String makeVcard(int accountId, int... contacts) throws RpcException {
+ return gson.fromJson(getResult("make_vcard", accountId, contacts), String.class);
+ }
+
+ public List importVcard(int accountId, String path) throws RpcException {
+ TypeToken> listType = new TypeToken>(){};
+ return gson.fromJson(getResult("import_vcard", accountId, path), listType.getType());
+ }
+
public HttpResponse getHttpResponse(int accountId, String url) throws RpcException {
return gson.fromJson(getResult("get_http_response", accountId, url), HttpResponse.class);
}
diff --git a/src/com/b44t/messenger/rpc/VcardContact.java b/src/com/b44t/messenger/rpc/VcardContact.java
new file mode 100644
index 000000000..75c47ea0a
--- /dev/null
+++ b/src/com/b44t/messenger/rpc/VcardContact.java
@@ -0,0 +1,60 @@
+package com.b44t.messenger.rpc;
+
+import android.util.Base64;
+
+public class VcardContact {
+ // Email address.
+ private final String addr;
+
+ // The contact's name, or the email address if no name was given.
+ private final String displayName;
+
+ // Public PGP key in Base64.
+ private final String key;
+
+ // Profile image in Base64.
+ private final String profileImage;
+
+ // Contact color in HTML color format.
+ private final String color;
+
+ // Last update timestamp.
+ private final int timestamp;
+
+ public VcardContact(String addr, String displayName, String key, String profileImage, String color, int timestamp) {
+ this.addr = addr;
+ this.displayName = displayName;
+ this.key = key;
+ this.profileImage = profileImage;
+ this.color = color;
+ this.timestamp = timestamp;
+ }
+
+ public String getAddr() {
+ return addr;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public byte[] getKey() {
+ return key == null? null : Base64.decode(key, Base64.NO_WRAP | Base64.NO_PADDING);
+ }
+
+ public boolean hasProfileImage() {
+ return profileImage != null;
+ }
+
+ public byte[] getProfileImage() {
+ return profileImage == null? null : Base64.decode(profileImage, Base64.NO_WRAP | Base64.NO_PADDING);
+ }
+
+ public String getColor() {
+ return color;
+ }
+
+ public int getTimestamp() {
+ return timestamp;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/AttachContactActivity.java b/src/org/thoughtcrime/securesms/AttachContactActivity.java
index 1cdbf9c81..fcacec7b5 100644
--- a/src/org/thoughtcrime/securesms/AttachContactActivity.java
+++ b/src/org/thoughtcrime/securesms/AttachContactActivity.java
@@ -2,26 +2,17 @@ package org.thoughtcrime.securesms;
import android.content.Intent;
-import com.b44t.messenger.DcContext;
-
import org.thoughtcrime.securesms.connect.DcHelper;
public class AttachContactActivity extends ContactSelectionActivity {
- public static final String NAME_EXTRA = "name_extra";
- public static final String ADDR_EXTRA = "addr_extra";
+ public static final String CONTACT_ID_EXTRA = "contact_id_extra";
@Override
public void onContactSelected(int specialId, String addr) {
- String name = "";
- DcContext dcContext = DcHelper.getContext(this);
- int contactId = dcContext.lookupContactIdByAddr(addr);
- if (contactId != 0) {
- name = dcContext.getContact(contactId).getDisplayName();
- }
Intent intent = new Intent();
- intent.putExtra(NAME_EXTRA, name);
- intent.putExtra(ADDR_EXTRA, addr);
+ int contactId = DcHelper.getContext(this).lookupContactIdByAddr(addr);
+ intent.putExtra(CONTACT_ID_EXTRA, contactId);
setResult(RESULT_OK, intent);
finish();
}
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index 73de6faa1..ae6552223 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -71,6 +71,8 @@ import com.b44t.messenger.DcContact;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import com.b44t.messenger.DcMsg;
+import com.b44t.messenger.rpc.Rpc;
+import com.b44t.messenger.rpc.RpcException;
import com.b44t.messenger.util.concurrent.ListenableFuture;
import com.b44t.messenger.util.concurrent.SettableFuture;
@@ -103,7 +105,6 @@ import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType;
import org.thoughtcrime.securesms.mms.AudioSlide;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
-import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.mms.QuoteModel;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.permissions.Permissions;
@@ -125,12 +126,7 @@ import org.thoughtcrime.securesms.video.recode.VideoRecoder;
import org.thoughtcrime.securesms.videochat.VideochatUtil;
import java.io.File;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
@@ -193,6 +189,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private ApplicationContext context;
private Recipient recipient;
private DcContext dcContext;
+ private Rpc rpc;
private DcChat dcChat = new DcChat(0, 0);
private int chatId;
private final boolean isSecureText = true;
@@ -204,6 +201,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
protected void onCreate(Bundle state, boolean ready) {
this.context = ApplicationContext.getInstance(getApplicationContext());
this.dcContext = DcHelper.getContext(context);
+ this.rpc = DcHelper.getRpc(context);
supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR_OVERLAY);
setContentView(R.layout.conversation_activity);
@@ -978,9 +976,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void addAttachmentContactInfo(Intent data) {
- String name = data.getStringExtra(AttachContactActivity.NAME_EXTRA);
- String mail = data.getStringExtra(AttachContactActivity.ADDR_EXTRA);
- composeText.append(name + "\n" + mail);
+ int contactId = data.getIntExtra(AttachContactActivity.CONTACT_ID_EXTRA, 0);
+ if (contactId == 0) {
+ return;
+ }
+
+ try {
+ byte[] vcard = rpc.makeVcard(dcContext.getAccountId(), contactId).getBytes();
+ String mimeType = "application/octet-stream";
+ setMedia(PersistentBlobProvider.getInstance().create(this, vcard, mimeType, "vcard.vcf"), MediaType.DOCUMENT);
+ } catch (RpcException e) {
+ Log.e(TAG, "makeVcard() failed", e);
+ }
}
private boolean isMultiUser() {
@@ -991,39 +998,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
return dcChat.getVisibility() == DcChat.DC_CHAT_VISIBILITY_ARCHIVED;
}
- public static String getRealPathFromAttachment(Context context, Attachment attachment) {
- try {
- // get file in the blobdir as `/[-].`
- String filename = attachment.getFileName();
- String ext = "";
- if(filename==null) {
- filename = new SimpleDateFormat("yyyy-MM-dd-HH-mm").format(new Date());
- ext = "." + MediaUtil.getExtensionFromMimeType(attachment.getContentType());
- }
- else {
- int i = filename.lastIndexOf(".");
- if(i>=0) {
- ext = filename.substring(i);
- filename = filename.substring(0, i);
- }
- }
- String path = DcHelper.getBlobdirFile(DcHelper.getContext(context), filename, ext);
-
- // copy content to this file
- if(path!=null) {
- InputStream inputStream = PartAuthority.getAttachmentStream(context, attachment.getDataUri());
- OutputStream outputStream = new FileOutputStream(path);
- Util.copy(inputStream, outputStream);
- }
-
- return path;
- }
- catch(Exception e) {
- e.printStackTrace();
- return null;
- }
- }
-
//////// send message or save draft
protected static final int ACTION_SEND_OUT = 1;
@@ -1076,7 +1050,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} else {
msg = new DcMsg(dcContext, DcMsg.DC_MSG_FILE);
}
- String path = getRealPathFromAttachment(this, attachment);
+ String path = attachment.getRealPath(this);
msg.setFile(path, null);
}
}
@@ -1336,7 +1310,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void sendSticker(@NonNull Uri uri, String contentType) {
Attachment attachment = new UriAttachment(uri, null, contentType,
AttachmentDatabase.TRANSFER_PROGRESS_STARTED, 0, 0, 0, null, null, false);
- String path = getRealPathFromAttachment(this, attachment);
+ String path = attachment.getRealPath(this);
Optional quote = inputPanel.getQuote();
inputPanel.clearQuote();
diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java
index 6be6d1912..34d6ab91e 100644
--- a/src/org/thoughtcrime/securesms/ConversationItem.java
+++ b/src/org/thoughtcrime/securesms/ConversationItem.java
@@ -32,16 +32,19 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
+import android.widget.Toast;
import androidx.annotation.DimenRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcContact;
import com.b44t.messenger.DcMsg;
import com.b44t.messenger.rpc.Reactions;
import com.b44t.messenger.rpc.RpcException;
+import com.b44t.messenger.rpc.VcardContact;
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
import org.thoughtcrime.securesms.components.AudioView;
@@ -51,6 +54,7 @@ import org.thoughtcrime.securesms.components.ConversationItemFooter;
import org.thoughtcrime.securesms.components.ConversationItemThumbnail;
import org.thoughtcrime.securesms.components.DocumentView;
import org.thoughtcrime.securesms.components.QuoteView;
+import org.thoughtcrime.securesms.components.VcardView;
import org.thoughtcrime.securesms.components.WebxdcView;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.connect.DcHelper;
@@ -62,6 +66,7 @@ import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideClickListener;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.mms.StickerSlide;
+import org.thoughtcrime.securesms.mms.VcardSlide;
import org.thoughtcrime.securesms.reactions.ReactionsConversationView;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
@@ -71,6 +76,7 @@ import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.Stub;
+import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -113,6 +119,7 @@ public class ConversationItem extends BaseConversationItem
private @NonNull Stub documentViewStub;
private @NonNull Stub webxdcViewStub;
private Stub stickerStub;
+ private Stub vcardViewStub;
private @Nullable EventListener eventListener;
private int measureCalls;
@@ -146,6 +153,7 @@ public class ConversationItem extends BaseConversationItem
this.documentViewStub = new Stub<>(findViewById(R.id.document_view_stub));
this.webxdcViewStub = new Stub<>(findViewById(R.id.webxdc_view_stub));
this.stickerStub = new Stub<>(findViewById(R.id.sticker_view_stub));
+ this.vcardViewStub = new Stub<>(findViewById(R.id.vcard_view_stub));
this.groupSenderHolder = findViewById(R.id.group_sender_holder);
this.quoteView = findViewById(R.id.quote_view);
this.container = findViewById(R.id.container);
@@ -301,6 +309,11 @@ public class ConversationItem extends BaseConversationItem
webxdcViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
webxdcViewStub.get().setClickable(batchSelected.isEmpty());
}
+
+ if (vcardViewStub.resolved()) {
+ vcardViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
+ vcardViewStub.get().setClickable(batchSelected.isEmpty());
+ }
}
private void setContentDescription() {
@@ -315,6 +328,8 @@ public class ConversationItem extends BaseConversationItem
desc += documentViewStub.get().getDescription() + "\n";
} else if (webxdcViewStub.resolved() && webxdcViewStub.get().getVisibility() == View.VISIBLE) {
desc += webxdcViewStub.get().getDescription() + "\n";
+ } else if (vcardViewStub.resolved() && vcardViewStub.get().getVisibility() == View.VISIBLE) {
+ desc += vcardViewStub.get().getDescription() + "\n";
} else if (mediaThumbnailStub.resolved() && mediaThumbnailStub.get().getVisibility() == View.VISIBLE) {
desc += mediaThumbnailStub.get().getDescription() + "\n";
} else if (stickerStub.resolved() && stickerStub.get().getVisibility() == View.VISIBLE) {
@@ -362,6 +377,10 @@ public class ConversationItem extends BaseConversationItem
return dcMsg.getType()==DcMsg.DC_MSG_WEBXDC;
}
+ private boolean hasVcard(DcMsg dcMsg) {
+ return dcMsg.getType()==DcMsg.DC_MSG_VCARD;
+ }
+
private boolean hasDocument(DcMsg dcMsg) {
return dcMsg.getType()==DcMsg.DC_MSG_FILE && !dcMsg.isSetupMessage();
}
@@ -463,6 +482,7 @@ public class ConversationItem extends BaseConversationItem
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
+ if (vcardViewStub.resolved()) vcardViewStub.get().setVisibility(View.GONE);
//noinspection ConstantConditions
int duration = messageRecord.getDuration();
@@ -489,6 +509,7 @@ public class ConversationItem extends BaseConversationItem
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
+ if (vcardViewStub.resolved()) vcardViewStub.get().setVisibility(View.GONE);
//noinspection ConstantConditions
documentViewStub.get().setDocument(new DocumentSlide(context, messageRecord));
@@ -508,6 +529,7 @@ public class ConversationItem extends BaseConversationItem
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
+ if (vcardViewStub.resolved()) vcardViewStub.get().setVisibility(View.GONE);
webxdcViewStub.get().setWebxdc(messageRecord, context.getString(R.string.webxdc_app));
webxdcViewStub.get().setWebxdcClickListener(new ThumbnailClickListener());
@@ -520,12 +542,33 @@ public class ConversationItem extends BaseConversationItem
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
footer.setVisibility(VISIBLE);
}
+ else if (hasVcard(messageRecord)) {
+ vcardViewStub.get().setVisibility(View.VISIBLE);
+ if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
+ if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
+ if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
+ if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
+ if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
+
+ vcardViewStub.get().setVcard(glideRequests, new VcardSlide(context, messageRecord), rpc);
+ vcardViewStub.get().setVcardClickListener(new ThumbnailClickListener());
+ vcardViewStub.get().setOnLongClickListener(passthroughClickListener);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ vcardViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+ }
+
+ ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ footer.setVisibility(VISIBLE);
+ }
else if (hasThumbnail(messageRecord)) {
mediaThumbnailStub.get().setVisibility(View.VISIBLE);
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
+ if (vcardViewStub.resolved()) vcardViewStub.get().setVisibility(View.GONE);
Slide slide = MediaUtil.getSlideForMsg(context, messageRecord);
@@ -566,6 +609,7 @@ public class ConversationItem extends BaseConversationItem
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
+ if (vcardViewStub.resolved()) vcardViewStub.get().setVisibility(View.GONE);
bodyBubble.setBackgroundColor(Color.TRANSPARENT);
@@ -587,6 +631,7 @@ public class ConversationItem extends BaseConversationItem
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
+ if (vcardViewStub.resolved()) vcardViewStub.get().setVisibility(View.GONE);
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -839,6 +884,7 @@ public class ConversationItem extends BaseConversationItem
else if (audioViewStub.resolved()) audioViewStub.get().togglePlay();
else if (documentViewStub.resolved()) documentViewStub.get().performClick();
else if (webxdcViewStub.resolved()) webxdcViewStub.get().performClick();
+ else if (vcardViewStub.resolved()) vcardViewStub.get().performClick();
}
/// Event handlers
@@ -847,8 +893,37 @@ public class ConversationItem extends BaseConversationItem
public void onClick(final View v, final Slide slide) {
if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) {
performClick();
- } else if (messageRecord.getType() == DcMsg.DC_MSG_WEBXDC) {
+ } else if (slide.isWebxdcDocument()) {
WebxdcActivity.openWebxdcActivity(context, messageRecord);
+ } else if (slide.isVcard()) {
+ try {
+ String path = slide.asAttachment().getRealPath(context);
+ VcardContact vcardContact = rpc.parseVcard(path).get(0);
+ new AlertDialog.Builder(context)
+ .setMessage(context.getString(R.string.ask_start_chat_with, vcardContact.getDisplayName()))
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ try {
+ List contactIds = rpc.importVcard(dcContext.getAccountId(), path);
+ if (contactIds.size() > 0) {
+ int chatId = dcContext.createChatByContactId(contactIds.get(0));
+ if (chatId != 0) {
+ Intent intent = new Intent(context, ConversationActivity.class);
+ intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, chatId);
+ context.startActivity(intent);
+ return;
+ }
+ }
+ } catch (RpcException e) {
+ Log.e(TAG, "failed to import vCard", e);
+ }
+ Toast.makeText(context, context.getResources().getString(R.string.error), Toast.LENGTH_SHORT).show();
+ })
+ .setNegativeButton(R.string.cancel, null)
+ .show();
+ } catch (RpcException e) {
+ Log.e(TAG, "failed to parse vCard", e);
+ Toast.makeText(context, context.getResources().getString(R.string.error), Toast.LENGTH_SHORT).show();
+ }
} else if (MediaPreviewActivity.isTypeSupported(slide) && slide.getUri() != null) {
Intent intent = new Intent(context, MediaPreviewActivity.class);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
diff --git a/src/org/thoughtcrime/securesms/attachments/Attachment.java b/src/org/thoughtcrime/securesms/attachments/Attachment.java
index 593aface7..daec88d9c 100644
--- a/src/org/thoughtcrime/securesms/attachments/Attachment.java
+++ b/src/org/thoughtcrime/securesms/attachments/Attachment.java
@@ -1,9 +1,21 @@
package org.thoughtcrime.securesms.attachments;
+import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import org.thoughtcrime.securesms.connect.DcHelper;
+import org.thoughtcrime.securesms.mms.PartAuthority;
+import org.thoughtcrime.securesms.util.MediaUtil;
+import org.thoughtcrime.securesms.util.Util;
+
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
public abstract class Attachment {
@NonNull
@@ -88,4 +100,35 @@ public abstract class Attachment {
public int getHeight() {
return height;
}
+
+ public String getRealPath(Context context) {
+ try {
+ // get file in the blobdir as `/[-].`
+ String filename = getFileName();
+ String ext = "";
+ if(filename==null) {
+ filename = new SimpleDateFormat("yyyy-MM-dd-HH-mm").format(new Date());
+ ext = "." + MediaUtil.getExtensionFromMimeType(getContentType());
+ }
+ else {
+ int i = filename.lastIndexOf(".");
+ if(i>=0) {
+ ext = filename.substring(i);
+ filename = filename.substring(0, i);
+ }
+ }
+ String path = DcHelper.getBlobdirFile(DcHelper.getContext(context), filename, ext);
+
+ // copy content to this file
+ InputStream inputStream = PartAuthority.getAttachmentStream(context, getDataUri());
+ OutputStream outputStream = new FileOutputStream(path);
+ Util.copy(inputStream, outputStream);
+
+ return path;
+ }
+ catch(Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
}
diff --git a/src/org/thoughtcrime/securesms/attachments/DcAttachment.java b/src/org/thoughtcrime/securesms/attachments/DcAttachment.java
index 779f127d7..ce5164ad4 100644
--- a/src/org/thoughtcrime/securesms/attachments/DcAttachment.java
+++ b/src/org/thoughtcrime/securesms/attachments/DcAttachment.java
@@ -1,6 +1,8 @@
package org.thoughtcrime.securesms.attachments;
import java.io.File;
+
+import android.content.Context;
import android.net.Uri;
import androidx.annotation.Nullable;
@@ -35,4 +37,9 @@ public class DcAttachment extends Attachment {
}
return getDataUri();
}
+
+ @Override
+ public String getRealPath(Context context) {
+ return dcMsg.getFile();
+ }
}
diff --git a/src/org/thoughtcrime/securesms/components/QuoteView.java b/src/org/thoughtcrime/securesms/components/QuoteView.java
index fa9d4f256..7ffdbf6cc 100644
--- a/src/org/thoughtcrime/securesms/components/QuoteView.java
+++ b/src/org/thoughtcrime/securesms/components/QuoteView.java
@@ -7,6 +7,7 @@ import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -20,11 +21,14 @@ import androidx.annotation.RequiresApi;
import com.annimon.stream.Stream;
import com.b44t.messenger.DcContact;
import com.b44t.messenger.DcMsg;
+import com.b44t.messenger.rpc.RpcException;
+import com.b44t.messenger.rpc.VcardContact;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.json.JSONObject;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.Attachment;
+import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.Slide;
@@ -185,7 +189,7 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
}
private void setQuoteAttachment(@NonNull GlideRequests glideRequests, @NonNull SlideDeck slideDeck) {
- List thumbnailSlides = Stream.of(slideDeck.getSlides()).filter(s -> s.hasImage() || s.hasVideo() || s.hasSticker() || s.isWebxdcDocument()).limit(1).toList();
+ List thumbnailSlides = Stream.of(slideDeck.getSlides()).filter(s -> s.hasImage() || s.hasVideo() || s.hasSticker() || s.isWebxdcDocument() || s.isVcard()).limit(1).toList();
List audioSlides = Stream.of(slideDeck.getSlides()).filter(s -> s.hasAudio()).limit(1).toList();
List documentSlides = Stream.of(attachments.getSlides()).filter(Slide::hasDocument).limit(1).toList();
@@ -206,7 +210,22 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.into(thumbnailView);
} catch (Exception e) {
- e.printStackTrace();
+ Log.e(TAG, "failed to get webxdc icon", e);
+ thumbnailView.setVisibility(GONE);
+ }
+ } else if (thumbnailSlides.get(0).isVcard()) {
+ try {
+ VcardContact vcardContact = DcHelper.getRpc(getContext()).parseVcard(quotedMsg.getFile()).get(0);
+ Recipient recipient = new Recipient(getContext(), vcardContact);
+ glideRequests.load(recipient.getContactPhoto(getContext()))
+ .error(recipient.getFallbackAvatarDrawable(getContext(), false))
+ .centerCrop()
+ .override(getContext().getResources().getDimensionPixelSize(R.dimen.quote_thumb_size))
+ .diskCacheStrategy(DiskCacheStrategy.NONE)
+ .into(thumbnailView);
+ } catch (RpcException e) {
+ Log.e(TAG, "failed to parse vCard", e);
+ thumbnailView.setVisibility(GONE);
}
} else {
Uri thumbnailUri = thumbnailSlides.get(0).getUri();
diff --git a/src/org/thoughtcrime/securesms/components/VcardView.java b/src/org/thoughtcrime/securesms/components/VcardView.java
new file mode 100644
index 000000000..36b8e84b5
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/VcardView.java
@@ -0,0 +1,75 @@
+package org.thoughtcrime.securesms.components;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.b44t.messenger.rpc.Rpc;
+import com.b44t.messenger.rpc.RpcException;
+import com.b44t.messenger.rpc.VcardContact;
+
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.mms.GlideRequests;
+import org.thoughtcrime.securesms.mms.SlideClickListener;
+import org.thoughtcrime.securesms.mms.VcardSlide;
+import org.thoughtcrime.securesms.recipients.Recipient;
+
+public class VcardView extends FrameLayout {
+ private static final String TAG = VcardView.class.getSimpleName();
+
+ private final @NonNull AvatarView avatar;
+ private final @NonNull TextView name;
+ private final @NonNull TextView address;
+
+ private @Nullable SlideClickListener viewListener;
+ private @Nullable VcardSlide slide;
+
+ public VcardView(Context context) {
+ this(context, null);
+ }
+
+ public VcardView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public VcardView(final Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ inflate(context, R.layout.vcard_view, this);
+
+ this.avatar = findViewById(R.id.avatar);
+ this.name = findViewById(R.id.name);
+ this.address = findViewById(R.id.addr);
+
+ setOnClickListener(v -> {
+ if (viewListener != null && slide != null) {
+ viewListener.onClick(v, slide);
+ }
+ });
+ }
+
+ public void setVcardClickListener(@Nullable SlideClickListener listener) {
+ this.viewListener = listener;
+ }
+
+ public void setVcard(@NonNull GlideRequests glideRequests, final @NonNull VcardSlide slide, final @NonNull Rpc rpc) {
+ try {
+ VcardContact vcardContact = rpc.parseVcard(slide.asAttachment().getRealPath(getContext())).get(0);
+ name.setText(vcardContact.getDisplayName());
+ address.setText(vcardContact.getAddr());
+ avatar.setAvatar(glideRequests, new Recipient(getContext(), vcardContact), false);
+ this.slide = slide;
+ } catch (RpcException e) {
+ Log.e(TAG, "failed to parse vCard", e);
+ }
+ }
+
+ public String getDescription() {
+ return name.getText() + "\n" + address.getText();
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/connect/DcHelper.java b/src/org/thoughtcrime/securesms/connect/DcHelper.java
index 91dc1738f..09a0ae27d 100644
--- a/src/org/thoughtcrime/securesms/connect/DcHelper.java
+++ b/src/org/thoughtcrime/securesms/connect/DcHelper.java
@@ -260,6 +260,7 @@ public class DcHelper {
dcContext.setStockTranslation(177, context.getString(R.string.reaction_by_other));
dcContext.setStockTranslation(190, context.getString(R.string.secure_join_wait));
dcContext.setStockTranslation(191, context.getString(R.string.secure_join_wait_timeout));
+ dcContext.setStockTranslation(200, context.getString(R.string.contact));
}
public static File getImexDir() {
diff --git a/src/org/thoughtcrime/securesms/contacts/avatars/GeneratedContactPhoto.java b/src/org/thoughtcrime/securesms/contacts/avatars/GeneratedContactPhoto.java
index d9e50f40f..07888ff3f 100644
--- a/src/org/thoughtcrime/securesms/contacts/avatars/GeneratedContactPhoto.java
+++ b/src/org/thoughtcrime/securesms/contacts/avatars/GeneratedContactPhoto.java
@@ -24,17 +24,21 @@ public class GeneratedContactPhoto implements FallbackContactPhoto {
@Override
public Drawable asDrawable(Context context, int color) {
+ return asDrawable(context, color, true);
+ }
+
+ public Drawable asDrawable(Context context, int color, boolean roundShape) {
int targetSize = context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size);
- return TextDrawable.builder()
- .beginConfig()
- .width(targetSize)
- .height(targetSize)
- .textColor(Color.WHITE)
- .bold()
- .toUpperCase()
- .endConfig()
- .buildRound(getCharacter(name), color);
+ TextDrawable.IShapeBuilder builder = TextDrawable.builder()
+ .beginConfig()
+ .width(targetSize)
+ .height(targetSize)
+ .textColor(Color.WHITE)
+ .bold()
+ .toUpperCase()
+ .endConfig();
+ return roundShape? builder.buildRound(getCharacter(name), color) : builder.buildRect(getCharacter(name), color);
}
private String getCharacter(String name) {
diff --git a/src/org/thoughtcrime/securesms/contacts/avatars/VcardContactPhoto.java b/src/org/thoughtcrime/securesms/contacts/avatars/VcardContactPhoto.java
new file mode 100644
index 000000000..36fac9f10
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/contacts/avatars/VcardContactPhoto.java
@@ -0,0 +1,45 @@
+package org.thoughtcrime.securesms.contacts.avatars;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.b44t.messenger.rpc.VcardContact;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+
+public class VcardContactPhoto implements ContactPhoto {
+ private final VcardContact vContact;
+
+ public VcardContactPhoto(VcardContact vContact) {
+ this.vContact = vContact;
+ }
+
+ @Override
+ public InputStream openInputStream(Context context) throws IOException {
+ byte[] blob = vContact.getProfileImage();
+ return (blob == null)? null : new ByteArrayInputStream(blob);
+ }
+
+ @Override
+ public @Nullable Uri getUri(@NonNull Context context) {
+ return null;
+ }
+
+ @Override
+ public boolean isProfilePhoto() {
+ return true;
+ }
+
+ @Override
+ public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
+ messageDigest.update(vContact.getAddr().getBytes());
+ messageDigest.update(ByteBuffer.allocate(4).putFloat(vContact.getTimestamp()).array());
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
index 135d67823..eedc1dcff 100644
--- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
+++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
@@ -41,12 +41,12 @@ import androidx.appcompat.app.AlertDialog;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcMsg;
+import com.b44t.messenger.rpc.RpcException;
import com.b44t.messenger.util.concurrent.ListenableFuture;
import com.b44t.messenger.util.concurrent.ListenableFuture.Listener;
import com.b44t.messenger.util.concurrent.SettableFuture;
import org.thoughtcrime.securesms.ApplicationContext;
-import org.thoughtcrime.securesms.ConversationActivity;
import org.thoughtcrime.securesms.MediaPreviewActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.ShareLocationDialog;
@@ -58,6 +58,7 @@ import org.thoughtcrime.securesms.components.AudioView;
import org.thoughtcrime.securesms.components.DocumentView;
import org.thoughtcrime.securesms.components.RemovableEditableMediaView;
import org.thoughtcrime.securesms.components.ThumbnailView;
+import org.thoughtcrime.securesms.components.VcardView;
import org.thoughtcrime.securesms.components.WebxdcView;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
@@ -93,6 +94,7 @@ public class AttachmentManager {
private AudioView audioView;
private DocumentView documentView;
private WebxdcView webxdcView;
+ private VcardView vcardView;
//private SignalMapView mapView;
private final @NonNull List garbage = new LinkedList<>();
@@ -116,6 +118,7 @@ public class AttachmentManager {
this.audioView = ViewUtil.findById(root, R.id.attachment_audio);
this.documentView = ViewUtil.findById(root, R.id.attachment_document);
this.webxdcView = ViewUtil.findById(root, R.id.attachment_webxdc);
+ this.vcardView = ViewUtil.findById(root, R.id.attachment_vcard);
//this.mapView = ViewUtil.findById(root, R.id.attachment_location);
this.removableMediaView = ViewUtil.findById(root, R.id.removable_media_view);
@@ -126,6 +129,7 @@ public class AttachmentManager {
audioView.getBackground().setColorFilter(incomingBubbleColor, PorterDuff.Mode.MULTIPLY);
documentView.getBackground().setColorFilter(incomingBubbleColor, PorterDuff.Mode.MULTIPLY);
webxdcView.getBackground().setColorFilter(incomingBubbleColor, PorterDuff.Mode.MULTIPLY);
+ vcardView.getBackground().setColorFilter(incomingBubbleColor, PorterDuff.Mode.MULTIPLY);
}
}
@@ -308,6 +312,9 @@ public class AttachmentManager {
audioView.setAudio((AudioSlide) slide, 0);
removableMediaView.display(audioView, false);
result.set(true);
+ } else if (slide.isVcard()) {
+ vcardView.setVcard(glideRequests, (VcardSlide)slide, DcHelper.getRpc(context));
+ removableMediaView.display(vcardView, false);
} else if (slide.hasDocument()) {
if (slide.isWebxdcDocument()) {
DcMsg instance = msg != null ? msg : DcHelper.getContext(context).getMsg(slide.dcMsgId);
@@ -685,13 +692,25 @@ public class AttachmentManager {
DcContext dcContext = DcHelper.getContext(context);
DcMsg msg = new DcMsg(dcContext, DcMsg.DC_MSG_WEBXDC);
Attachment attachment = new UriAttachment(uri, null, MediaUtil.WEBXDC, AttachmentDatabase.TRANSFER_PROGRESS_STARTED, 0, 0, 0, fileName, null, false);
- String path = ConversationActivity.getRealPathFromAttachment(context, attachment);
+ String path = attachment.getRealPath(context);
msg.setFile(path, MediaUtil.WEBXDC);
dcContext.setDraft(chatId, msg);
return new DocumentSlide(context, msg);
- } else {
- return new DocumentSlide(context, uri, mimeType, dataSize, fileName);
}
+
+ if (mimeType.equals(MediaUtil.VCARD) || (fileName != null && (fileName.endsWith(".vcf") || fileName.endsWith(".vcard")))) {
+ VcardSlide slide = new VcardSlide(context, uri, dataSize, fileName);
+ String path = slide.asAttachment().getRealPath(context);
+ try {
+ if (DcHelper.getRpc(context).parseVcard(path).size() == 1) {
+ return slide;
+ }
+ } catch (RpcException e) {
+ e.printStackTrace();
+ }
+ }
+
+ return new DocumentSlide(context, uri, mimeType, dataSize, fileName);
default: throw new AssertionError("unrecognized enum");
}
}
diff --git a/src/org/thoughtcrime/securesms/mms/Slide.java b/src/org/thoughtcrime/securesms/mms/Slide.java
index 89d40e9ef..56fd0d842 100644
--- a/src/org/thoughtcrime/securesms/mms/Slide.java
+++ b/src/org/thoughtcrime/securesms/mms/Slide.java
@@ -98,6 +98,10 @@ public abstract class Slide {
return false;
}
+ public boolean isVcard() {
+ return false;
+ }
+
public boolean hasLocation() {
return false;
}
diff --git a/src/org/thoughtcrime/securesms/mms/VcardSlide.java b/src/org/thoughtcrime/securesms/mms/VcardSlide.java
new file mode 100644
index 000000000..01f1d7625
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/mms/VcardSlide.java
@@ -0,0 +1,26 @@
+package org.thoughtcrime.securesms.mms;
+
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.b44t.messenger.DcMsg;
+
+public class VcardSlide extends DocumentSlide {
+
+ public VcardSlide(Context context, DcMsg dcMsg) {
+ super(context, dcMsg);
+ }
+
+ public VcardSlide(@NonNull Context context, @NonNull Uri uri, long size, @Nullable String fileName) {
+ super(context, uri, "text/vcard", size, fileName);
+ }
+
+ @Override
+ public boolean isVcard() {
+ return true;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java
index 9225e79dd..3942c3bd6 100644
--- a/src/org/thoughtcrime/securesms/recipients/Recipient.java
+++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java
@@ -21,13 +21,15 @@ import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.text.TextUtils;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import android.text.TextUtils;
import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcContact;
import com.b44t.messenger.DcContext;
+import com.b44t.messenger.rpc.VcardContact;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
@@ -37,15 +39,14 @@ import org.thoughtcrime.securesms.contacts.avatars.GroupRecordContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.LocalFileContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.SystemContactPhoto;
+import org.thoughtcrime.securesms.contacts.avatars.VcardContactPhoto;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.util.Hash;
import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.Util;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
-import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;
@@ -65,6 +66,7 @@ public class Recipient {
private final @Nullable DcChat dcChat;
private @Nullable DcContact dcContact;
+ private final @Nullable VcardContact vContact;
public static @NonNull Recipient fromChat(@NonNull Context context, int dcMsgId) {
DcContext dcContext = DcHelper.getContext(context);
@@ -90,21 +92,26 @@ public class Recipient {
}
public Recipient(@NonNull Context context, @NonNull DcChat dcChat) {
- this(context, dcChat, null, null);
+ this(context, dcChat, null, null, null);
+ }
+
+ public Recipient(@NonNull Context context, @NonNull VcardContact vContact) {
+ this(context, null, null, null, vContact);
}
public Recipient(@NonNull Context context, @NonNull DcContact dcContact) {
- this(context, null, dcContact, null);
+ this(context, null, dcContact, null, null);
}
public Recipient(@NonNull Context context, @NonNull DcContact dcContact, @NonNull String profileName) {
- this(context, null, dcContact, profileName);
+ this(context, null, dcContact, profileName, null);
}
- private Recipient(@NonNull Context context, @Nullable DcChat dcChat, @Nullable DcContact dcContact, @Nullable String profileName) {
+ private Recipient(@NonNull Context context, @Nullable DcChat dcChat, @Nullable DcContact dcContact, @Nullable String profileName, @Nullable VcardContact vContact) {
this.dcChat = dcChat;
this.dcContact = dcContact;
this.profileName = profileName;
+ this.vContact = vContact;
this.contactUri = null;
this.systemContactPhoto = null;
this.customLabel = null;
@@ -141,6 +148,9 @@ public class Recipient {
else if(dcContact!=null) {
return dcContact.getDisplayName();
}
+ else if(vContact!=null) {
+ return vContact.getDisplayName();
+ }
return "";
}
@@ -168,18 +178,6 @@ public class Recipient {
return dcChat!=null && dcChat.isMultiUser();
}
- public @NonNull List loadParticipants(Context context) {
- List participants = new ArrayList<>();
- if (dcChat!=null) {
- DcContext dcContext = DcHelper.getAccounts(context).getAccount(dcChat.getAccountId());
- int[] contactIds = dcContext.getChatContacts(dcChat.getId());
- for (int contactId : contactIds) {
- participants.add(new Recipient(context, dcContext.getContact(contactId)));
- }
- }
- return participants;
- }
-
public synchronized void addListener(RecipientModifiedListener listener) {
listeners.add(listener);
}
@@ -200,15 +198,21 @@ public class Recipient {
else if(dcContact!=null) {
rgb = dcContact.getColor();
}
- int argb = Color.argb(0xFF, Color.red(rgb), Color.green(rgb), Color.blue(rgb));
- return argb;
+ else if(vContact!=null) {
+ rgb = Color.parseColor(vContact.getColor());
+ }
+ return Color.argb(0xFF, Color.red(rgb), Color.green(rgb), Color.blue(rgb));
}
public synchronized @NonNull Drawable getFallbackAvatarDrawable(Context context) {
- return getFallbackContactPhoto().asDrawable(context, getFallbackAvatarColor());
+ return getFallbackAvatarDrawable(context, true);
}
- public synchronized @NonNull FallbackContactPhoto getFallbackContactPhoto() {
+ public synchronized @NonNull Drawable getFallbackAvatarDrawable(Context context, boolean roundShape) {
+ return getFallbackContactPhoto().asDrawable(context, getFallbackAvatarColor(), roundShape);
+ }
+
+ public synchronized @NonNull GeneratedContactPhoto getFallbackContactPhoto() {
String name = getName();
if (!TextUtils.isEmpty(profileName)) return new GeneratedContactPhoto(profileName);
else if (!TextUtils.isEmpty(name)) return new GeneratedContactPhoto(name);
@@ -231,6 +235,10 @@ public class Recipient {
}
}
+ if (vContact!=null && vContact.hasProfileImage()) {
+ return new VcardContactPhoto(vContact);
+ }
+
if (systemContactPhoto != null) {
return new SystemContactPhoto(address, systemContactPhoto, 0);
}
@@ -262,7 +270,7 @@ public class Recipient {
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || !(o instanceof Recipient)) return false;
+ if (!(o instanceof Recipient)) return false;
Recipient that = (Recipient) o;
@@ -290,6 +298,7 @@ public class Recipient {
return dcChat!=null? dcChat : new DcChat(0, 0);
}
+ @NonNull
@Override
public String toString() {
return "Recipient{" +
diff --git a/src/org/thoughtcrime/securesms/util/MediaUtil.java b/src/org/thoughtcrime/securesms/util/MediaUtil.java
index 70ca04ce4..7b205b992 100644
--- a/src/org/thoughtcrime/securesms/util/MediaUtil.java
+++ b/src/org/thoughtcrime/securesms/util/MediaUtil.java
@@ -26,6 +26,7 @@ import org.thoughtcrime.securesms.mms.ImageSlide;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.StickerSlide;
+import org.thoughtcrime.securesms.mms.VcardSlide;
import org.thoughtcrime.securesms.mms.VideoSlide;
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
@@ -48,6 +49,7 @@ public class MediaUtil {
public static final String VIDEO_UNSPECIFIED = "video/*";
public static final String OCTET = "application/octet-stream";
public static final String WEBXDC = "application/webxdc+zip";
+ public static final String VCARD = "text/vcard";
public static Slide getSlideForMsg(Context context, DcMsg dcMsg) {
@@ -63,6 +65,8 @@ public class MediaUtil {
} else if (dcMsg.getType() == DcMsg.DC_MSG_AUDIO
|| dcMsg.getType() == DcMsg.DC_MSG_VOICE) {
slide = new AudioSlide(context, dcMsg);
+ } else if (dcMsg.getType() == DcMsg.DC_MSG_VCARD) {
+ slide = new VcardSlide(context, dcMsg);
} else if (dcMsg.getType() == DcMsg.DC_MSG_FILE
|| dcMsg.getType() == DcMsg.DC_MSG_WEBXDC) {
slide = new DocumentSlide(context, dcMsg);
@@ -301,6 +305,8 @@ public class MediaUtil {
return "webp";
case WEBXDC:
return "xdc";
+ case VCARD:
+ return "vcf";
}
return null;
}