mirror of
https://github.com/ArcaneChat/android.git
synced 2026-07-03 14:05:24 +02:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b9649a908e | |||
| 2f20f558ad | |||
| 6f868f653a | |||
| 42b529dd4a | |||
| d7eab73687 | |||
| 1deebdf552 | |||
| 20fbb0e1ee | |||
| 6a53e229d1 | |||
| 74ef2d478a | |||
| feeece405d | |||
| 20459a5710 | |||
| a3a8b3581f | |||
| f2571dba91 | |||
| 708ffe617c | |||
| f6ecb94047 | |||
| 2d4b52b037 | |||
| 5257f39aee | |||
| 20f1475856 | |||
| db0295e5a6 | |||
| d019c1bfb5 | |||
| 4542ba1b9a | |||
| 9b982db899 | |||
| a1a72b79b3 | |||
| 319e2ebe5a | |||
| 49a00f3788 | |||
| 74ee2fb8db | |||
| 655ed8cad4 | |||
| 1d94fe8bca |
@@ -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
@@ -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
|
||||
|
||||
+1
-1
Submodule jni/deltachat-core-rust updated: ad50c27fab...66254b9c8c
@@ -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>That’s 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>Don’t 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 channel’s 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 don’t 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 contact’s 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<>());
|
||||
}
|
||||
}
|
||||
+68
-35
@@ -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,
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -70,6 +70,10 @@ window.webxdc = (() => {
|
||||
|
||||
selfName: InternalJSApi.selfName(),
|
||||
|
||||
isAppSender: InternalJSApi.isAppSender(),
|
||||
|
||||
isBroadcast: InternalJSApi.isBroadcast(),
|
||||
|
||||
joinRealtimeChannel: () => {
|
||||
realtimeChannel = createRealtimeChannel();
|
||||
InternalJSApi.sendRealtimeAdvertisement();
|
||||
|
||||
@@ -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">Išeiti</string>
|
||||
<string name="global_menu_file_quit_desktop">Iš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>
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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">S’niset 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>
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
Reference in New Issue
Block a user