mirror of
https://github.com/deltachat/deltachat-android.git
synced 2025-10-03 17:59:39 +02:00
display vcard view type and allow to import/export contacts as vcard (#3043)
* implement Rpc API for parse_vcard * allow to create Recipient from VcardContact * display vcard view-type in incoming/outgoing messages * fix linter warnings in Recipient class * properly show vcard view-type in quoted messages * display fallback avatar in quotes * allow to attach vcard * implement basic click listener * timestamp type got changed from float to int * set stick translation for "contact" * share contact as vcard using the new Rpc.makeVcard API * allow to import contact when clicking vcard
This commit is contained in:
parent
1884f9bc4f
commit
c705790d26
23 changed files with 565 additions and 96 deletions
|
@ -69,6 +69,17 @@
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:background="@drawable/message_bubble_background_sent_alone"/>
|
android:background="@drawable/message_bubble_background_sent_alone"/>
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.VcardView
|
||||||
|
android:id="@+id/attachment_vcard"
|
||||||
|
android:layout_width="230dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_marginLeft="10dp"
|
||||||
|
android:layout_marginRight="10dp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:background="@drawable/message_bubble_background_sent_alone"/>
|
||||||
|
|
||||||
</org.thoughtcrime.securesms.components.RemovableEditableMediaView>
|
</org.thoughtcrime.securesms.components.RemovableEditableMediaView>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
|
@ -153,6 +153,16 @@
|
||||||
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||||
android:layout_marginRight="@dimen/message_bubble_horizontal_padding" />
|
android:layout_marginRight="@dimen/message_bubble_horizontal_padding" />
|
||||||
|
|
||||||
|
<ViewStub
|
||||||
|
android:id="@+id/vcard_view_stub"
|
||||||
|
android:layout="@layout/conversation_item_vcard"
|
||||||
|
android:layout_width="210dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/message_bubble_top_padding"
|
||||||
|
android:layout_marginBottom="@dimen/message_bubble_collapsed_footer_padding"
|
||||||
|
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||||
|
android:layout_marginRight="@dimen/message_bubble_horizontal_padding" />
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||||
android:id="@+id/conversation_item_body"
|
android:id="@+id/conversation_item_body"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
|
@ -131,6 +131,16 @@
|
||||||
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||||
android:layout_marginRight="@dimen/message_bubble_horizontal_padding" />
|
android:layout_marginRight="@dimen/message_bubble_horizontal_padding" />
|
||||||
|
|
||||||
|
<ViewStub
|
||||||
|
android:id="@+id/vcard_view_stub"
|
||||||
|
android:layout="@layout/conversation_item_vcard"
|
||||||
|
android:layout_width="210dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/message_bubble_top_padding"
|
||||||
|
android:layout_marginBottom="@dimen/message_bubble_collapsed_footer_padding"
|
||||||
|
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||||
|
android:layout_marginRight="@dimen/message_bubble_horizontal_padding" />
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||||
android:id="@+id/conversation_item_body"
|
android:id="@+id/conversation_item_body"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
10
res/layout/conversation_item_vcard.xml
Normal file
10
res/layout/conversation_item_vcard.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<org.thoughtcrime.securesms.components.VcardView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/vcard_view"
|
||||||
|
android:layout_width="210dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"/>
|
54
res/layout/vcard_view.xml
Normal file
54
res/layout/vcard_view.xml
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<org.thoughtcrime.securesms.contacts.ContactSelectionListItem
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.AvatarView
|
||||||
|
android:id="@+id/avatar"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp" />
|
||||||
|
|
||||||
|
<LinearLayout android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||||
|
android:id="@+id/name"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
|
||||||
|
android:drawablePadding="5dp"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="Frieeeeeeedrich Nieeeeeeeeeetzsche" />
|
||||||
|
<!-- Attention: Using android:maxLines="1", if the name is an emoji followed by a
|
||||||
|
long word and the chat is muted, then the long word is not shown at all
|
||||||
|
(instead of using `…`). That's why we use android:singleLine="true" -->
|
||||||
|
|
||||||
|
|
||||||
|
<TextView android:id="@+id/addr"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textDirection="ltr"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:fontFamily="sans-serif-light"
|
||||||
|
tools:text="user@example.com" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</org.thoughtcrime.securesms.contacts.ContactSelectionListItem>
|
|
@ -20,6 +20,7 @@ public class DcMsg {
|
||||||
public final static int DC_MSG_FILE = 60;
|
public final static int DC_MSG_FILE = 60;
|
||||||
public final static int DC_MSG_VIDEOCHAT_INVITATION = 70;
|
public final static int DC_MSG_VIDEOCHAT_INVITATION = 70;
|
||||||
public final static int DC_MSG_WEBXDC = 80;
|
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_UNKNOWN = 0;
|
||||||
public final static int DC_INFO_GROUP_NAME_CHANGED = 2;
|
public final static int DC_INFO_GROUP_NAME_CHANGED = 2;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
@ -95,6 +96,20 @@ public class Rpc {
|
||||||
return gson.fromJson(getResult("get_system_info"), mapType.getType());
|
return gson.fromJson(getResult("get_system_info"), mapType.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<VcardContact> parseVcard(String path) throws RpcException {
|
||||||
|
TypeToken<List<VcardContact>> listType = new TypeToken<List<VcardContact>>(){};
|
||||||
|
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<Integer> importVcard(int accountId, String path) throws RpcException {
|
||||||
|
TypeToken<List<Integer>> listType = new TypeToken<List<Integer>>(){};
|
||||||
|
return gson.fromJson(getResult("import_vcard", accountId, path), listType.getType());
|
||||||
|
}
|
||||||
|
|
||||||
public HttpResponse getHttpResponse(int accountId, String url) throws RpcException {
|
public HttpResponse getHttpResponse(int accountId, String url) throws RpcException {
|
||||||
return gson.fromJson(getResult("get_http_response", accountId, url), HttpResponse.class);
|
return gson.fromJson(getResult("get_http_response", accountId, url), HttpResponse.class);
|
||||||
}
|
}
|
||||||
|
|
60
src/com/b44t/messenger/rpc/VcardContact.java
Normal file
60
src/com/b44t/messenger/rpc/VcardContact.java
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,26 +2,17 @@ package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
import com.b44t.messenger.DcContext;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||||
|
|
||||||
public class AttachContactActivity extends ContactSelectionActivity {
|
public class AttachContactActivity extends ContactSelectionActivity {
|
||||||
|
|
||||||
public static final String NAME_EXTRA = "name_extra";
|
public static final String CONTACT_ID_EXTRA = "contact_id_extra";
|
||||||
public static final String ADDR_EXTRA = "addr_extra";
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContactSelected(int specialId, String addr) {
|
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 intent = new Intent();
|
||||||
intent.putExtra(NAME_EXTRA, name);
|
int contactId = DcHelper.getContext(this).lookupContactIdByAddr(addr);
|
||||||
intent.putExtra(ADDR_EXTRA, addr);
|
intent.putExtra(CONTACT_ID_EXTRA, contactId);
|
||||||
setResult(RESULT_OK, intent);
|
setResult(RESULT_OK, intent);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,8 @@ import com.b44t.messenger.DcContact;
|
||||||
import com.b44t.messenger.DcContext;
|
import com.b44t.messenger.DcContext;
|
||||||
import com.b44t.messenger.DcEvent;
|
import com.b44t.messenger.DcEvent;
|
||||||
import com.b44t.messenger.DcMsg;
|
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.ListenableFuture;
|
||||||
import com.b44t.messenger.util.concurrent.SettableFuture;
|
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.AudioSlide;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
|
||||||
import org.thoughtcrime.securesms.mms.QuoteModel;
|
import org.thoughtcrime.securesms.mms.QuoteModel;
|
||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
|
@ -125,12 +126,7 @@ import org.thoughtcrime.securesms.video.recode.VideoRecoder;
|
||||||
import org.thoughtcrime.securesms.videochat.VideochatUtil;
|
import org.thoughtcrime.securesms.videochat.VideochatUtil;
|
||||||
|
|
||||||
import java.io.File;
|
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.ArrayList;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
@ -193,6 +189,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
private ApplicationContext context;
|
private ApplicationContext context;
|
||||||
private Recipient recipient;
|
private Recipient recipient;
|
||||||
private DcContext dcContext;
|
private DcContext dcContext;
|
||||||
|
private Rpc rpc;
|
||||||
private DcChat dcChat = new DcChat(0, 0);
|
private DcChat dcChat = new DcChat(0, 0);
|
||||||
private int chatId;
|
private int chatId;
|
||||||
private final boolean isSecureText = true;
|
private final boolean isSecureText = true;
|
||||||
|
@ -204,6 +201,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
protected void onCreate(Bundle state, boolean ready) {
|
protected void onCreate(Bundle state, boolean ready) {
|
||||||
this.context = ApplicationContext.getInstance(getApplicationContext());
|
this.context = ApplicationContext.getInstance(getApplicationContext());
|
||||||
this.dcContext = DcHelper.getContext(context);
|
this.dcContext = DcHelper.getContext(context);
|
||||||
|
this.rpc = DcHelper.getRpc(context);
|
||||||
|
|
||||||
supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR_OVERLAY);
|
supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR_OVERLAY);
|
||||||
setContentView(R.layout.conversation_activity);
|
setContentView(R.layout.conversation_activity);
|
||||||
|
@ -978,9 +976,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAttachmentContactInfo(Intent data) {
|
private void addAttachmentContactInfo(Intent data) {
|
||||||
String name = data.getStringExtra(AttachContactActivity.NAME_EXTRA);
|
int contactId = data.getIntExtra(AttachContactActivity.CONTACT_ID_EXTRA, 0);
|
||||||
String mail = data.getStringExtra(AttachContactActivity.ADDR_EXTRA);
|
if (contactId == 0) {
|
||||||
composeText.append(name + "\n" + mail);
|
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() {
|
private boolean isMultiUser() {
|
||||||
|
@ -991,39 +998,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
return dcChat.getVisibility() == DcChat.DC_CHAT_VISIBILITY_ARCHIVED;
|
return dcChat.getVisibility() == DcChat.DC_CHAT_VISIBILITY_ARCHIVED;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getRealPathFromAttachment(Context context, Attachment attachment) {
|
|
||||||
try {
|
|
||||||
// get file in the blobdir as `<blobdir>/<name>[-<uniqueNumber>].<ext>`
|
|
||||||
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
|
//////// send message or save draft
|
||||||
|
|
||||||
protected static final int ACTION_SEND_OUT = 1;
|
protected static final int ACTION_SEND_OUT = 1;
|
||||||
|
@ -1076,7 +1050,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
} else {
|
} else {
|
||||||
msg = new DcMsg(dcContext, DcMsg.DC_MSG_FILE);
|
msg = new DcMsg(dcContext, DcMsg.DC_MSG_FILE);
|
||||||
}
|
}
|
||||||
String path = getRealPathFromAttachment(this, attachment);
|
String path = attachment.getRealPath(this);
|
||||||
msg.setFile(path, null);
|
msg.setFile(path, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1336,7 +1310,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
private void sendSticker(@NonNull Uri uri, String contentType) {
|
private void sendSticker(@NonNull Uri uri, String contentType) {
|
||||||
Attachment attachment = new UriAttachment(uri, null, contentType,
|
Attachment attachment = new UriAttachment(uri, null, contentType,
|
||||||
AttachmentDatabase.TRANSFER_PROGRESS_STARTED, 0, 0, 0, null, null, false);
|
AttachmentDatabase.TRANSFER_PROGRESS_STARTED, 0, 0, 0, null, null, false);
|
||||||
String path = getRealPathFromAttachment(this, attachment);
|
String path = attachment.getRealPath(this);
|
||||||
|
|
||||||
Optional<QuoteModel> quote = inputPanel.getQuote();
|
Optional<QuoteModel> quote = inputPanel.getQuote();
|
||||||
inputPanel.clearQuote();
|
inputPanel.clearQuote();
|
||||||
|
|
|
@ -32,16 +32,19 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.DimenRes;
|
import androidx.annotation.DimenRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import com.b44t.messenger.DcChat;
|
import com.b44t.messenger.DcChat;
|
||||||
import com.b44t.messenger.DcContact;
|
import com.b44t.messenger.DcContact;
|
||||||
import com.b44t.messenger.DcMsg;
|
import com.b44t.messenger.DcMsg;
|
||||||
import com.b44t.messenger.rpc.Reactions;
|
import com.b44t.messenger.rpc.Reactions;
|
||||||
import com.b44t.messenger.rpc.RpcException;
|
import com.b44t.messenger.rpc.RpcException;
|
||||||
|
import com.b44t.messenger.rpc.VcardContact;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
|
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
|
||||||
import org.thoughtcrime.securesms.components.AudioView;
|
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.ConversationItemThumbnail;
|
||||||
import org.thoughtcrime.securesms.components.DocumentView;
|
import org.thoughtcrime.securesms.components.DocumentView;
|
||||||
import org.thoughtcrime.securesms.components.QuoteView;
|
import org.thoughtcrime.securesms.components.QuoteView;
|
||||||
|
import org.thoughtcrime.securesms.components.VcardView;
|
||||||
import org.thoughtcrime.securesms.components.WebxdcView;
|
import org.thoughtcrime.securesms.components.WebxdcView;
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
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.SlideClickListener;
|
||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||||
import org.thoughtcrime.securesms.mms.StickerSlide;
|
import org.thoughtcrime.securesms.mms.StickerSlide;
|
||||||
|
import org.thoughtcrime.securesms.mms.VcardSlide;
|
||||||
import org.thoughtcrime.securesms.reactions.ReactionsConversationView;
|
import org.thoughtcrime.securesms.reactions.ReactionsConversationView;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
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.ViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.views.Stub;
|
import org.thoughtcrime.securesms.util.views.Stub;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -113,6 +119,7 @@ public class ConversationItem extends BaseConversationItem
|
||||||
private @NonNull Stub<DocumentView> documentViewStub;
|
private @NonNull Stub<DocumentView> documentViewStub;
|
||||||
private @NonNull Stub<WebxdcView> webxdcViewStub;
|
private @NonNull Stub<WebxdcView> webxdcViewStub;
|
||||||
private Stub<BorderlessImageView> stickerStub;
|
private Stub<BorderlessImageView> stickerStub;
|
||||||
|
private Stub<VcardView> vcardViewStub;
|
||||||
private @Nullable EventListener eventListener;
|
private @Nullable EventListener eventListener;
|
||||||
|
|
||||||
private int measureCalls;
|
private int measureCalls;
|
||||||
|
@ -146,6 +153,7 @@ public class ConversationItem extends BaseConversationItem
|
||||||
this.documentViewStub = new Stub<>(findViewById(R.id.document_view_stub));
|
this.documentViewStub = new Stub<>(findViewById(R.id.document_view_stub));
|
||||||
this.webxdcViewStub = new Stub<>(findViewById(R.id.webxdc_view_stub));
|
this.webxdcViewStub = new Stub<>(findViewById(R.id.webxdc_view_stub));
|
||||||
this.stickerStub = new Stub<>(findViewById(R.id.sticker_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.groupSenderHolder = findViewById(R.id.group_sender_holder);
|
||||||
this.quoteView = findViewById(R.id.quote_view);
|
this.quoteView = findViewById(R.id.quote_view);
|
||||||
this.container = findViewById(R.id.container);
|
this.container = findViewById(R.id.container);
|
||||||
|
@ -301,6 +309,11 @@ public class ConversationItem extends BaseConversationItem
|
||||||
webxdcViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
|
webxdcViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
|
||||||
webxdcViewStub.get().setClickable(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() {
|
private void setContentDescription() {
|
||||||
|
@ -315,6 +328,8 @@ public class ConversationItem extends BaseConversationItem
|
||||||
desc += documentViewStub.get().getDescription() + "\n";
|
desc += documentViewStub.get().getDescription() + "\n";
|
||||||
} else if (webxdcViewStub.resolved() && webxdcViewStub.get().getVisibility() == View.VISIBLE) {
|
} else if (webxdcViewStub.resolved() && webxdcViewStub.get().getVisibility() == View.VISIBLE) {
|
||||||
desc += webxdcViewStub.get().getDescription() + "\n";
|
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) {
|
} else if (mediaThumbnailStub.resolved() && mediaThumbnailStub.get().getVisibility() == View.VISIBLE) {
|
||||||
desc += mediaThumbnailStub.get().getDescription() + "\n";
|
desc += mediaThumbnailStub.get().getDescription() + "\n";
|
||||||
} else if (stickerStub.resolved() && stickerStub.get().getVisibility() == View.VISIBLE) {
|
} 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;
|
return dcMsg.getType()==DcMsg.DC_MSG_WEBXDC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasVcard(DcMsg dcMsg) {
|
||||||
|
return dcMsg.getType()==DcMsg.DC_MSG_VCARD;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasDocument(DcMsg dcMsg) {
|
private boolean hasDocument(DcMsg dcMsg) {
|
||||||
return dcMsg.getType()==DcMsg.DC_MSG_FILE && !dcMsg.isSetupMessage();
|
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 (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||||
if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
|
if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
|
||||||
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
||||||
|
if (vcardViewStub.resolved()) vcardViewStub.get().setVisibility(View.GONE);
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
int duration = messageRecord.getDuration();
|
int duration = messageRecord.getDuration();
|
||||||
|
@ -489,6 +509,7 @@ public class ConversationItem extends BaseConversationItem
|
||||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||||
if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
|
if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
|
||||||
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
||||||
|
if (vcardViewStub.resolved()) vcardViewStub.get().setVisibility(View.GONE);
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
documentViewStub.get().setDocument(new DocumentSlide(context, messageRecord));
|
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 (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||||
if (stickerStub.resolved()) stickerStub.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().setWebxdc(messageRecord, context.getString(R.string.webxdc_app));
|
||||||
webxdcViewStub.get().setWebxdcClickListener(new ThumbnailClickListener());
|
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);
|
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
footer.setVisibility(VISIBLE);
|
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)) {
|
else if (hasThumbnail(messageRecord)) {
|
||||||
mediaThumbnailStub.get().setVisibility(View.VISIBLE);
|
mediaThumbnailStub.get().setVisibility(View.VISIBLE);
|
||||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||||
if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
|
if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
|
||||||
if (stickerStub.resolved()) stickerStub.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);
|
Slide slide = MediaUtil.getSlideForMsg(context, messageRecord);
|
||||||
|
|
||||||
|
@ -566,6 +609,7 @@ public class ConversationItem extends BaseConversationItem
|
||||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||||
if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
|
if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
|
||||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||||
|
if (vcardViewStub.resolved()) vcardViewStub.get().setVisibility(View.GONE);
|
||||||
|
|
||||||
bodyBubble.setBackgroundColor(Color.TRANSPARENT);
|
bodyBubble.setBackgroundColor(Color.TRANSPARENT);
|
||||||
|
|
||||||
|
@ -587,6 +631,7 @@ public class ConversationItem extends BaseConversationItem
|
||||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||||
if (webxdcViewStub.resolved()) webxdcViewStub.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(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
ViewUtil.updateLayoutParams(groupSenderHolder, 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 (audioViewStub.resolved()) audioViewStub.get().togglePlay();
|
||||||
else if (documentViewStub.resolved()) documentViewStub.get().performClick();
|
else if (documentViewStub.resolved()) documentViewStub.get().performClick();
|
||||||
else if (webxdcViewStub.resolved()) webxdcViewStub.get().performClick();
|
else if (webxdcViewStub.resolved()) webxdcViewStub.get().performClick();
|
||||||
|
else if (vcardViewStub.resolved()) vcardViewStub.get().performClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Event handlers
|
/// Event handlers
|
||||||
|
@ -847,8 +893,37 @@ public class ConversationItem extends BaseConversationItem
|
||||||
public void onClick(final View v, final Slide slide) {
|
public void onClick(final View v, final Slide slide) {
|
||||||
if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) {
|
if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) {
|
||||||
performClick();
|
performClick();
|
||||||
} else if (messageRecord.getType() == DcMsg.DC_MSG_WEBXDC) {
|
} else if (slide.isWebxdcDocument()) {
|
||||||
WebxdcActivity.openWebxdcActivity(context, messageRecord);
|
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<Integer> 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) {
|
} else if (MediaPreviewActivity.isTypeSupported(slide) && slide.getUri() != null) {
|
||||||
Intent intent = new Intent(context, MediaPreviewActivity.class);
|
Intent intent = new Intent(context, MediaPreviewActivity.class);
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
|
|
@ -1,9 +1,21 @@
|
||||||
package org.thoughtcrime.securesms.attachments;
|
package org.thoughtcrime.securesms.attachments;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
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 {
|
public abstract class Attachment {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@ -88,4 +100,35 @@ public abstract class Attachment {
|
||||||
public int getHeight() {
|
public int getHeight() {
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRealPath(Context context) {
|
||||||
|
try {
|
||||||
|
// get file in the blobdir as `<blobdir>/<name>[-<uniqueNumber>].<ext>`
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package org.thoughtcrime.securesms.attachments;
|
package org.thoughtcrime.securesms.attachments;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
@ -35,4 +37,9 @@ public class DcAttachment extends Attachment {
|
||||||
}
|
}
|
||||||
return getDataUri();
|
return getDataUri();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRealPath(Context context) {
|
||||||
|
return dcMsg.getFile();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
@ -20,11 +21,14 @@ import androidx.annotation.RequiresApi;
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
import com.b44t.messenger.DcContact;
|
import com.b44t.messenger.DcContact;
|
||||||
import com.b44t.messenger.DcMsg;
|
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 com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||||
|
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.mms.Slide;
|
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) {
|
private void setQuoteAttachment(@NonNull GlideRequests glideRequests, @NonNull SlideDeck slideDeck) {
|
||||||
List<Slide> thumbnailSlides = Stream.of(slideDeck.getSlides()).filter(s -> s.hasImage() || s.hasVideo() || s.hasSticker() || s.isWebxdcDocument()).limit(1).toList();
|
List<Slide> thumbnailSlides = Stream.of(slideDeck.getSlides()).filter(s -> s.hasImage() || s.hasVideo() || s.hasSticker() || s.isWebxdcDocument() || s.isVcard()).limit(1).toList();
|
||||||
List<Slide> audioSlides = Stream.of(slideDeck.getSlides()).filter(s -> s.hasAudio()).limit(1).toList();
|
List<Slide> audioSlides = Stream.of(slideDeck.getSlides()).filter(s -> s.hasAudio()).limit(1).toList();
|
||||||
List<Slide> documentSlides = Stream.of(attachments.getSlides()).filter(Slide::hasDocument).limit(1).toList();
|
List<Slide> 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)
|
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||||
.into(thumbnailView);
|
.into(thumbnailView);
|
||||||
} catch (Exception e) {
|
} 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 {
|
} else {
|
||||||
Uri thumbnailUri = thumbnailSlides.get(0).getUri();
|
Uri thumbnailUri = thumbnailSlides.get(0).getUri();
|
||||||
|
|
75
src/org/thoughtcrime/securesms/components/VcardView.java
Normal file
75
src/org/thoughtcrime/securesms/components/VcardView.java
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -260,6 +260,7 @@ public class DcHelper {
|
||||||
dcContext.setStockTranslation(177, context.getString(R.string.reaction_by_other));
|
dcContext.setStockTranslation(177, context.getString(R.string.reaction_by_other));
|
||||||
dcContext.setStockTranslation(190, context.getString(R.string.secure_join_wait));
|
dcContext.setStockTranslation(190, context.getString(R.string.secure_join_wait));
|
||||||
dcContext.setStockTranslation(191, context.getString(R.string.secure_join_wait_timeout));
|
dcContext.setStockTranslation(191, context.getString(R.string.secure_join_wait_timeout));
|
||||||
|
dcContext.setStockTranslation(200, context.getString(R.string.contact));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static File getImexDir() {
|
public static File getImexDir() {
|
||||||
|
|
|
@ -24,17 +24,21 @@ public class GeneratedContactPhoto implements FallbackContactPhoto {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Drawable asDrawable(Context context, int color) {
|
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);
|
int targetSize = context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size);
|
||||||
|
|
||||||
return TextDrawable.builder()
|
TextDrawable.IShapeBuilder builder = TextDrawable.builder()
|
||||||
.beginConfig()
|
.beginConfig()
|
||||||
.width(targetSize)
|
.width(targetSize)
|
||||||
.height(targetSize)
|
.height(targetSize)
|
||||||
.textColor(Color.WHITE)
|
.textColor(Color.WHITE)
|
||||||
.bold()
|
.bold()
|
||||||
.toUpperCase()
|
.toUpperCase()
|
||||||
.endConfig()
|
.endConfig();
|
||||||
.buildRound(getCharacter(name), color);
|
return roundShape? builder.buildRound(getCharacter(name), color) : builder.buildRect(getCharacter(name), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getCharacter(String name) {
|
private String getCharacter(String name) {
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,12 +41,12 @@ import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import com.b44t.messenger.DcContext;
|
import com.b44t.messenger.DcContext;
|
||||||
import com.b44t.messenger.DcMsg;
|
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;
|
||||||
import com.b44t.messenger.util.concurrent.ListenableFuture.Listener;
|
import com.b44t.messenger.util.concurrent.ListenableFuture.Listener;
|
||||||
import com.b44t.messenger.util.concurrent.SettableFuture;
|
import com.b44t.messenger.util.concurrent.SettableFuture;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
import org.thoughtcrime.securesms.ConversationActivity;
|
|
||||||
import org.thoughtcrime.securesms.MediaPreviewActivity;
|
import org.thoughtcrime.securesms.MediaPreviewActivity;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.ShareLocationDialog;
|
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.DocumentView;
|
||||||
import org.thoughtcrime.securesms.components.RemovableEditableMediaView;
|
import org.thoughtcrime.securesms.components.RemovableEditableMediaView;
|
||||||
import org.thoughtcrime.securesms.components.ThumbnailView;
|
import org.thoughtcrime.securesms.components.ThumbnailView;
|
||||||
|
import org.thoughtcrime.securesms.components.VcardView;
|
||||||
import org.thoughtcrime.securesms.components.WebxdcView;
|
import org.thoughtcrime.securesms.components.WebxdcView;
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
|
@ -93,6 +94,7 @@ public class AttachmentManager {
|
||||||
private AudioView audioView;
|
private AudioView audioView;
|
||||||
private DocumentView documentView;
|
private DocumentView documentView;
|
||||||
private WebxdcView webxdcView;
|
private WebxdcView webxdcView;
|
||||||
|
private VcardView vcardView;
|
||||||
//private SignalMapView mapView;
|
//private SignalMapView mapView;
|
||||||
|
|
||||||
private final @NonNull List<Uri> garbage = new LinkedList<>();
|
private final @NonNull List<Uri> garbage = new LinkedList<>();
|
||||||
|
@ -116,6 +118,7 @@ public class AttachmentManager {
|
||||||
this.audioView = ViewUtil.findById(root, R.id.attachment_audio);
|
this.audioView = ViewUtil.findById(root, R.id.attachment_audio);
|
||||||
this.documentView = ViewUtil.findById(root, R.id.attachment_document);
|
this.documentView = ViewUtil.findById(root, R.id.attachment_document);
|
||||||
this.webxdcView = ViewUtil.findById(root, R.id.attachment_webxdc);
|
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.mapView = ViewUtil.findById(root, R.id.attachment_location);
|
||||||
this.removableMediaView = ViewUtil.findById(root, R.id.removable_media_view);
|
this.removableMediaView = ViewUtil.findById(root, R.id.removable_media_view);
|
||||||
|
|
||||||
|
@ -126,6 +129,7 @@ public class AttachmentManager {
|
||||||
audioView.getBackground().setColorFilter(incomingBubbleColor, PorterDuff.Mode.MULTIPLY);
|
audioView.getBackground().setColorFilter(incomingBubbleColor, PorterDuff.Mode.MULTIPLY);
|
||||||
documentView.getBackground().setColorFilter(incomingBubbleColor, PorterDuff.Mode.MULTIPLY);
|
documentView.getBackground().setColorFilter(incomingBubbleColor, PorterDuff.Mode.MULTIPLY);
|
||||||
webxdcView.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);
|
audioView.setAudio((AudioSlide) slide, 0);
|
||||||
removableMediaView.display(audioView, false);
|
removableMediaView.display(audioView, false);
|
||||||
result.set(true);
|
result.set(true);
|
||||||
|
} else if (slide.isVcard()) {
|
||||||
|
vcardView.setVcard(glideRequests, (VcardSlide)slide, DcHelper.getRpc(context));
|
||||||
|
removableMediaView.display(vcardView, false);
|
||||||
} else if (slide.hasDocument()) {
|
} else if (slide.hasDocument()) {
|
||||||
if (slide.isWebxdcDocument()) {
|
if (slide.isWebxdcDocument()) {
|
||||||
DcMsg instance = msg != null ? msg : DcHelper.getContext(context).getMsg(slide.dcMsgId);
|
DcMsg instance = msg != null ? msg : DcHelper.getContext(context).getMsg(slide.dcMsgId);
|
||||||
|
@ -685,13 +692,25 @@ public class AttachmentManager {
|
||||||
DcContext dcContext = DcHelper.getContext(context);
|
DcContext dcContext = DcHelper.getContext(context);
|
||||||
DcMsg msg = new DcMsg(dcContext, DcMsg.DC_MSG_WEBXDC);
|
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);
|
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);
|
msg.setFile(path, MediaUtil.WEBXDC);
|
||||||
dcContext.setDraft(chatId, msg);
|
dcContext.setDraft(chatId, msg);
|
||||||
return new DocumentSlide(context, 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");
|
default: throw new AssertionError("unrecognized enum");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,6 +98,10 @@ public abstract class Slide {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isVcard() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasLocation() {
|
public boolean hasLocation() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
26
src/org/thoughtcrime/securesms/mms/VcardSlide.java
Normal file
26
src/org/thoughtcrime/securesms/mms/VcardSlide.java
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,13 +21,15 @@ import android.content.Context;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import com.b44t.messenger.DcChat;
|
import com.b44t.messenger.DcChat;
|
||||||
import com.b44t.messenger.DcContact;
|
import com.b44t.messenger.DcContact;
|
||||||
import com.b44t.messenger.DcContext;
|
import com.b44t.messenger.DcContext;
|
||||||
|
import com.b44t.messenger.rpc.VcardContact;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
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.LocalFileContactPhoto;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.SystemContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.SystemContactPhoto;
|
||||||
|
import org.thoughtcrime.securesms.contacts.avatars.VcardContactPhoto;
|
||||||
import org.thoughtcrime.securesms.database.Address;
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
import org.thoughtcrime.securesms.util.Hash;
|
import org.thoughtcrime.securesms.util.Hash;
|
||||||
import org.thoughtcrime.securesms.util.Prefs;
|
import org.thoughtcrime.securesms.util.Prefs;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
@ -65,6 +66,7 @@ public class Recipient {
|
||||||
|
|
||||||
private final @Nullable DcChat dcChat;
|
private final @Nullable DcChat dcChat;
|
||||||
private @Nullable DcContact dcContact;
|
private @Nullable DcContact dcContact;
|
||||||
|
private final @Nullable VcardContact vContact;
|
||||||
|
|
||||||
public static @NonNull Recipient fromChat(@NonNull Context context, int dcMsgId) {
|
public static @NonNull Recipient fromChat(@NonNull Context context, int dcMsgId) {
|
||||||
DcContext dcContext = DcHelper.getContext(context);
|
DcContext dcContext = DcHelper.getContext(context);
|
||||||
|
@ -90,21 +92,26 @@ public class Recipient {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Recipient(@NonNull Context context, @NonNull DcChat dcChat) {
|
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) {
|
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) {
|
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.dcChat = dcChat;
|
||||||
this.dcContact = dcContact;
|
this.dcContact = dcContact;
|
||||||
this.profileName = profileName;
|
this.profileName = profileName;
|
||||||
|
this.vContact = vContact;
|
||||||
this.contactUri = null;
|
this.contactUri = null;
|
||||||
this.systemContactPhoto = null;
|
this.systemContactPhoto = null;
|
||||||
this.customLabel = null;
|
this.customLabel = null;
|
||||||
|
@ -141,6 +148,9 @@ public class Recipient {
|
||||||
else if(dcContact!=null) {
|
else if(dcContact!=null) {
|
||||||
return dcContact.getDisplayName();
|
return dcContact.getDisplayName();
|
||||||
}
|
}
|
||||||
|
else if(vContact!=null) {
|
||||||
|
return vContact.getDisplayName();
|
||||||
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,18 +178,6 @@ public class Recipient {
|
||||||
return dcChat!=null && dcChat.isMultiUser();
|
return dcChat!=null && dcChat.isMultiUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull List<Recipient> loadParticipants(Context context) {
|
|
||||||
List<Recipient> 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) {
|
public synchronized void addListener(RecipientModifiedListener listener) {
|
||||||
listeners.add(listener);
|
listeners.add(listener);
|
||||||
}
|
}
|
||||||
|
@ -200,15 +198,21 @@ public class Recipient {
|
||||||
else if(dcContact!=null) {
|
else if(dcContact!=null) {
|
||||||
rgb = dcContact.getColor();
|
rgb = dcContact.getColor();
|
||||||
}
|
}
|
||||||
int argb = Color.argb(0xFF, Color.red(rgb), Color.green(rgb), Color.blue(rgb));
|
else if(vContact!=null) {
|
||||||
return argb;
|
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) {
|
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();
|
String name = getName();
|
||||||
if (!TextUtils.isEmpty(profileName)) return new GeneratedContactPhoto(profileName);
|
if (!TextUtils.isEmpty(profileName)) return new GeneratedContactPhoto(profileName);
|
||||||
else if (!TextUtils.isEmpty(name)) return new GeneratedContactPhoto(name);
|
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) {
|
if (systemContactPhoto != null) {
|
||||||
return new SystemContactPhoto(address, systemContactPhoto, 0);
|
return new SystemContactPhoto(address, systemContactPhoto, 0);
|
||||||
}
|
}
|
||||||
|
@ -262,7 +270,7 @@ public class Recipient {
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || !(o instanceof Recipient)) return false;
|
if (!(o instanceof Recipient)) return false;
|
||||||
|
|
||||||
Recipient that = (Recipient) o;
|
Recipient that = (Recipient) o;
|
||||||
|
|
||||||
|
@ -290,6 +298,7 @@ public class Recipient {
|
||||||
return dcChat!=null? dcChat : new DcChat(0, 0);
|
return dcChat!=null? dcChat : new DcChat(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Recipient{" +
|
return "Recipient{" +
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.StickerSlide;
|
import org.thoughtcrime.securesms.mms.StickerSlide;
|
||||||
|
import org.thoughtcrime.securesms.mms.VcardSlide;
|
||||||
import org.thoughtcrime.securesms.mms.VideoSlide;
|
import org.thoughtcrime.securesms.mms.VideoSlide;
|
||||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||||
|
|
||||||
|
@ -48,6 +49,7 @@ public class MediaUtil {
|
||||||
public static final String VIDEO_UNSPECIFIED = "video/*";
|
public static final String VIDEO_UNSPECIFIED = "video/*";
|
||||||
public static final String OCTET = "application/octet-stream";
|
public static final String OCTET = "application/octet-stream";
|
||||||
public static final String WEBXDC = "application/webxdc+zip";
|
public static final String WEBXDC = "application/webxdc+zip";
|
||||||
|
public static final String VCARD = "text/vcard";
|
||||||
|
|
||||||
|
|
||||||
public static Slide getSlideForMsg(Context context, DcMsg dcMsg) {
|
public static Slide getSlideForMsg(Context context, DcMsg dcMsg) {
|
||||||
|
@ -63,6 +65,8 @@ public class MediaUtil {
|
||||||
} else if (dcMsg.getType() == DcMsg.DC_MSG_AUDIO
|
} else if (dcMsg.getType() == DcMsg.DC_MSG_AUDIO
|
||||||
|| dcMsg.getType() == DcMsg.DC_MSG_VOICE) {
|
|| dcMsg.getType() == DcMsg.DC_MSG_VOICE) {
|
||||||
slide = new AudioSlide(context, dcMsg);
|
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
|
} else if (dcMsg.getType() == DcMsg.DC_MSG_FILE
|
||||||
|| dcMsg.getType() == DcMsg.DC_MSG_WEBXDC) {
|
|| dcMsg.getType() == DcMsg.DC_MSG_WEBXDC) {
|
||||||
slide = new DocumentSlide(context, dcMsg);
|
slide = new DocumentSlide(context, dcMsg);
|
||||||
|
@ -301,6 +305,8 @@ public class MediaUtil {
|
||||||
return "webp";
|
return "webp";
|
||||||
case WEBXDC:
|
case WEBXDC:
|
||||||
return "xdc";
|
return "xdc";
|
||||||
|
case VCARD:
|
||||||
|
return "vcf";
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue