mirror of
https://github.com/ArcaneChat/android.git
synced 2026-07-03 14:05:24 +02:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f5e90be53 | |||
| 34fa0620ab | |||
| fe37af1a3f | |||
| 3459802d67 | |||
| b8bf847941 | |||
| 5b6024584a | |||
| 101a31628d | |||
| 2a2ce883b4 | |||
| 5a0e26bdb5 | |||
| 0c9277dfd0 | |||
| 37886cf296 | |||
| df5ba2eced | |||
| 3c2ddb96b3 | |||
| e2a825dfb2 | |||
| 8d49c7e595 | |||
| 966d5dac24 | |||
| 7f684f4d2e | |||
| 1510f7f3a8 | |||
| 6916becf7f | |||
| ad5b496f5c | |||
| e647401db4 | |||
| 3113f9c3ab | |||
| a1e47865c5 |
+2
-2
@@ -33,8 +33,8 @@ android {
|
||||
useLibrary 'org.apache.http.legacy'
|
||||
|
||||
defaultConfig {
|
||||
versionCode 30000700
|
||||
versionName "1.50.4"
|
||||
versionCode 30000701
|
||||
versionName "1.50.5"
|
||||
|
||||
applicationId "chat.delta.lite"
|
||||
multiDexEnabled true
|
||||
|
||||
@@ -17,19 +17,21 @@ ArcaneChat is a Delta Chat client and was created with a focus on usability, goo
|
||||
<b>Main differences with official Delta Chat client:</b>
|
||||
|
||||
<ul>
|
||||
<li>Basic markdown support (bold, italic, strike, etc.)</li>
|
||||
<li>Support for some markdown styles in text messages (bold, italic, strike, etc.)</li>
|
||||
<li>Support for displaying Telegram's animated stickers (.tgs files)</li>
|
||||
<li>Support for SVG images previews</li>
|
||||
<li>Multiple color themes/skins</li>
|
||||
<li>It is possible to disable accounts to completely disconnect them to save data/bandwidth</li>
|
||||
<li>It is possible to disable profiles to completely disconnect them saving data/bandwidth</li>
|
||||
<li>You can easily see the connection status of all your profiles in the profile switcher</li>
|
||||
<li>A videochat instance is set by default</li>
|
||||
<li>Replies to your messages in muted groups trigger notifications</li>
|
||||
<li>Location streaming feature enabled by default and extra option to share location for 12 hours</li>
|
||||
<li>Clicking on a message with a POI location, will open the POI on the map</li>
|
||||
<li>Last-seen status of contacts is shown in your contact list, like in WhatsApp, Telegram, etc.</li>
|
||||
<li>Videos are played in loop, useful for short GIF videos</li>
|
||||
<li>Verified icon is shown in the chat list for the "Device Messages" chat</li>
|
||||
<li>Voice messages have more aggressive compression in "worse quality" mode to save data plan</li>
|
||||
<li>Automatic download of messages limited to 640KB by default</li>
|
||||
<li>Account's display name is always shown in the app's title bar instead of the name of the app</li>
|
||||
<li>Profile's display name is always shown in the app's title bar instead of the name of the app</li>
|
||||
<li>Your avatar is visible to other users in Mailing Lists</li>
|
||||
<li>Can be selected as app to open .xdc files</li>
|
||||
<li>For mini-apps developers: there are some extra features in the WebXDC API, check https://github.com/ArcaneChat/android/#webxdc</li>
|
||||
|
||||
@@ -1658,6 +1658,21 @@ JNIEXPORT void Java_com_b44t_messenger_DcMsg_setFile(JNIEnv *env, jobject obj, j
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT void Java_com_b44t_messenger_DcMsg_forceSticker(JNIEnv *env, jobject obj)
|
||||
{
|
||||
dc_msg_force_sticker(get_dc_msg(env, obj));
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jstring Java_com_b44t_messenger_DcMsg_getPOILocation(JNIEnv *env, jobject obj)
|
||||
{
|
||||
char* temp = dc_msg_get_poi_location(get_dc_msg(env, obj));
|
||||
jstring ret = JSTRING_NEW(temp);
|
||||
dc_str_unref(temp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT void Java_com_b44t_messenger_DcMsg_setDimension(JNIEnv *env, jobject obj, int width, int height)
|
||||
{
|
||||
dc_msg_set_dimension(get_dc_msg(env, obj), width, height);
|
||||
|
||||
+1
-1
Submodule jni/deltachat-core-rust updated: abdabcee00...ba15bba35d
Binary file not shown.
@@ -160,9 +160,11 @@ public class DcMsg {
|
||||
public native void setSubject (String text);
|
||||
public native void setHtml (String text);
|
||||
public native void setFile (String file, String filemime);
|
||||
public native void forceSticker ();
|
||||
public native void setDimension (int width, int height);
|
||||
public native void setDuration (int duration);
|
||||
public native void setLocation (float latitude, float longitude);
|
||||
public native String getPOILocation ();
|
||||
public void setQuote (DcMsg quote) { setQuoteCPtr(quote.msgCPtr); }
|
||||
public native String getQuotedText ();
|
||||
public native String getError ();
|
||||
|
||||
@@ -45,6 +45,7 @@ import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@@ -53,6 +54,7 @@ import android.view.View.OnFocusChangeListener;
|
||||
import android.view.View.OnKeyListener;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
@@ -179,7 +181,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private AttachmentTypeSelector attachmentTypeSelector;
|
||||
private AttachmentManager attachmentManager;
|
||||
private AudioRecorder audioRecorder;
|
||||
private Stub<MediaKeyboard> emojiDrawerStub;
|
||||
private FrameLayout emojiPickerContainer;
|
||||
private MediaKeyboard emojiPicker;
|
||||
protected HidingLinearLayout quickAttachmentToggle;
|
||||
private InputPanel inputPanel;
|
||||
|
||||
@@ -319,10 +322,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
super.onConfigurationChanged(newConfig);
|
||||
composeText.setTransport(sendButton.getSelectedTransport());
|
||||
|
||||
if (emojiDrawerStub.resolved() && container.getCurrentInput() == emojiDrawerStub.get()) {
|
||||
if (emojiPicker != null && container.getCurrentInput() == emojiPicker) {
|
||||
container.hideAttachedInput(true);
|
||||
}
|
||||
|
||||
emojiPicker = null; // force reloading next time onEmojiToggle() is called
|
||||
initializeBackground();
|
||||
}
|
||||
|
||||
@@ -360,7 +364,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
for (int i = 0; i < uriCount; i++) {
|
||||
uriList.add(multipleUris.getItemAt(i).getUri());
|
||||
}
|
||||
askSendingFiles(uriList, () -> SendRelayedMessageUtil.sendMultipleMsgs(this, chatId, uriList, null));
|
||||
askSendingFiles(uriList, () -> {
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
SendRelayedMessageUtil.sendMultipleMsgs(this, chatId, uriList, null);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -806,7 +814,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
sendButton = ViewUtil.findById(this, R.id.send_button);
|
||||
attachButton = ViewUtil.findById(this, R.id.attach_button);
|
||||
composeText = ViewUtil.findById(this, R.id.embedded_text_editor);
|
||||
emojiDrawerStub = ViewUtil.findStubById(this, R.id.emoji_drawer_stub);
|
||||
emojiPickerContainer = ViewUtil.findById(this, R.id.emoji_picker_container);
|
||||
composePanel = ViewUtil.findById(this, R.id.bottom_panel);
|
||||
container = ViewUtil.findById(this, R.id.layout_container);
|
||||
quickAttachmentToggle = ViewUtil.findById(this, R.id.quick_attachment_toggle);
|
||||
@@ -1081,7 +1089,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
);
|
||||
});
|
||||
doSend = VideoRecoder.prepareVideo(ConversationActivity.this, dcChat.getId(), msg);
|
||||
Util.runOnMain(() -> progressDialog.dismiss());
|
||||
Util.runOnMain(() -> {
|
||||
try {
|
||||
progressDialog.dismiss();
|
||||
} catch (final IllegalArgumentException e) {
|
||||
// The activity is finishing/destroyed, do nothing.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (doSend) {
|
||||
@@ -1237,16 +1251,23 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
});
|
||||
}
|
||||
|
||||
private void reloadEmojiPicker() {
|
||||
emojiPickerContainer.removeAllViews();
|
||||
emojiPicker = (MediaKeyboard) LayoutInflater.from(this).inflate(R.layout.conversation_activity_emojidrawer_stub, emojiPickerContainer, false);
|
||||
emojiPickerContainer.addView(emojiPicker);
|
||||
inputPanel.setMediaKeyboard(emojiPicker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEmojiToggle() {
|
||||
if (!emojiDrawerStub.resolved()) {
|
||||
inputPanel.setMediaKeyboard(emojiDrawerStub.get());
|
||||
if (emojiPicker == null) {
|
||||
reloadEmojiPicker();
|
||||
}
|
||||
|
||||
if (container.getCurrentInput() == emojiDrawerStub.get()) {
|
||||
if (container.getCurrentInput() == emojiPicker) {
|
||||
container.showSoftkey(composeText);
|
||||
} else {
|
||||
container.show(composeText, emojiDrawerStub.get());
|
||||
container.show(composeText, emojiPicker);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1275,6 +1296,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
msg.setQuote(quote.get().getQuotedMsg());
|
||||
}
|
||||
msg.setFile(path, null);
|
||||
msg.forceSticker();
|
||||
dcContext.sendMsg(chatId, msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -210,7 +211,7 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
dcContext.marknoticedChat((int) chatId);
|
||||
Util.runOnBackground(() -> dcContext.marknoticedChat((int) chatId));
|
||||
if (list.getAdapter() != null) {
|
||||
list.getAdapter().notifyDataSetChanged();
|
||||
}
|
||||
@@ -811,6 +812,9 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
else if(messageRecord.getInfoType() == DcMsg.DC_INFO_WEBXDC_INFO_MESSAGE) {
|
||||
WebxdcActivity.openWebxdcActivity(getContext(), messageRecord.getParent(), messageRecord.getWebxdcHref());
|
||||
}
|
||||
else if (!TextUtils.isEmpty(messageRecord.getPOILocation()) && messageRecord.getType() == DcMsg.DC_MSG_TEXT && !messageRecord.hasHtml()) {
|
||||
WebxdcActivity.openMaps(getContext(), getListAdapter().getChat().getId(), "index.html#"+messageRecord.getPOILocation());
|
||||
}
|
||||
else {
|
||||
String self_mail = dcContext.getConfig("configured_mail_user");
|
||||
if (self_mail != null && !self_mail.isEmpty()
|
||||
|
||||
@@ -257,6 +257,10 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
if (isFinishing()) {
|
||||
Log.w(TAG, "Activity is finishing, aborting onNewIntent()");
|
||||
return;
|
||||
}
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
refresh();
|
||||
|
||||
@@ -82,8 +82,16 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
private boolean hideActionBar = false;
|
||||
|
||||
public static void openMaps(Context context, int chatId) {
|
||||
openMaps(context, chatId, "");
|
||||
}
|
||||
|
||||
public static void openMaps(Context context, int chatId, String href) {
|
||||
DcContext dcContext = DcHelper.getContext(context);
|
||||
int msgId = dcContext.initWebxdcIntegration(chatId);
|
||||
int msgId = 0;
|
||||
final int mapsVersion = 2;
|
||||
if (dcContext.getConfigInt("ui.maps_version") >= mapsVersion) {
|
||||
msgId = dcContext.initWebxdcIntegration(chatId);
|
||||
}
|
||||
if (msgId == 0) {
|
||||
try {
|
||||
InputStream inputStream = context.getResources().getAssets().open("webxdc/maps.xdc");
|
||||
@@ -98,8 +106,9 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
Toast.makeText(context, "Cannot get maps.xdc, see log for details.", Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
dcContext.setConfigInt("ui.maps_version", mapsVersion);
|
||||
}
|
||||
openWebxdcActivity(context, msgId, true, "");
|
||||
openWebxdcActivity(context, msgId, true, href);
|
||||
}
|
||||
|
||||
public static void openWebxdcActivity(Context context, DcMsg instance) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.AvatarView;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
@@ -109,6 +110,19 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientM
|
||||
this.nameView.setEnabled(true);
|
||||
this.nameView.setText(name==null? "#" : name);
|
||||
|
||||
if (contact != null) {
|
||||
if (contact.isBot()) {
|
||||
number = getContext().getString(R.string.bot);
|
||||
} else if (contact.wasSeenRecently()) {
|
||||
number = getContext().getString(R.string.online);
|
||||
} else {
|
||||
long timestamp = contact.getLastSeen();
|
||||
if (timestamp != 0) {
|
||||
number = getContext().getString(R.string.last_seen_at, DateUtils.getExtendedTimeSpanString(getContext(), timestamp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(number!=null) {
|
||||
this.numberView.setText(number == null ? "" : number);
|
||||
this.labelView.setText(label==null? "" : label);
|
||||
|
||||
@@ -21,6 +21,8 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
public final class FetchForegroundService extends Service {
|
||||
private static final String TAG = FcmReceiveService.class.getSimpleName();
|
||||
private static final Object SERVICE_LOCK = new Object();
|
||||
private static final Object STOP_NOTIFIER = new Object();
|
||||
private static volatile boolean fetchingSynchronously = false;
|
||||
private static Intent service;
|
||||
|
||||
public static void start(Context context) {
|
||||
@@ -30,15 +32,40 @@ public final class FetchForegroundService extends Service {
|
||||
}
|
||||
|
||||
GenericForegroundService.createFgNotificationChannel(context);
|
||||
synchronized (SERVICE_LOCK) {
|
||||
if (service == null) {
|
||||
service = new Intent(context, FetchForegroundService.class);
|
||||
ContextCompat.startForegroundService(context, service);
|
||||
try {
|
||||
synchronized (SERVICE_LOCK) {
|
||||
if (service == null) {
|
||||
service = new Intent(context, FetchForegroundService.class);
|
||||
ContextCompat.startForegroundService(context, service);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to start foreground service: " + e + ", fetching in background.");
|
||||
// According to the documentation https://firebase.google.com/docs/cloud-messaging/android/receive,
|
||||
// we need to handle the message within 20s, and the time window may be even shorter than 20s,
|
||||
// so, use 10s to be safe.
|
||||
fetchingSynchronously = true;
|
||||
if (ApplicationContext.dcAccounts.backgroundFetch(10)) {
|
||||
// The background fetch was successful, but we need to wait until all events were processed.
|
||||
// After all events were processed, we will get DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE,
|
||||
// and stop() will be called.
|
||||
while (fetchingSynchronously) {
|
||||
try {
|
||||
// The `wait()` needs to be enclosed in a while loop because there may be
|
||||
// "spurious wake-ups", i.e. `wait()` may return even though `notifyAll()` wasn't called.
|
||||
STOP_NOTIFIER.wait();
|
||||
} catch (InterruptedException ex) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void stop(Context context) {
|
||||
if (fetchingSynchronously) {
|
||||
fetchingSynchronously = false;
|
||||
STOP_NOTIFIER.notifyAll();
|
||||
}
|
||||
|
||||
synchronized (SERVICE_LOCK) {
|
||||
if (service != null) {
|
||||
context.stopService(service);
|
||||
|
||||
@@ -110,6 +110,7 @@ public class SendRelayedMessageUtil {
|
||||
message = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT);
|
||||
} else if ("sticker".equals(type)) {
|
||||
message = new DcMsg(dcContext, DcMsg.DC_MSG_STICKER);
|
||||
message.forceSticker();
|
||||
} else if ("image".equals(type) || MediaUtil.isImageType(mimeType)) {
|
||||
message = new DcMsg(dcContext, DcMsg.DC_MSG_IMAGE);
|
||||
} else if ("audio".equals(type) || MediaUtil.isAudioType(mimeType)) {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.thoughtcrime.securesms.components.emoji.MediaKeyboard
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/emoji_drawer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone">
|
||||
|
||||
<androidx.emoji2.emojipicker.EmojiPickerView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/emoji_picker"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?emoji_drawer_background"
|
||||
app:emojiGridColumns="12"/>
|
||||
|
||||
</org.thoughtcrime.securesms.components.emoji.MediaKeyboard>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.emoji2.emojipicker.EmojiPickerView
|
||||
android:id="@+id/emoji_picker"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:emojiGridColumns="10"/>
|
||||
</RelativeLayout>
|
||||
@@ -51,12 +51,11 @@
|
||||
|
||||
<include layout="@layout/conversation_input_panel"/>
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/emoji_drawer_stub"
|
||||
<FrameLayout
|
||||
android:id="@+id/emoji_picker_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inflatedId="@+id/emoji_drawer"
|
||||
android:layout="@layout/conversation_activity_emojidrawer_stub" />
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
</org.thoughtcrime.securesms.components.InputAwareLayout>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- WARNING: on modifications also update the layout-land folder. -->
|
||||
<org.thoughtcrime.securesms.components.emoji.MediaKeyboard
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/emoji_drawer"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- WARNING: on modifications also update the layout-land folder. -->
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
|
||||
@@ -182,7 +182,7 @@
|
||||
<string name="video">Video</string>
|
||||
<string name="documents">Documentos</string>
|
||||
<string name="contact">Contacto</string>
|
||||
<string name="bot">Bot</string>
|
||||
<string name="bot">bot</string>
|
||||
<string name="camera">Cámara</string>
|
||||
<!-- As in "start a video recording" or "take a photo"; eg. the description of the "shutter button" in cameras -->
|
||||
<string name="capture">Capturar</string>
|
||||
|
||||
@@ -174,7 +174,7 @@
|
||||
<string name="video">Video</string>
|
||||
<string name="documents">Documents</string>
|
||||
<string name="contact">Contact</string>
|
||||
<string name="bot">Bot</string>
|
||||
<string name="bot">bot</string>
|
||||
<string name="camera">Camera</string>
|
||||
<!-- As in "start a video recording" or "take a photo"; eg. the description of the "shutter button" in cameras -->
|
||||
<string name="capture">Capture</string>
|
||||
|
||||
Reference in New Issue
Block a user