Compare commits

..

28 Commits

Author SHA1 Message Date
adbenitez b9649a908e Merge remote-tracking branch 'upstream/main' 2026-05-30 17:18:28 +02:00
adb 2f20f558ad Merge pull request #4453 from deltachat/prep-2.51.0
prepare 2.51.0
2026-05-30 03:06:36 +02:00
adbenitez 6f868f653a prepare 2.51.0 2026-05-30 01:01:01 +02:00
adb 42b529dd4a Merge pull request #4452 from deltachat/update-core-and-stuff-30-05-2026
Update core to 2.51.0
2026-05-30 00:31:37 +02:00
adbenitez d7eab73687 update translations 2026-05-30 00:17:36 +02:00
adbenitez 1deebdf552 update deltachat-core-rust to 'chore(release): prepare for 2.51.0' of 'v2.51.0' 2026-05-30 00:14:45 +02:00
wchen342 20fbb0e1ee Reset scroll position after switching account (#4451) 2026-05-29 16:25:16 +02:00
adb 6a53e229d1 Merge pull request #4450 from deltachat/adb/fix-notifyCalls-toggle
fix notifyCalls toggle state setting
2026-05-29 15:11:49 +02:00
adbenitez 74ef2d478a fix notifyCalls toggle state setting 2026-05-29 15:07:05 +02:00
adbenitez feeece405d fix WebxdcActivity 2026-05-29 02:15:06 +02:00
adbenitez 20459a5710 Merge remote-tracking branch 'upstream/main' 2026-05-28 23:42:34 +02:00
adb a3a8b3581f Merge pull request #4449 from deltachat/adb/issue-4445
fix sharing contact across profiles
2026-05-28 23:35:38 +02:00
adbenitez f2571dba91 fix sharing contact across profiles 2026-05-28 20:04:27 +02:00
adb 708ffe617c Merge pull request #4448 from deltachat/adb/issue-4447
respect IMAP Folder field if set
2026-05-28 19:39:47 +02:00
adbenitez f6ecb94047 respect IMAP Folder field if set 2026-05-28 19:30:07 +02:00
link2xt 2d4b52b037 fixup 2026-05-27 16:35:13 +00:00
link2xt 5257f39aee Fix debouncing in chatlist and search
Reset inLoadChatlist only right before exit
in loadChatlistAsync().
Otherwise if loadChatlistAsync() is called
while the loop is running loadChatlist() or Util.sleep(100),
loadChatlistAsync() will see inLoadChatlist=false,
set needsAnotherLoad=true and start a second loop.
This way it is possible to spawn any number of background loops
running loadChatlist() simultaneously
so no debouncing actually happened.

Debouncing in SearchViewModel.updateQuery()
is fixed similarly by copying the code structure from loadChatlistAsync().
Previously it did not even have the lock.
2026-05-27 16:35:13 +00:00
adb 20f1475856 Merge pull request #4366 from deltachat/adb/add-isbroadcast-isappsender
add new webxdc.isAppSender and webxdc.isBroadcast APIs
2026-05-26 20:58:54 +02:00
adb db0295e5a6 Merge branch 'main' into adb/add-isbroadcast-isappsender 2026-05-26 20:55:38 +02:00
wchen342 d019c1bfb5 Use new RPC for updating and stopping location streaming (#4400) 2026-05-26 18:02:13 +02:00
adbenitez 4542ba1b9a update changelog 2026-05-26 17:29:06 +02:00
adb 9b982db899 Merge branch 'main' into adb/add-isbroadcast-isappsender 2026-05-26 17:12:03 +02:00
adbenitez a1a72b79b3 use Log.e instead of Log.i 2026-05-26 17:11:19 +02:00
adbenitez 319e2ebe5a use WebxdcMessageInfo everywhere 2026-05-25 18:15:32 +02:00
adbenitez 49a00f3788 apply spotless 2026-05-25 17:37:13 +02:00
adb 74ee2fb8db Merge branch 'main' into adb/add-isbroadcast-isappsender 2026-05-25 16:53:18 +02:00
adb 655ed8cad4 Merge branch 'main' into adb/add-isbroadcast-isappsender 2026-04-29 17:19:36 +02:00
adbenitez 1d94fe8bca add new webxdc.isAppSender and webxdc.isBroadcast APIs 2026-04-18 22:36:43 +02:00
25 changed files with 367 additions and 283 deletions
+7 -2
View File
@@ -1,6 +1,6 @@
# Delta Chat Android Changelog
## v2.50.0
## 2.51.0
2026-05
* Better incoming call system integration
@@ -34,11 +34,16 @@
* Resend the last 10 messages to new broadcast channel member
* Enable PQC (Post-Quantum Cryptography) support. We do not generate PQC keys yet, this step is needed for forward compatibility
* Improve avatar quality
* Add new webxdc.isAppSender and webxdc.isBroadcast APIs for mini-apps
* Fix: avoid invalid empty "~" notifications when some peer is streaming location
* Fix: Improve detection of stickers
* Fix text direction issues for RTL languages in "Show full message" view
* Fix: Reconnect when removing a relay
* Update to core 2.50.0
* Fix: Location streaming now works correctly with multiple accounts
* Fix debouncing in chatlist and search
* Fix sharing contact across profiles
* Fix: Reset scroll location after switching account
* Update to core 2.51.0
## v2.49.0
2026-04
+2 -2
View File
@@ -34,8 +34,8 @@ android {
useLibrary 'org.apache.http.legacy'
defaultConfig {
versionCode 30000743
versionName "2.50.0"
versionCode 30000744
versionName "2.51.0"
applicationId "chat.delta.lite"
multiDexEnabled true
+95 -95
View File
@@ -29,19 +29,19 @@
<li><a href="#сколько-участников-может-быть-в-одной-группе">Сколько участников может быть в одной группе?</a></li>
</ul>
</li>
<li><a href="#channels">Channels</a>
<li><a href="#channels">Каналы</a>
<ul>
<li><a href="#subscribe-to-a-channel">Subscribe to a channel</a></li>
<li><a href="#create-a-channel">Create a channel</a></li>
<li><a href="#how-many-subscribers-can-a-channel-have">How many subscribers can a channel have?</a></li>
<li><a href="#подписка-на-канал">Подписка на канал</a></li>
<li><a href="#создание-канала">Создание канала</a></li>
<li><a href="#какое-максимальное-количество-подписчиков-может-быть-у-канала">Какое максимальное количество подписчиков может быть у канала?</a></li>
</ul>
</li>
<li><a href="#calls">Calls</a>
<li><a href="#calls">Звонки</a>
<ul>
<li><a href="#place-a-call">Place a call</a></li>
<li><a href="#accept-or-reject-a-call">Accept or reject a call</a></li>
<li><a href="#during-a-call">During a call</a></li>
<li><a href="#missed-calls-and-notifications">Missed calls and notifications</a></li>
<li><a href="#как-сделать-звонок">Как сделать звонок</a></li>
<li><a href="#принять-или-отклонить-вызов">Принять или отклонить вызов</a></li>
<li><a href="#во-время-звонка">Во время звонка</a></li>
<li><a href="#пропущенные-вызовы-и-уведомления">Пропущенные вызовы и уведомления</a></li>
</ul>
</li>
<li><a href="#webxdc">Встроенные приложения чата</a>
@@ -640,191 +640,191 @@
<h2 id="channels">
Channels <a href="#channels" class="anchor"></a>
Каналы <a href="#channels" class="anchor"></a>
</h2>
<p>Channels are a one-to-many tool for broadcasting messages.</p>
<p>Каналы представляют собой инструмент типа “один-ко-многим” для трансляции сообщений.</p>
<h3 id="subscribe-to-a-channel">
<h3 id="подписка-на-канал">
Subscribe to a channel <a href="#subscribe-to-a-channel" class="anchor"></a>
Подписка на канал <a href="#подписка-на-канал" class="anchor"></a>
</h3>
<ul>
<li>Scan the <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> <strong>QR code</strong>
or tap the <strong>invite link</strong> you got from the channel owner.</li>
<li>Отсканируйте <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> <strong>QR-код</strong>
или нажмите на ссылку-приглашение, которую вы получили от владельца канала.</li>
</ul>
<p>Thats all!
You will receive a few of the messages from the channel history
and, from that point on, all new messages from the channel.</p>
<p>Всё готово!
Сначала вы получите несколько сообщений из истории канала,
а затем — все новые сообщения, поступающие в него.</p>
<p><strong>Dont worry,</strong> if that does not happen immediately.
Once the channel owner comes online, your join request will be processed.</p>
<p><strong>Не беспокойтесь,</strong> если это произойдет не сразу.
Как только владелец канала выйдет в сеть, ваш запрос на вступление будет обработан.</p>
<p>As all of Delta Chat, also Channels are private and decentralized,
there is no public discovery.</p>
<p>Как и весь Delta Chat, каналы являются приватными и децентрализованными,
поэтому возможность публичного поиска каналов отсутствует.</p>
<p>Other channel subscribers will not see that you subscribed and cannot message you.
The channel owner, however, can message you.
They will also see that you read a message unless you have read receipts disabled.</p>
<p>Другие подписчики канала не увидят факта вашей подписки и не смогут отправлять вам сообщения.
Однако, владелец канала сможет отправить вам сообщение.
Также он будет видеть, что вы прочитали сообщение, если только вы не отключите подтверждение о прочтении.</p>
<p>If you do not want to share your main profile,
you can also create a <a href="#multiple-accounts">dedicated profile</a> for joining a channel.</p>
<p>Если вы не хотите использовать свой основной профиль,
вы можете создать <a href="#multiple-accounts">специальный профиль</a> для подписки на канал.</p>
<h3 id="create-a-channel">
<h3 id="создание-канала">
Create a channel <a href="#create-a-channel" class="anchor"></a>
Создание канала <a href="#создание-канала" class="anchor"></a>
</h3>
<ul>
<li>
<p>Tap <strong>New Chat</strong> and choose <strong>New Channel</strong>.</p>
<p>Нажмите <strong>Новый чат</strong> и выберите <strong>Новый канал</strong>.</p>
</li>
<li>
<p>Enter a <strong>name</strong>, optionally set an <strong>image</strong> and <strong>description</strong>, and hit the <strong>Create</strong> button.</p>
<p>Введите <strong>название</strong>, по желанию добавьте <strong>изображение</strong> и <strong>описание</strong>, а затем нажмите кнопку <strong>Создать</strong>.</p>
</li>
<li>
<p>You can now send and manage messages as usual.</p>
<p>Теперь вы можете отправлять сообщения и управлять ими в обычном режиме.</p>
</li>
<li>
<p>From the channels profile, <strong>share the QR code or invite link with others</strong>.</p>
<p>В профиле канала вы можете <strong>поделиться QR-кодом или ссылкой-приглашением с другими пользователями</strong>.</p>
</li>
</ul>
<p>Subscribers will receive your messages,
but they cannot send messages in your channel.
When subscribing, they will receive <strong>a few of the latest messages of the channel history</strong>.</p>
<p>Подписчики будут получать ваши сообщения,
но не смогут отправлять сообщения в вашем канале.
При подписке они получат <strong>несколько последних сообщений из истории канала</strong>.</p>
<p>You can see the <strong>view count</strong> beside each message.
Note that this only counts subscribers who have read receipts enabled,
so the real view count may be larger.</p>
<p>Рядом с каждым сообщением вы можете увидеть <strong>количество просмотров</strong>.
Обратите внимание, что учитываются только те подписчики, у которых включены уведомления о прочтении,
поэтому реальное количество просмотров может быть больше.</p>
<h3 id="how-many-subscribers-can-a-channel-have">
<h3 id="какое-максимальное-количество-подписчиков-может-быть-у-канала">
How many subscribers can a channel have? <a href="#how-many-subscribers-can-a-channel-have" class="anchor"></a>
Какое максимальное количество подписчиков может быть у канала? <a href="#какое-максимальное-количество-подписчиков-может-быть-у-канала" class="anchor"></a>
</h3>
<p>Channels are designed for much larger audiences than <a href="#groups">groups</a>.</p>
<p>Каналы предназначены для гораздо более широкой аудитории, чем <a href="#groups">группы</a>.</p>
<p>The practical limit depends on the used <a href="#relays">relay</a>,
so there is no single fixed number that applies everywhere.</p>
<p>Практический предел зависит от используемого <a href="#relays">релея</a>,
поэтому не существует единого фиксированного значения, применимого во всех случаях.</p>
<p>For really large channels with several tens of thousands of subscribers,
we recommend using a <a href="#multiple-accounts">dedicated profile</a> for the channel
and checking whether the relay is suitable.</p>
<p>Для крайне крупных каналов с десятками тысяч подписчиков,
мы рекомендуем использовать <a href="#multiple-accounts">специальный профиль</a> для управления каналом,
а также предварительно проверить пригодность релея.</p>
<p>But dont be too hesitant: Delta Chat is designed to be relay-agnostic,
so you can change your relay at any point easily -
your existing subscribers will not even notice.
You only have to update the invite link you share with new subscribers in that case.</p>
<p>Не стоит опасаться: архитектура Delta Chat позволяет использовать любой релей (relay-agnostic),
поэтому вы можете легко изменить его в любой момент -
ваши текущие подписчики этого даже не заметят.
В этом случае достаточно будет обновить ссылку-приглашение, которую вы передаёте новым пользователям.</p>
<h2 id="calls">
Calls <a href="#calls" class="anchor"></a>
Звонки <a href="#calls" class="anchor"></a>
</h2>
<p>Delta Chat supports one-to-one <strong>audio calls</strong> and <strong>video calls</strong>.</p>
<p>Delta Chat поддерживает <strong>аудио-</strong> и <strong>видеозвонки</strong> в режиме “один-на-один”.</p>
<p>Calls are supported on Desktop, Ubuntu Touch, iOS and Android 8 and newer.</p>
<p>Звонки работают на ПК, Ubuntu Touch, iOS и Android версии 8 и новее.</p>
<h3 id="place-a-call">
<h3 id="как-сделать-звонок">
Place a call <a href="#place-a-call" class="anchor"></a>
Как сделать звонок <a href="#как-сделать-звонок" class="anchor"></a>
</h3>
<ul>
<li>
<p>In a one-to-one chat, tap the 📞 <strong>call icon</strong>.</p>
<p>В чате “один-на-один” нажмите на 📞 <strong>значок вызова</strong>.</p>
</li>
<li>
<p>This opens a small menu
where you can choose whether to place an <strong>Audio Call</strong> or a <strong>Video Call</strong>.</p>
<p>Откроется небольшое меню
в котором вы сможете выбрать вид связи <strong>аудио-</strong> или <strong>видеозвонок</strong>.</p>
</li>
</ul>
<h3 id="accept-or-reject-a-call">
<h3 id="принять-или-отклонить-вызов">
Accept or reject a call <a href="#accept-or-reject-a-call" class="anchor"></a>
Принять или отклонить вызов <a href="#принять-или-отклонить-вызов" class="anchor"></a>
</h3>
<ul>
<li>
<p>When someone calls you,
Delta Chat shows an <strong>incoming call screen</strong> or notification.</p>
<p>При входящем звонке,
Delta Chat показывает <strong>экран входящего вызова</strong> или уведомление.</p>
</li>
<li>
<p>Tap <strong>Accept</strong> to answer
or <strong>Decline</strong> to reject the call.</p>
<p>Нажмите <strong>Принять</strong> чтобы ответить
или <strong>Отклонить</strong> чтобы сбросить звонок.</p>
</li>
</ul>
<h3 id="during-a-call">
<h3 id="во-время-звонка">
During a call <a href="#during-a-call" class="anchor"></a>
Во время звонка <a href="#во-время-звонка" class="anchor"></a>
</h3>
<ul>
<li>
<p>You can <strong>mute</strong> your microphone.</p>
<p>Вы можете <strong>отключить</strong> звук микрофона.</p>
</li>
<li>
<p>You can <strong>enable or disable your camera</strong>.</p>
<p>Вы можете <strong>включить или выключить камеру</strong>.</p>
</li>
<li>
<p>On mobile, you can <strong>switch between front and back cameras</strong>.</p>
<p>На мобильных устройствах можно <strong>переключаться между фронтальной и основной камерами</strong>.</p>
</li>
</ul>
<p>Depending on the device, you can also select the audio output or use picture-in-picture.
On desktop, the call is using a dedicated window
and you can continue using the main Delta Chat window as usual.</p>
<p>В зависимости от устройства вы можете выбрать источник аудиовыхода или использовать режим “картинка в картинке”.
В приложении для ПК звонок осуществляется в отдельном окне,
что позволяет продолжать работу в основном окне Delta Chat в обычном режиме.</p>
<h3 id="missed-calls-and-notifications">
<h3 id="пропущенные-вызовы-и-уведомления">
Missed calls and notifications <a href="#missed-calls-and-notifications" class="anchor"></a>
Пропущенные вызовы и уведомления <a href="#пропущенные-вызовы-и-уведомления" class="anchor"></a>
</h3>
<ul>
<li>
<p>If you do not answer, do not hear the ringing, or do not have your device at hand,
the call appears as a <strong>missed call</strong>.</p>
<p>Если вы не ответите на звонок, не услышите сигнал или ваше устройство будет недоступно,
вызов отобразится как <strong>пропущенный</strong>.</p>
</li>
<li>
<p><strong>Only your accepted contacts</strong> can make your device ring.
Contact requests will appear as usual and will not ring.</p>
<p><strong>Только ваши подтвержденные контакты</strong> могут заставить ваше устройство звонить.
Запросы на добавление в контакты будут приходить как обычно, но вызова не будет.</p>
</li>
<li>
<p>At <strong>Settings → Notifications → Calls</strong>,
you can disable the special call ringing screen completely.
If you do so, you will not be disturbed by any ringing notification,
you can still pick up the call by tapping the incoming call message bubble in its chat.</p>
<p>В разделе <strong>Настройки → Уведомления → Звонки</strong>,
вы можете полностью отключить специальный экран входящего вызова.
Если вы это сделаете, никакие уведомления о звонках не будут вас беспокоить;
при этом вы всё равно сможете принять звонок, нажав на иконку сообщения о входящем звонке в соответствующем чате.</p>
</li>
</ul>
@@ -1624,20 +1624,20 @@ Delta Chat вместо этого использует реализацию Ope
</h3>
<p>The used <a href="#relays">relays</a> need to know your IP Address,
as well as sometimes your contacts devices if you have a <a href="#calls">call</a>
or use <a href="#webxdc">apps</a> together.</p>
<p>Используемым <a href="#relays">релеям</a> необходимо знать ваш IP-адрес,
а в некоторых случаях — данные устройств ваших контактов, если вы совершаете <a href="#calls">вызов</a>
или совместно используете <a href="#webxdc">приложения</a>.</p>
<p>IP Addresses are needed for connectivity and efficiency.
Delta Chat neither persists nor exposes them.
Note that IP Addresses
are not like an address you give to a delivery service,
but typically less precise, often defining city or region only.</p>
<p>IP-адреса необходимы для обеспечения связи и эффективной работы.
Delta Chat не сохраняет их и не раскрывает третьим лицам.
Обратите внимание, что IP-адрес
— это не тот же адрес, который вы указываете службе доставки,
он, как правило, менее точен и зачастую позволяет определить лишь город или регион.</p>
<p>If you see your IP Address as a risk,
we recommend to use a VPN for the whole system.
Per-app options leave gaps across your system.
For example, tapping a link can expose IP Addresses to unknown parties, which is by far the larger risk.</p>
<p>Если вы считаете свой IP-адрес зоной риска,
мы рекомендуем использовать VPN для всей системы.
Настройка VPN для отдельных приложений оставляет уязвимости в общей защите устройства.
Например, нажатие на ссылку может раскрыть ваш IP-адрес неизвестным сторонам, что представляет собой гораздо больший риск.</p>
<h3 id="sealedsender">
@@ -2,6 +2,8 @@
package chat.delta.rpc.types;
public class WebxdcMessageInfo {
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
public String orientation;
/** if the Webxdc represents a document, then this is the name of the document */
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
public String document;
@@ -41,7 +41,6 @@ import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.connect.DirectShareUtil;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.SendRelayedMessageUtil;
import org.thoughtcrime.securesms.util.ShareUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.task.SnackbarAsyncTask;
import org.thoughtcrime.securesms.util.views.ProgressDialog;
@@ -488,10 +487,6 @@ public abstract class BaseConversationListFragment extends Fragment implements A
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
if (isRelayingMessageContent(getActivity())) {
if (ShareUtil.getSharedContactId(getActivity()) != 0) {
return false; // no sharing of a contact to multiple recipients at the same time, we can
// reconsider when that becomes a real-world need
}
Context context = getContext();
if (context != null) {
fab.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_send_sms_white_24dp));
@@ -879,13 +879,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void handleSharing() {
ArrayList<Uri> uriList = ShareUtil.getSharedUris(this);
int sharedContactId = ShareUtil.getSharedContactId(this);
if (uriList.size() > 1) {
askSendingFiles(uriList, () -> SendRelayedMessageUtil.immediatelyRelay(this, chatId));
} else {
if (sharedContactId != 0) {
addAttachmentContactInfo(sharedContactId);
} else if (ShareUtil.getSharedHtml(this) != null
if (ShareUtil.getSharedHtml(this) != null
|| ShareUtil.getSharedSubject(this) != null
|| ("sticker".equals(ShareUtil.getSharedType(this)) && !uriList.isEmpty())) {
SendRelayedMessageUtil.immediatelyRelay(this, chatId);
@@ -727,6 +727,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
refreshAvatar();
refreshUnreadIndicator();
refreshTitle();
conversationListFragment.resetScrollPosition();
conversationListFragment.loadChatlistAsync();
}
@@ -64,6 +64,7 @@ public class ConversationListFragment extends BaseConversationListFragment
private Timer reloadTimer;
private boolean chatlistJustLoaded;
private boolean reloadTimerInstantly;
private boolean resetScrollPosition;
@Override
public void onCreate(Bundle icicle) {
@@ -240,9 +241,9 @@ public class ConversationListFragment extends BaseConversationListFragment
public void loadChatlistAsync() {
synchronized (loadChatlistLock) {
needsAnotherLoad = true;
if (inLoadChatlist) {
Log.i(TAG, "chatlist loading debounced");
needsAnotherLoad = true;
return;
}
inLoadChatlist = true;
@@ -251,6 +252,9 @@ public class ConversationListFragment extends BaseConversationListFragment
Util.runOnAnyBackgroundThread(
() -> {
while (true) {
Log.i(TAG, "executing debounced chatlist loading");
loadChatlist();
synchronized (loadChatlistLock) {
if (!needsAnotherLoad) {
inLoadChatlist = false;
@@ -259,8 +263,6 @@ public class ConversationListFragment extends BaseConversationListFragment
needsAnotherLoad = false;
}
Log.i(TAG, "executing debounced chatlist loading");
loadChatlist();
Util.sleep(100);
}
});
@@ -302,6 +304,11 @@ public class ConversationListFragment extends BaseConversationListFragment
}
((ConversationListAdapter) list.getAdapter()).changeData(chatlist);
if (resetScrollPosition) {
list.scrollToPosition(0);
resetScrollPosition = false;
}
});
}
@@ -358,4 +365,8 @@ public class ConversationListFragment extends BaseConversationListFragment
loadChatlistAsync();
}
}
public void resetScrollPosition() {
resetScrollPosition = true;
}
}
@@ -7,6 +7,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
@@ -25,8 +26,11 @@ import com.b44t.messenger.DcContact;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.ShareUtil;
@@ -36,6 +40,8 @@ import org.thoughtcrime.securesms.util.ViewUtil;
public class ProfileActivity extends PassphraseRequiredActionBarActivity
implements DcEventCenter.DcEventDelegate {
private static final String TAG = "ProfileActivity";
public static final String CHAT_ID_EXTRA = "chat_id";
public static final String CONTACT_ID_EXTRA = "contact_id";
@@ -398,7 +404,18 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
Intent composeIntent = new Intent();
DcContact dcContact = dcContext.getContact(contactId);
if (dcContact.isKeyContact()) {
ShareUtil.setSharedContactId(composeIntent, contactId);
try {
byte[] vcard =
rpc.makeVcard(rpc.getSelectedAccountId(), Collections.singletonList(contactId))
.getBytes();
Uri vcardUri =
PersistentBlobProvider.getInstance().create(this, vcard, "text/vcard", "contact.vcf");
ArrayList<Uri> uris = new ArrayList<>();
uris.add(vcardUri);
ShareUtil.setSharedUris(composeIntent, uris);
} catch (RpcException e) {
Log.e(TAG, "Failed to create vCard for sharing contactId=" + contactId, e);
}
} else {
ShareUtil.setSharedText(composeIntent, dcContact.getAddr());
}
@@ -34,6 +34,7 @@ import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
import chat.delta.rpc.Rpc;
import chat.delta.rpc.RpcException;
import chat.delta.rpc.types.WebxdcMessageInfo;
import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
@@ -74,6 +75,8 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
private String baseURL;
private String sourceCodeUrl = "";
private String selfAddr;
private boolean isAppSender;
private boolean isBroadcast;
private int sendUpdateMaxSize;
private int sendUpdateInterval;
private boolean internetAccess = false;
@@ -220,21 +223,32 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
// (a random-id would also work, but would need maintenance and does not add benefits as we
// regard the file-part interceptRequest() only,
// also a random-id is not that useful for debugging)
this.baseURL = "https://acc" + dcContext.getAccountId() + "-msg" + appMessageId + ".localhost";
this.baseURL = "https://acc" + accountId + "-msg" + appMessageId + ".localhost";
final JSONObject info = this.dcAppMsg.getWebxdcInfo();
internetAccess = JsonUtils.optBoolean(info, "internet_access");
if ("landscape".equals(JsonUtils.optString(info, "orientation"))) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else {
// enter fullscreen mode if necessary,
// this is needed here because if the app is opened while already in landscape mode,
// onConfigurationChanged() is not triggered
setScreenMode(getResources().getConfiguration());
WebxdcMessageInfo info;
try {
info = rpc.getWebxdcInfo(accountId, appMessageId);
if ("landscape".equals(info.orientation)) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else {
// enter fullscreen mode if necessary,
// this is needed here because if the app is opened while already in landscape mode,
// onConfigurationChanged() is not triggered
setScreenMode(getResources().getConfiguration());
}
internetAccess = info.internetAccess;
selfAddr = info.selfAddr;
isAppSender = info.isAppSender;
isBroadcast = info.isBroadcast;
sendUpdateMaxSize = info.sendUpdateMaxSize;
sendUpdateInterval = info.sendUpdateInterval;
} catch (RpcException e) { // unexpected error, log it and finish
Log.e(TAG, "RPC Error", e);
finish();
return;
}
selfAddr = info.optString("self_addr");
sendUpdateMaxSize = info.optInt("send_update_max_size");
sendUpdateInterval = info.optInt("send_update_interval");
toggleFakeProxy(!internetAccess);
@@ -487,22 +501,22 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
this.dcContext.getMsg(event.getData2Int()); // msg changed, reload data from db
Util.runOnAnyBackgroundThread(
() -> {
final JSONObject info = dcAppMsg.getWebxdcInfo();
final DcChat chat = dcContext.getChat(dcAppMsg.getChatId());
Util.runOnMain(
() -> {
updateTitleAndMenu(info, chat);
});
try {
final WebxdcMessageInfo info =
rpc.getWebxdcInfo(dcContext.getAccountId(), dcAppMsg.getId());
final DcChat chat = dcContext.getChat(dcAppMsg.getChatId());
Util.runOnMain(() -> updateTitleAndMenu(info, chat));
} catch (RpcException e) {
Log.e(TAG, "RPC Error", e);
}
});
}
}
private void updateTitleAndMenu(JSONObject info, DcChat chat) {
final String docName = JsonUtils.optString(info, "document");
final String xdcName = JsonUtils.optString(info, "name");
final String currSourceCodeUrl = JsonUtils.optString(info, "source_code_url");
getSupportActionBar()
.setTitle((docName.isEmpty() ? xdcName : docName) + " " + chat.getName());
private void updateTitleAndMenu(WebxdcMessageInfo info, DcChat chat) {
final String docName = TextUtils.isEmpty(info.document) ? info.name : info.document;
getSupportActionBar().setTitle(docName + " " + chat.getName());
String currSourceCodeUrl = info.sourceCodeUrl != null ? info.sourceCodeUrl : "";
if (!sourceCodeUrl.equals(currSourceCodeUrl)) {
sourceCodeUrl = currSourceCodeUrl;
invalidateOptionsMenu();
@@ -522,19 +536,20 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
Context context = activity.getApplicationContext();
try {
DcContext dcContext = DcHelper.getContext(context);
Rpc rpc = DcHelper.getRpc(context);
int accountId = dcContext.getAccountId();
DcMsg msg = dcContext.getMsg(msgId);
final JSONObject info = msg.getWebxdcInfo();
WebxdcMessageInfo info = rpc.getWebxdcInfo(accountId, msgId);
final String docName = JsonUtils.optString(info, "document");
final String xdcName = JsonUtils.optString(info, "name");
byte[] blob = msg.getWebxdcBlob(JsonUtils.optString(info, "icon"));
final String docName = TextUtils.isEmpty(info.document) ? info.name : info.document;
byte[] blob = msg.getWebxdcBlob(info.icon);
ByteArrayInputStream is = new ByteArrayInputStream(blob);
BitmapDrawable drawable = (BitmapDrawable) Drawable.createFromStream(is, "icon");
Bitmap bitmap = drawable.getBitmap();
ShortcutInfoCompat shortcutInfoCompat =
new ShortcutInfoCompat.Builder(context, "xdc-" + dcContext.getAccountId() + "-" + msgId)
.setShortLabel(docName.isEmpty() ? xdcName : docName)
new ShortcutInfoCompat.Builder(context, "xdc-" + accountId + "-" + msgId)
.setShortLabel(docName)
.setIcon(
IconCompat.createWithBitmap(
bitmap)) // createWithAdaptiveBitmap() removes decorations but cuts out a too
@@ -631,6 +646,16 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
return WebxdcActivity.this.dcContext.getName();
}
@JavascriptInterface
public boolean isAppSender() {
return WebxdcActivity.this.isAppSender;
}
@JavascriptInterface
public boolean isBroadcast() {
return WebxdcActivity.this.isBroadcast;
}
/**
* @noinspection unused
*/
@@ -1,53 +0,0 @@
package org.thoughtcrime.securesms.geolocation;
import android.content.Context;
import android.content.SharedPreferences;
import java.util.HashSet;
import java.util.Set;
public final class ActiveLocationChats {
private static final String PREFS_NAME = "location_streaming";
private static final String KEY_ACTIVE = "active_chat_ids";
private ActiveLocationChats() {}
private static SharedPreferences prefs(Context context) {
return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
}
/**
* Add a chat. Uses commit() to guarantee the write reaches disk before the process can die to
* preserve the superset invariant.
*/
static void add(Context context, int chatId) {
Set<String> current = new HashSet<>(getAll(context));
current.add(String.valueOf(chatId));
prefs(context).edit().putStringSet(KEY_ACTIVE, current).commit();
}
public static void remove(Context context, int chatId) {
Set<String> current = new HashSet<>(getAll(context));
current.remove(String.valueOf(chatId));
prefs(context).edit().putStringSet(KEY_ACTIVE, current).apply();
}
static void clear(Context context) {
prefs(context).edit().remove(KEY_ACTIVE).apply();
}
static Set<Integer> getAllIds(Context context) {
Set<Integer> ids = new HashSet<>();
for (String s : getAll(context)) {
try {
ids.add(Integer.parseInt(s));
} catch (NumberFormatException ignored) {
}
}
return ids;
}
private static Set<String> getAll(Context context) {
return prefs(context).getStringSet(KEY_ACTIVE, new HashSet<>());
}
}
@@ -17,13 +17,15 @@ import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.app.ServiceCompat;
import androidx.core.content.ContextCompat;
import chat.delta.rpc.Rpc;
import chat.delta.rpc.RpcException;
import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.connect.DcHelper;
public class LocationStreamingService extends Service {
private static final String TAG = "LocationStreamingService";
private static final String TAG = "LocationStreamingSvc";
private static final String ACTION_STOP = "org.thoughtcrime.securesms.geolocation.STOP_STREAMING";
private static final int NOTIFICATION_ID = 8801;
private static final String CHANNEL_ID = "location_streaming";
@@ -36,38 +38,63 @@ public class LocationStreamingService extends Service {
// static API
/** Register a chat for location updates, then ensure the service is running. */
public static void startSharing(Context context, int chatId, int durationSeconds) {
ActiveLocationChats.add(context, chatId);
DcHelper.getContext(context).sendLocationsToChat(chatId, durationSeconds);
ContextCompat.startForegroundService(
context, new Intent(context, LocationStreamingService.class));
}
/** Unregister a chat. If no chats remain, stop the service. */
public static void stopSharing(Context context, int chatId) {
ActiveLocationChats.remove(context, chatId);
DcHelper.getContext(context).sendLocationsToChat(chatId, 0);
if (!DcHelper.getContext(context).isSendingLocationsToChat(0)) {
context.stopService(new Intent(context, LocationStreamingService.class));
}
}
public static void ensureRunning(Context context) {
if (!hasLocationPermission(context)) {
for (int chatId : ActiveLocationChats.getAllIds(context)) {
DcHelper.getContext(context).sendLocationsToChat(chatId, 0);
}
ActiveLocationChats.clear(context);
public static void startSharing(Context context, int accountId, int chatId, int durationSeconds) {
try {
DcHelper.getRpc(context).sendLocationsToChat(accountId, chatId, durationSeconds);
} catch (RpcException e) {
Log.e(TAG, "Failed to start location streaming", e);
return;
}
ContextCompat.startForegroundService(
context, new Intent(context, LocationStreamingService.class));
}
/** Unregister a chat. If no chats remain, stop the service. */
public static void stopSharing(Context context, int accountId, int chatId) {
try {
DcHelper.getRpc(context).sendLocationsToChat(accountId, chatId, 0);
} catch (RpcException e) {
Log.e(TAG, "Failed to stop location streaming", e);
}
if (!isAnySharingActive(context)) {
context.stopService(new Intent(context, LocationStreamingService.class));
}
}
public static void ensureRunning(Context context) {
if (!hasLocationPermission(context)) {
try {
DcHelper.getRpc(context).stopSendingLocations();
} catch (RpcException e) {
Log.e(TAG, "Failed to stop location streaming", e);
}
return;
}
if (isAnySharingActive(context)) {
ContextCompat.startForegroundService(
context, new Intent(context, LocationStreamingService.class));
}
}
public static boolean isRunning() {
return running;
}
private static boolean isAnySharingActive(Context context) {
try {
Rpc rpc = DcHelper.getRpc(context);
for (int accountId : rpc.getAllAccountIds()) {
if (rpc.isSendingLocations(accountId)) {
return true;
}
}
} catch (RpcException e) {
Log.e(TAG, "Failed to check location streaming state", e);
return true;
}
return false;
}
// lifecycle
@Override
@@ -86,7 +113,7 @@ public class LocationStreamingService extends Service {
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
if (intent != null && ACTION_STOP.equals(intent.getAction())) {
stopAllSharing();
stopAllSharing(this);
stopSelf();
return START_NOT_STICKY;
}
@@ -100,11 +127,12 @@ public class LocationStreamingService extends Service {
return START_STICKY;
}
private void stopAllSharing() {
for (int chatId : ActiveLocationChats.getAllIds(this)) {
DcHelper.getContext(this).sendLocationsToChat(chatId, 0);
private static void stopAllSharing(Context context) {
try {
DcHelper.getRpc(context).stopSendingLocations();
} catch (RpcException e) {
Log.e(TAG, "Failed to stop sending locations", e);
}
ActiveLocationChats.clear(this);
}
@Nullable
@@ -150,16 +178,21 @@ public class LocationStreamingService extends Service {
private void publishAndWrite(Location location) {
LocationData.getInstance().post(location);
boolean keepGoing =
DcHelper.getContext(this)
.setLocation(
(float) location.getLatitude(),
(float) location.getLongitude(),
location.getAccuracy());
boolean keepGoing;
try {
keepGoing =
DcHelper.getRpc(this)
.setLocation(
(float) location.getLatitude(),
(float) location.getLongitude(),
location.getAccuracy());
} catch (RpcException e) {
Log.e(TAG, "Failed to set location", e);
return;
}
Log.d(TAG, "keepGoing: " + keepGoing);
if (!keepGoing) {
stopAllSharing();
stopSelf();
}
}
@@ -36,6 +36,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import chat.delta.rpc.Rpc;
import chat.delta.rpc.RpcException;
import chat.delta.util.ListenableFuture;
import chat.delta.util.SettableFuture;
@@ -63,7 +64,6 @@ import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
import org.thoughtcrime.securesms.components.audioplay.AudioView;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.geolocation.ActiveLocationChats;
import org.thoughtcrime.securesms.geolocation.LocationStreamingService;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
@@ -491,16 +491,29 @@ public class AttachmentManager {
public static void selectLocation(Activity activity, int chatId) {
Context appContext = activity.getApplicationContext();
Rpc rpc = DcHelper.getRpc(appContext);
int accountId = DcHelper.getContext(appContext).getAccountId();
if (DcHelper.getContext(appContext).isSendingLocationsToChat(chatId)) {
boolean currentlySharing;
try {
currentlySharing = rpc.isSendingLocationsToChat(accountId, chatId);
} catch (RpcException e) {
Log.e(TAG, "Failed to check location streaming state", e);
return;
}
if (currentlySharing) {
if (LocationStreamingService.isRunning()) {
LocationStreamingService.stopSharing(appContext, chatId);
LocationStreamingService.stopSharing(appContext, accountId, chatId);
return;
}
// Stale service is dead but chat layer still thinks it's sharing.
// Clean up this chat and fall through to the fresh start flow.
ActiveLocationChats.remove(appContext, chatId);
DcHelper.getContext(appContext).sendLocationsToChat(chatId, 0);
// Clean up and fall through to the fresh start flow.
try {
rpc.sendLocationsToChat(accountId, chatId, 0);
} catch (RpcException e) {
Log.e(TAG, "Failed to stop stale location streaming", e);
}
}
Permissions.with(activity)
@@ -514,7 +527,8 @@ public class AttachmentManager {
ShareLocationDialog.show(
activity,
durationInSeconds ->
LocationStreamingService.startSharing(appContext, chatId, durationInSeconds));
LocationStreamingService.startSharing(
appContext, accountId, chatId, durationInSeconds));
})
.request(
android.Manifest.permission.ACCESS_FINE_LOCATION,
@@ -167,7 +167,7 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
notificationsEnabled.setChecked(!dcContext.isMuted());
notificationsEnabled.setSummary(getSummary(getContext(), false));
mentionNotifEnabled.setChecked(dcContext.isMentionsEnabled());
notifyCalls.setChecked(dcContext.getConfig("who_can_call_me") != "2");
notifyCalls.setChecked(!"2".equals(dcContext.getConfig("who_can_call_me")));
// set without altering "unset" state of the preference
reliableService.setOnPreferenceChangeListener(null);
@@ -73,6 +73,9 @@ public class EditRelayActivity extends BaseActionBarActivity
private ProgressDialog progressDialog;
private boolean cancelled = false;
private View imapFolderLayout;
private boolean showImapFolder = false;
Spinner imapSecurity;
Spinner smtpSecurity;
Spinner certCheck;
@@ -102,9 +105,11 @@ public class EditRelayActivity extends BaseActionBarActivity
advancedGroup = findViewById(R.id.advanced_group);
advancedIcon = findViewById(R.id.advanced_icon);
imapFolderLayout = findViewById(R.id.imap_folder);
TextView advancedTextView = findViewById(R.id.advanced_text);
TextInputEditText imapServerInput = findViewById(R.id.imap_server_text);
TextInputEditText imapPortInput = findViewById(R.id.imap_port_text);
TextInputEditText imapFolderInput = findViewById(R.id.imap_folder_text);
TextInputEditText smtpServerInput = findViewById(R.id.smtp_server_text);
TextInputEditText smtpPortInput = findViewById(R.id.smtp_port_text);
TextView viewLogText = findViewById(R.id.view_log_button);
@@ -197,6 +202,12 @@ public class EditRelayActivity extends BaseActionBarActivity
if (config.imapPort != null) imapPortInput.setText(config.imapPort.toString());
expandAdvanced = expandAdvanced || config.imapPort != null;
showImapFolder = !TextUtils.isEmpty(config.imapFolder);
if (showImapFolder) {
imapFolderInput.setText(config.imapFolder);
expandAdvanced = true;
}
intVal = socketSecurityToInt(config.imapSecurity);
imapSecurity.setSelection(ViewUtil.checkBounds(intVal, imapSecurity));
expandAdvanced = expandAdvanced || intVal != 0;
@@ -393,9 +404,11 @@ public class EditRelayActivity extends BaseActionBarActivity
boolean advancedViewVisible = advancedGroup.getVisibility() == View.VISIBLE;
if (advancedViewVisible) {
advancedGroup.setVisibility(View.GONE);
imapFolderLayout.setVisibility(View.GONE);
advancedIcon.setRotation(45);
} else {
advancedGroup.setVisibility(View.VISIBLE);
imapFolderLayout.setVisibility(showImapFolder ? View.VISIBLE : View.GONE);
advancedIcon.setRotation(0);
}
}
@@ -498,6 +511,7 @@ public class EditRelayActivity extends BaseActionBarActivity
param.password = getParam(R.id.password_text, false);
param.imapServer = getParam(R.id.imap_server_text, true);
param.imapPort = Util.objectToInt(getParam(R.id.imap_port_text, true));
param.imapFolder = getParam(R.id.imap_folder_text, true);
param.imapSecurity = socketSecurityFromInt(imapSecurity.getSelectedItemPosition());
param.imapUser = getParam(R.id.imap_login_text, false);
param.smtpServer = getParam(R.id.smtp_server_text, true);
@@ -22,6 +22,7 @@ class SearchViewModel extends ViewModel {
private final Context appContext;
private final DcContext dcContext;
private boolean forwarding = false;
private final Object bgSearchLock = new Object();
private boolean inBgSearch;
private boolean needsAnotherBgSearch;
@@ -45,27 +46,29 @@ class SearchViewModel extends ViewModel {
}
public void updateQuery() {
if (inBgSearch) {
needsAnotherBgSearch = true;
Log.i(TAG, "... search call debounced");
} else {
synchronized (bgSearchLock) {
if (inBgSearch) {
Log.i(TAG, "... search call debounced");
needsAnotherBgSearch = true;
return;
}
inBgSearch = true;
Util.runOnBackground(
() -> {
Util.sleep(100);
needsAnotherBgSearch = false;
queryAndCallback(lastQuery, searchResult::postValue);
while (needsAnotherBgSearch) {
Util.sleep(100);
needsAnotherBgSearch = false;
Log.i(TAG, "... executing debounced search call");
queryAndCallback(lastQuery, searchResult::postValue);
}
inBgSearch = false;
});
}
Util.runOnBackground(
() -> {
while (true) {
Log.i(TAG, "... executing debounced search call");
queryAndCallback(lastQuery, searchResult::postValue);
synchronized (bgSearchLock) {
if (!needsAnotherBgSearch) {
inBgSearch = false;
return;
}
needsAnotherBgSearch = false;
}
Util.sleep(100);
}
});
}
private void queryAndCallback(@NonNull String query, @NonNull SearchViewModel.Callback callback) {
@@ -15,7 +15,6 @@ public class ShareUtil {
private static final String FORWARDED_MESSAGE_ACCID = "forwarded_message_accid";
private static final String FORWARDED_MESSAGE_IDS = "forwarded_message_ids";
private static final String SHARED_URIS = "shared_uris";
private static final String SHARED_CONTACT_ID = "shared_contact_id";
private static final String IS_SHARING = "is_sharing";
private static final String IS_FROM_WEBXDC = "is_from_webxdc";
private static final String SHARED_TITLE = "shared_title";
@@ -112,15 +111,6 @@ public class ShareUtil {
}
}
public static int getSharedContactId(Activity activity) {
try {
return activity.getIntent().getIntExtra(SHARED_CONTACT_ID, 0);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
public static String getSharedText(Activity activity) {
try {
return activity.getIntent().getStringExtra(TEXT_EXTRA);
@@ -142,7 +132,6 @@ public class ShareUtil {
activity.getIntent().removeExtra(FORWARDED_MESSAGE_ACCID);
activity.getIntent().removeExtra(FORWARDED_MESSAGE_IDS);
activity.getIntent().removeExtra(SHARED_URIS);
activity.getIntent().removeExtra(SHARED_CONTACT_ID);
activity.getIntent().removeExtra(IS_SHARING);
activity.getIntent().removeExtra(DIRECT_SHARING_CHAT_ID);
activity.getIntent().removeExtra(TEXT_EXTRA);
@@ -167,9 +156,6 @@ public class ShareUtil {
if (!getSharedUris(currentActivity).isEmpty()) {
newActivityIntent.putParcelableArrayListExtra(SHARED_URIS, getSharedUris(currentActivity));
}
if (getSharedContactId(currentActivity) != 0) {
newActivityIntent.putExtra(SHARED_CONTACT_ID, getSharedContactId(currentActivity));
}
if (getSharedText(currentActivity) != null) {
newActivityIntent.putExtra(TEXT_EXTRA, getSharedText(currentActivity));
}
@@ -220,11 +206,6 @@ public class ShareUtil {
composeIntent.putExtra(IS_SHARING, true);
}
public static void setSharedContactId(Intent composeIntent, int contactId) {
composeIntent.putExtra(SHARED_CONTACT_ID, contactId);
composeIntent.putExtra(IS_SHARING, true);
}
public static void setSharedTitle(Intent composeIntent, String text) {
composeIntent.putExtra(SHARED_TITLE, text);
}
+18 -1
View File
@@ -220,6 +220,23 @@
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/imap_folder"
android:layout_width="0dp"
android:layout_height="58dp"
app:layout_constraintEnd_toEndOf="@id/guideline_root_end"
app:layout_constraintStart_toStartOf="@id/guideline_root_start"
app:layout_constraintTop_toBottomOf="@id/imap_port">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/imap_folder_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
android:hint="IMAP folder" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/imap_security_label"
style="@style/TextAppearance.AppCompat.Caption"
@@ -228,7 +245,7 @@
android:text="@string/login_imap_security"
app:layout_constraintEnd_toEndOf="@id/guideline_root_end"
app:layout_constraintStart_toStartOf="@id/guideline_root_start"
app:layout_constraintTop_toBottomOf="@id/imap_port" />
app:layout_constraintTop_toBottomOf="@id/imap_folder" />
<Spinner
android:id="@+id/imap_security"
+4
View File
@@ -70,6 +70,10 @@ window.webxdc = (() => {
selfName: InternalJSApi.selfName(),
isAppSender: InternalJSApi.isAppSender(),
isBroadcast: InternalJSApi.isBroadcast(),
joinRealtimeChannel: () => {
realtimeChannel = createRealtimeChannel();
InternalJSApi.sendRealtimeAdvertisement();
+6 -1
View File
@@ -944,7 +944,7 @@
<string name="welcome_desktop">Sveiki atvykę į Delta Chat</string>
<string name="global_menu_preferences_language_desktop">Pasirinkti kalbą...</string>
<string name="global_menu_file_desktop">Failas</string>
<string name="global_menu_file_quit_desktop">eiti</string>
<string name="global_menu_file_quit_desktop">jungti</string>
<string name="global_menu_edit_desktop">Taisyti</string>
<string name="global_menu_edit_undo_desktop">Atšaukti</string>
<string name="global_menu_edit_redo_desktop">Grąžinti</string>
@@ -956,6 +956,8 @@
<string name="global_menu_view_developer_desktop">Plėtotojas</string>
<string name="global_menu_view_developer_tools_desktop">Plėtotojo įrankiai</string>
<string name="global_menu_help_desktop">Žinynas</string>
<string name="delta_chat_homepage">Delta Chat internetinė svetainė</string>
<string name="contribute">Talkinti</string>
<string name="global_menu_help_report_desktop">Pranešti apie klaidą</string>
<string name="global_menu_help_about_desktop">Apie Delta Chat</string>
<string name="global_menu_file_open_desktop">Atverti Delta Chat</string>
@@ -990,6 +992,9 @@
<string name="a11y_message_context_menu_btn_label">Veiksmai su žinute</string>
<string name="a11y_background_preview_label">Fono peržiūra</string>
<string name="a11y_disappearing_messages_activated">Išnykstančios žinutės aktyvuotos</string>
<!-- The placeholder will be replaced by a link, eg. "Open https://delta.chat". Used e.g. in for accessibility. -->
<string name="open_link">Atverti %1$s</string>
<string name="a11y_connectivity_hint">Bakstelėkite du kartus norėdami pamatyti išsamesnę informaciją apie jungiamumą.</string>
<string name="login_error_no_internet_connection">Nėra interneto ryšio, nepavyko prisijungti.</string>
<string name="share_account_not_configured">Paskyra nėra sukonfigūruota.</string>
+3
View File
@@ -438,6 +438,7 @@
<string name="call_answered_elsewhere">Er is opgenomen op een ander apparaat</string>
<string name="call_requires_camera_permission">Cameratoegang is vereist om te kunnen videobellen</string>
<string name="call_requires_mic_permission">Microfoontoegang is vereist om te kunnen bellen</string>
<string name="call_requires_connection">De oproep kan niet worden gestart. Zorg dat je verbonden bent met het internet en probeer het opnieuw.</string>
<!-- Used e.g. in a notifications to describe an ongoing call. "Call" is a noun here, in the meaning of "This is the call with ..." where the placeholder is replaced by the name of the contact. -->
<string name="call_with">Bellen met %1$s</string>
<!-- Use e.g. in a notifications during ringing. Placeholder is relaced by the name of the contact being called. -->
@@ -672,6 +673,7 @@
<string name="welcome_chat_over_email">Veilige, gedecentraliseerde gesprekken</string>
<string name="scan_invitation_code">Uitnodigingscode scannen</string>
<string name="login_title">Inloggen</string>
<string name="login_advanced_hint">Deze inlogmethode is alleen voor ervaren gebruikers.\n\nGebruik geen adres dat je reeds in een andere app gebruikt.</string>
<string name="login_inbox">Postvak in</string>
<string name="login_imap_login">IMAP-inlognaam</string>
<string name="login_imap_server">IMAP-server</string>
@@ -706,6 +708,7 @@
<string name="used_for_sending">Wordt gebruikt voor versturen</string>
<string name="hide_from_contacts">Verbergen op contactpersonenlijst</string>
<string name="hidden_from_contacts">Verborgen op contactpersonenlijst</string>
<string name="enforce_e2ee">Versleuteling afdwingen op alle relays</string>
<!-- Hint for the list of relays -->
<string name="transport_list_hint">Berichten worden op alle relays ontvangen.\n\n⚠️ Als je hier iets wijzigt, doe dat dan alléén als ál je apparaten gebruikmaken van versie 2.47.0, anders kunnen er berichten ontbreken.</string>
<!-- shown if a QR code was scanned that can be used as a relay -->
+6 -3
View File
@@ -462,6 +462,7 @@
<string name="call_answered_elsewhere">Звонок отвечен на другом устройстве</string>
<string name="call_requires_camera_permission">Для видеозвонков требуется разрешение на использование камеры</string>
<string name="call_requires_mic_permission">Для совершения звонков требуется разрешение на использование микрофона</string>
<string name="call_requires_connection">Не удалось начать вызов. Убедитесь, что ваше устройство подключено к интернету, и повторите попытку.</string>
<!-- Used e.g. in a notifications to describe an ongoing call. "Call" is a noun here, in the meaning of "This is the call with ..." where the placeholder is replaced by the name of the contact. -->
<string name="call_with">Звонок от %1$s</string>
<!-- Use e.g. in a notifications during ringing. Placeholder is relaced by the name of the contact being called. -->
@@ -487,9 +488,9 @@
<!-- First placeholder gets replaced by the number of messages to forward. Second placeholder gets replaced by the name of the destination chat -->
<plurals name="ask_forward_messages">
<item quantity="one">Переслать сообщение пользователю %2$s?</item>
<item quantity="few">Переслать %1$d сообщения пользователям %2$s?</item>
<item quantity="many">Переслать %1$d сообщения пользователям %2$s?</item>
<item quantity="other">Переслать %1$d сообщения пользователям %2$s?</item>
<item quantity="few">Переслать %1$d сообщения пользователю %2$s?</item>
<item quantity="many">Переслать %1$d сообщений пользователю %2$s?</item>
<item quantity="other">Переслать %1$d сообщений пользователю %2$s?</item>
</plurals>
<!-- deprecated, use ask_forward_messages -->
<string name="ask_forward">Переслать сообщения %1$s?</string>
@@ -708,6 +709,7 @@
<string name="welcome_chat_over_email">Безопасный децентрализованный чат</string>
<string name="scan_invitation_code">Сканировать код приглашения</string>
<string name="login_title">Войти</string>
<string name="login_advanced_hint">Этот способ входа предназначен для опытных пользователей.\n\nНе используйте адрес, который вы уже используете в другом приложении.</string>
<string name="login_inbox">Входящие</string>
<string name="login_imap_login">Имя пользователя IMAP</string>
<string name="login_imap_server">Сервер IMAP</string>
@@ -742,6 +744,7 @@
<string name="used_for_sending">Используется для отправки</string>
<string name="hide_from_contacts">Скрыть из контактов</string>
<string name="hidden_from_contacts">Скрыт из контактов</string>
<string name="enforce_e2ee">Принудительное шифрование для всех релеев</string>
<!-- Hint for the list of relays -->
<string name="transport_list_hint">Сообщения поступают на все релеи.\n\n⚠️ Если вы измените какие-либо настройки в этом разделе, убедитесь, что на всех ваших устройствах установлена версия Delta Chat не ниже 2.47.0. В противном случае старые версии могут не получать сообщения.</string>
<!-- shown if a QR code was scanned that can be used as a relay -->
+4
View File
@@ -430,6 +430,7 @@
<string name="call_answered_elsewhere">Thirrje që ka marrë përgjigje në pajisje tjetër</string>
<string name="call_requires_camera_permission">Për thirrje me video janë të domosdoshme leje mbi kamerën</string>
<string name="call_requires_mic_permission">Leja mbi mikrofonin është e domosdoshme për thirrje</string>
<string name="call_requires_connection">Sniset dot thirrja. Sigurohuni se keni lidhje internet dhe riprovoni.</string>
<!-- Used e.g. in a notifications to describe an ongoing call. "Call" is a noun here, in the meaning of "This is the call with ..." where the placeholder is replaced by the name of the contact. -->
<string name="call_with">Thirrje me %1$s</string>
<!-- Use e.g. in a notifications during ringing. Placeholder is relaced by the name of the contact being called. -->
@@ -664,6 +665,7 @@
<string name="welcome_chat_over_email">Fjalosje e Decentralizuar, e Parrezik</string>
<string name="scan_invitation_code">Skanoni Kod Ftese</string>
<string name="login_title">Hyni</string>
<string name="login_advanced_hint">Kjo hyrje është për përdorues të thelluar.\n\nMos përdorni një adresë që e përdorni në një aplikacion tjetër.</string>
<string name="login_inbox">Të marrë</string>
<string name="login_imap_login">Emër Hyrjeje në IMAP</string>
<string name="login_imap_server">Shërbyes IMAP</string>
@@ -698,6 +700,7 @@
<string name="used_for_sending">E përdorur për dërgim</string>
<string name="hide_from_contacts">Fshihe prej Kontaktesh</string>
<string name="hidden_from_contacts">Fshehur prej kontakteve</string>
<string name="enforce_e2ee">Detyro Fshehtëzim për Krejt Reletë</string>
<!-- Hint for the list of relays -->
<string name="transport_list_hint">Mesazhet merren në të tëra reletë.\n\n⚠️ Nëse ndryshoni diçka këtu, sigurohuni se krejt pajisjet tuaja xhirojnë të paktën versionin 2.47.0. Përndryshe, pajisje më të vjetra mund të humbin mesazhe.</string>
<!-- shown if a QR code was scanned that can be used as a relay -->
@@ -841,6 +844,7 @@
<string name="pref_show_emails_all">Krejt</string>
<string name="pref_experimental_features">Veçori Eksperimentale</string>
<string name="pref_experimental_features_explain">Këto veçori mund të jenë të paqëndrueshme dhe mund të ndryshohen, ose hiqen</string>
<string name="pref_on_demand_location_streaming">Dhënie Vendndodhjeje</string>
<string name="pref_background_default">Figurë parazgjedhje</string>
<string name="pref_background_default_color">Ngjyrë parazgjedhje</string>
<string name="pref_background_custom_image">Figurë vetjake</string>
+3
View File
@@ -426,6 +426,7 @@
<string name="call_answered_elsewhere">通话已在其他设备上接听</string>
<string name="call_requires_camera_permission">视频通话需要摄像头权限</string>
<string name="call_requires_mic_permission">通话需要麦克风权限</string>
<string name="call_requires_connection">无法开始通话。请确保您的设备已连接到互联网,然后重试。</string>
<!-- Used e.g. in a notifications to describe an ongoing call. "Call" is a noun here, in the meaning of "This is the call with ..." where the placeholder is replaced by the name of the contact. -->
<string name="call_with">与 %1$s 的通话</string>
<!-- Use e.g. in a notifications during ringing. Placeholder is relaced by the name of the contact being called. -->
@@ -654,6 +655,7 @@
<string name="welcome_chat_over_email">安全的去中心化聊天</string>
<string name="scan_invitation_code">扫描邀请码</string>
<string name="login_title">登录</string>
<string name="login_advanced_hint">此登录适用于高级用户。\n\n请勿使用您在其他应用中已使用的地址。</string>
<string name="login_inbox">收件箱</string>
<string name="login_imap_login">IMAP 登录名</string>
<string name="login_imap_server">IMAP 服务器</string>
@@ -688,6 +690,7 @@
<string name="used_for_sending">用于发送</string>
<string name="hide_from_contacts">对联系人隐藏</string>
<string name="hidden_from_contacts">已对联系人隐藏</string>
<string name="enforce_e2ee">对所有中继强制加密</string>
<!-- Hint for the list of relays -->
<string name="transport_list_hint">在所有中继上均已收到消息。\n\n⚠️ 如果您在此处进行任何更改,请确保所有设备至少运行 2.47.0 版本。否则,较旧的设备可能会错过消息。</string>
<!-- shown if a QR code was scanned that can be used as a relay -->