diff --git a/app/build.gradle b/app/build.gradle
index 791f8a884a..9480b4621c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -83,8 +83,8 @@ ktlint {
version = "0.43.2"
}
-def canonicalVersionCode = 988
-def canonicalVersionName = "5.29.4"
+def canonicalVersionCode = 990
+def canonicalVersionName = "5.29.6"
def mollyRevision = 0
def postFixSize = 100
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
index 2c088024ad..1da0f92d8f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
@@ -39,6 +39,7 @@
import org.signal.core.util.logging.Log;
import org.signal.core.util.tracing.Tracer;
import org.signal.glide.SignalGlideCodecs;
+import org.thoughtcrime.securesms.emoji.JumboEmoji;
import org.thoughtcrime.securesms.mms.SignalGlideModule;
import org.signal.ringrtc.CallManager;
import org.thoughtcrime.securesms.avatar.AvatarPickerStorage;
@@ -209,6 +210,7 @@ private void onCreateUnlock() {
.addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this))
.addPostRender(EmojiSearchIndexDownloadJob::scheduleIfNecessary)
.addPostRender(() -> SignalDatabase.messageLog().trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge()))
+ .addPostRender(() -> JumboEmoji.updateCurrentVersion(this))
.execute();
Log.d(TAG, "onCreateUnlock() took " + (System.currentTimeMillis() - startTime) + " ms");
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/Emoji.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/Emoji.java
index 155d541594..6935bf17ab 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/Emoji.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/Emoji.java
@@ -1,18 +1,27 @@
package org.thoughtcrime.securesms.components.emoji;
+import androidx.annotation.Nullable;
+
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
public class Emoji {
private final List variations;
+ private final List rawVariations;
public Emoji(String... variations) {
- this.variations = Arrays.asList(variations);
+ this(Arrays.asList(variations), Collections.emptyList());
}
public Emoji(List variations) {
+ this(variations, Collections.emptyList());
+ }
+
+ public Emoji(List variations, List rawVariations) {
this.variations = variations;
+ this.rawVariations = rawVariations;
}
public String getValue() {
@@ -26,4 +35,11 @@ public List getVariations() {
public boolean hasMultipleVariations() {
return variations.size() > 1;
}
+
+ public @Nullable String getRawVariation(int variationIndex) {
+ if (rawVariations != null && variationIndex >= 0 && variationIndex < rawVariations.size()) {
+ return rawVariations.get(variationIndex);
+ }
+ return null;
+ }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java
index e53bcbba6c..c7b1b9437e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java
@@ -149,8 +149,8 @@ public void onFailure(ExecutionException exception) {
throw new IllegalStateException("Unexpected subclass " + loadResult.getClass());
}
- if (jumboEmoji) {
- JumboEmoji.LoadResult result = JumboEmoji.loadJumboEmoji(context, drawInfo.getRawEmoji());
+ if (jumboEmoji && drawInfo.getJumboSheet() != null) {
+ JumboEmoji.LoadResult result = JumboEmoji.loadJumboEmoji(context, drawInfo);
if (result instanceof JumboEmoji.LoadResult.Immediate) {
ThreadUtil.runOnMain(() -> {
jumboLoaded.set(true);
@@ -171,7 +171,11 @@ public void onSuccess(Bitmap result) {
@Override
public void onFailure(ExecutionException exception) {
- Log.d(TAG, "Failed to load jumbo emoji bitmap resource", exception);
+ if (exception.getCause() instanceof JumboEmoji.CannotAutoDownload) {
+ Log.i(TAG, "Download restrictions are preventing jumbomoji use");
+ } else {
+ Log.d(TAG, "Failed to load jumbo emoji bitmap resource", exception);
+ }
}
});
}
@@ -200,15 +204,19 @@ public void onFailure(ExecutionException exception) {
Bitmap bitmap = null;
- if (jumboEmoji) {
- JumboEmoji.LoadResult result = JumboEmoji.loadJumboEmoji(context, drawInfo.getRawEmoji());
+ if (jumboEmoji && drawInfo.getJumboSheet() != null) {
+ JumboEmoji.LoadResult result = JumboEmoji.loadJumboEmoji(context, drawInfo);
if (result instanceof JumboEmoji.LoadResult.Immediate) {
bitmap = ((JumboEmoji.LoadResult.Immediate) result).getBitmap();
} else if (result instanceof JumboEmoji.LoadResult.Async) {
try {
bitmap = ((JumboEmoji.LoadResult.Async) result).getTask().get(10, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException exception) {
- Log.d(TAG, "Failed to load jumbo emoji bitmap resource", exception);
+ if (exception.getCause() instanceof JumboEmoji.CannotAutoDownload) {
+ Log.i(TAG, "Download restrictions are preventing jumbomoji use");
+ } else {
+ Log.d(TAG, "Failed to load jumbo emoji bitmap resource", exception);
+ }
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java
index f7c1859f65..d5bbf0f180 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java
@@ -30,6 +30,7 @@
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
import org.thoughtcrime.securesms.components.mention.MentionRendererDelegate;
+import org.thoughtcrime.securesms.emoji.JumboEmoji;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
@@ -43,7 +44,8 @@ public class EmojiTextView extends AppCompatTextView {
private final boolean scaleEmojis;
- private static final char ELLIPSIS = '…';
+ private static final char ELLIPSIS = '…';
+ private static final float JUMBOMOJI_SCALE = 0.8f;
private boolean forceCustom;
private CharSequence previousText;
@@ -113,13 +115,13 @@ protected void onDraw(Canvas canvas) {
public void setText(@Nullable CharSequence text, BufferType type) {
EmojiParser.CandidateList candidates = isInEditMode() ? null : EmojiProvider.getCandidates(text);
- if (scaleEmojis && candidates != null && candidates.allEmojis) {
+ if (scaleEmojis && candidates != null && candidates.allEmojis && (candidates.hasJumboForAll() || JumboEmoji.canDownloadJumbo(getContext()))) {
int emojis = candidates.size();
float scale = 1.0f;
- if (emojis <= 5) scale += 0.9f;
- if (emojis <= 4) scale += 0.9f;
- if (emojis <= 2) scale += 0.9f;
+ if (emojis <= 5) scale += JUMBOMOJI_SCALE;
+ if (emojis <= 4) scale += JUMBOMOJI_SCALE;
+ if (emojis <= 2) scale += JUMBOMOJI_SCALE;
isJumbomoji = scale > 1.0f;
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, originalFontSize * scale);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiDrawInfo.kt b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiDrawInfo.kt
index a19fa638ba..28de3aca78 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiDrawInfo.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiDrawInfo.kt
@@ -1,13 +1,5 @@
package org.thoughtcrime.securesms.components.emoji.parsing
import org.thoughtcrime.securesms.emoji.EmojiPage
-import org.thoughtcrime.securesms.util.Hex
-import java.nio.charset.Charset
-data class EmojiDrawInfo(val page: EmojiPage, val index: Int, private val emoji: String) {
- val rawEmoji: String
- get() {
- val emojiBytes: ByteArray = emoji.toByteArray(Charset.forName("UTF-16"))
- return Hex.toStringCondensed(emojiBytes.slice(2 until emojiBytes.size).toByteArray())
- }
-}
+data class EmojiDrawInfo(val page: EmojiPage, val index: Int, private val emoji: String, val rawEmoji: String?, val jumboSheet: String?)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiParser.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiParser.java
index 91450e102b..ced9178683 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiParser.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiParser.java
@@ -24,6 +24,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import org.thoughtcrime.securesms.emoji.JumboEmoji;
+
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -127,6 +129,15 @@ public int size() {
return list.size();
}
+ public boolean hasJumboForAll() {
+ for (Candidate candidate : list) {
+ if (!JumboEmoji.hasJumboEmoji(candidate.drawInfo)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
@Override
public @NonNull Iterator iterator() {
return list.iterator();
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java
index a262dcda73..cb662abc9f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java
@@ -3560,14 +3560,14 @@ public void handleReaction(@NonNull ConversationMessage conversationMessage,
@Override
public void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord) {
- if (messageRecord.hasFailedWithNetworkFailures()) {
+ if (messageRecord.isIdentityMismatchFailure()) {
+ SafetyNumberChangeDialog.show(this, messageRecord);
+ } else if (messageRecord.hasFailedWithNetworkFailures()) {
new AlertDialog.Builder(this)
.setMessage(R.string.conversation_activity__message_could_not_be_sent)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.conversation_activity__send, (dialog, which) -> MessageSender.resend(this, messageRecord))
.show();
- } else if (messageRecord.isIdentityMismatchFailure()) {
- SafetyNumberChangeDialog.show(this, messageRecord);
} else {
startActivity(MessageDetailsActivity.getIntentForMessageDetails(this, messageRecord, messageRecord.getRecipient().getId(), messageRecord.getThreadId()));
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java
index 9f494f7a67..df0238be5e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java
@@ -849,9 +849,11 @@ public void markAsRemoteDelete(long messageId) {
deletedAttachments = SignalDatabase.attachments().deleteAttachmentsForMessage(messageId);
SignalDatabase.mentions().deleteMentionsForMessage(messageId);
SignalDatabase.messageLog().deleteAllRelatedToMessage(messageId, true);
+ SignalDatabase.reactions().deleteReactions(new MessageId(messageId, true));
threadId = getThreadIdForMessage(messageId);
SignalDatabase.threads().update(threadId, false);
+
db.setTransactionSuccessful();
} finally {
db.endTransaction();
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt
index 6ce8ce7a75..d80e6da5fc 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt
@@ -168,6 +168,10 @@ class ReactionDatabase(context: Context, databaseHelper: SignalDatabase) : Datab
ApplicationDependencies.getDatabaseObserver().notifyMessageUpdateObservers(messageId)
}
+ fun deleteReactions(messageId: MessageId) {
+ writableDatabase.delete(TABLE_NAME, "$MESSAGE_ID = ? AND $IS_MMS = ?", SqlUtil.buildArgs(messageId.id, if (messageId.mms) 1 else 0))
+ }
+
fun hasReaction(messageId: MessageId, reaction: ReactionRecord): Boolean {
val query = "$MESSAGE_ID = ? AND $IS_MMS = ? AND $AUTHOR_ID = ? AND $EMOJI = ?"
val args = SqlUtil.buildArgs(messageId.id, if (messageId.mms) 1 else 0, reaction.author, reaction.emoji)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java
index 59a0417d05..1f2724364b 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java
@@ -392,6 +392,7 @@ public void markAsRemoteDelete(long id) {
threadId = getThreadIdForMessage(id);
+ SignalDatabase.reactions().deleteReactions(new MessageId(id, false));
SignalDatabase.threads().update(threadId, false);
SignalDatabase.messageLog().deleteAllRelatedToMessage(id, false);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt
index dc78dfbc08..bb8d472716 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt
@@ -132,8 +132,9 @@ object SignalDatabaseMigrations {
private const val NOTIFICATION_PROFILES = 123
private const val NOTIFICATION_PROFILES_END_FIX = 124
private const val REACTION_BACKUP_CLEANUP = 125
+ private const val REACTION_REMOTE_DELETE_CLEANUP = 126
- const val DATABASE_VERSION = 125
+ const val DATABASE_VERSION = 126
@JvmStatic
fun migrate(context: Context, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
@@ -1643,6 +1644,19 @@ object SignalDatabaseMigrations {
""".trimIndent()
)
}
+
+ if (oldVersion < REACTION_REMOTE_DELETE_CLEANUP) {
+ db.execSQL(
+ // language=sql
+ """
+ DELETE FROM reaction
+ WHERE
+ (is_mms = 0 AND message_id IN (SELECT _id from sms WHERE remote_deleted = 1))
+ OR
+ (is_mms = 1 AND message_id IN (SELECT _id from mms WHERE remote_deleted = 1))
+ """.trimIndent()
+ )
+ }
}
@JvmStatic
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java
index 506bd6815f..76bf99b33d 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java
@@ -612,7 +612,7 @@ public boolean isJumbomoji(Context context) {
if (isJumboji == null) {
if (getBody().length() <= EmojiSource.getLatest().getMaxEmojiLength() * JumboEmoji.MAX_JUMBOJI_COUNT) {
EmojiParser.CandidateList candidates = EmojiProvider.getCandidates(getDisplayBody(context));
- isJumboji = candidates != null && candidates.allEmojis && candidates.size() <= JumboEmoji.MAX_JUMBOJI_COUNT;
+ isJumboji = candidates != null && candidates.allEmojis && candidates.size() <= JumboEmoji.MAX_JUMBOJI_COUNT && (candidates.hasJumboForAll() || JumboEmoji.canDownloadJumbo(context));
} else {
isJumboji = false;
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiDownloader.java b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiDownloader.java
index 920ca7460a..4c1bd3ab57 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiDownloader.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiDownloader.java
@@ -4,10 +4,12 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.util.Consumer;
import com.mobilecoin.lib.util.Hex;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.UUID;
@@ -44,6 +46,16 @@ public class EmojiDownloader {
() -> new EmojiFiles.Name(imagePath, UUID.randomUUID()));
}
+ public static void streamFileFromRemote(@NonNull EmojiFiles.Version version,
+ @NonNull String bucket,
+ @NonNull String path,
+ @NonNull Consumer streamConsumer)
+ throws IOException
+ {
+ streamFromRemote(() -> EmojiRemote.getObject(new EmojiFileRequest(version.getVersion(), bucket, path)),
+ streamConsumer);
+ }
+
private static @NonNull EmojiFiles.Name downloadAndVerifyFromRemote(@NonNull Context context,
@NonNull EmojiFiles.Version version,
@NonNull Producer responseProducer,
@@ -90,6 +102,23 @@ public class EmojiDownloader {
}
}
+ private static void streamFromRemote(@NonNull Producer responseProducer,
+ @NonNull Consumer streamConsumer) throws IOException
+ {
+ try (Response response = responseProducer.produce()) {
+ if (!response.isSuccessful()) {
+ throw new IOException("Unsuccessful response " + response.code());
+ }
+
+ ResponseBody responseBody = response.body();
+ if (responseBody == null) {
+ throw new IOException("No response body");
+ }
+
+ streamConsumer.accept(Okio.buffer(responseBody.source()).inputStream());
+ }
+ }
+
private static @Nullable String getMD5FromResponse(@NonNull Response response) {
Pattern pattern = Pattern.compile(".*([a-f0-9]{32}).*");
String header = response.header("etag");
diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiJsonParser.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiJsonParser.kt
index 78cb318a6a..c8f7d1d010 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiJsonParser.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiJsonParser.kt
@@ -18,6 +18,7 @@ typealias UriFactory = (sprite: String, format: String) -> Uri
*/
object EmojiJsonParser {
private val OBJECT_MAPPER = ObjectMapper()
+ private const val ESTIMATED_EMOJI_COUNT = 3500
@JvmStatic
fun verify(body: InputStream) {
@@ -36,11 +37,12 @@ object EmojiJsonParser {
val format: String = node["format"].textValue()
val obsolete: List = node["obsolete"].toObseleteList()
val dataPages: List = getDataPages(format, node["emoji"], uriFactory)
+ val jumboPages: Map = getJumboPages(node["jumbomoji"])
val displayPages: List = mergeToDisplayPages(dataPages)
val metrics: EmojiMetrics = node["metrics"].toEmojiMetrics()
val densities: List = node["densities"].toDensityList()
- return ParsedEmojiData(metrics, densities, format, displayPages, dataPages, obsolete)
+ return ParsedEmojiData(metrics, densities, format, displayPages, dataPages, jumboPages, obsolete)
}
private fun getDataPages(format: String, emoji: JsonNode, uriFactory: UriFactory): List {
@@ -64,13 +66,33 @@ object EmojiJsonParser {
.toList()
}
+ private fun getJumboPages(jumbo: JsonNode?): Map {
+ if (jumbo != null) {
+ return jumbo.fields()
+ .asSequence()
+ .map { (page: String, node: JsonNode) ->
+ node.associate { it.textValue() to page }
+ }
+ .flatMap { it.entries }
+ .associateTo(HashMap(ESTIMATED_EMOJI_COUNT)) { it.key to it.value }
+ }
+ return emptyMap()
+ }
+
private fun createPage(pageName: String, format: String, page: JsonNode, uriFactory: UriFactory): EmojiPageModel {
val category = EmojiCategory.forKey(pageName.asCategoryKey())
val pageList = page.mapIndexed { i, data ->
if (data.size() == 0) {
throw IllegalStateException("Page index $pageName.$i had no data")
} else {
- Emoji(data.map { it.textValue().encodeAsUtf16() })
+ val variations: MutableList = mutableListOf()
+ val rawVariations: MutableList = mutableListOf()
+ data.forEach {
+ variations += it.textValue().encodeAsUtf16()
+ rawVariations += it.textValue()
+ }
+
+ Emoji(variations, rawVariations)
}
}
@@ -111,5 +133,6 @@ data class ParsedEmojiData(
override val format: String,
override val displayPages: List,
override val dataPages: List,
+ override val jumboPages: Map,
override val obsolete: List
) : EmojiData
diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiRemote.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiRemote.kt
index 7c1888222a..e3b75d3ade 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiRemote.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiRemote.kt
@@ -6,7 +6,7 @@ import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import java.io.IOException
-private const val VERSION_URL = "https://updates.signal.org/dynamic/android/emoji/version_v2.txt"
+private const val VERSION_URL = "https://updates.signal.org/dynamic/android/emoji/version_v3.txt"
private const val BASE_STATIC_BUCKET_URL = "https://updates.signal.org/static/android/emoji"
/**
@@ -93,3 +93,11 @@ class EmojiImageRequest(
) : EmojiRequest {
override val url: String = "$BASE_STATIC_BUCKET_URL/$version/$density/$name.$format"
}
+
+class EmojiFileRequest(
+ version: Int,
+ density: String,
+ name: String,
+) : EmojiRequest {
+ override val url: String = "$BASE_STATIC_BUCKET_URL/$version/$density/$name"
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt
index aa1032f454..91d8f09187 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt
@@ -62,8 +62,13 @@ class EmojiSource(
.filter { it.spriteUri != null }
.forEach { page ->
val emojiPage = emojiPageFactory(page.spriteUri!!)
- page.emoji.forEachIndexed { idx, emoji ->
- tree.add(emoji, EmojiDrawInfo(emojiPage, idx, emoji))
+
+ var overallIndex = 0
+ page.displayEmoji.forEach { emoji: Emoji ->
+ emoji.variations.forEachIndexed { variationIndex, variation ->
+ val raw = emoji.getRawVariation(variationIndex)
+ tree.add(variation, EmojiDrawInfo(emojiPage, overallIndex++, variation, raw, jumboPages[raw]))
+ }
}
}
@@ -142,6 +147,7 @@ interface EmojiData {
val format: String
val displayPages: List
val dataPages: List
+ val jumboPages: Map
val obsolete: List
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/JumboEmoji.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/JumboEmoji.kt
index f22231e5f5..4d5724f3ac 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/emoji/JumboEmoji.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/emoji/JumboEmoji.kt
@@ -3,16 +3,22 @@ package org.thoughtcrime.securesms.emoji
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
+import android.os.SystemClock
import androidx.annotation.MainThread
+import org.signal.core.util.ThreadUtil
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.logging.Log
+import org.thoughtcrime.securesms.components.emoji.parsing.EmojiDrawInfo
+import org.thoughtcrime.securesms.emoji.protos.JumbomojiPack
import org.thoughtcrime.securesms.jobmanager.impl.AutoDownloadEmojiConstraint
+import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.ListenableFutureTask
import org.thoughtcrime.securesms.util.SoftHashMap
import org.thoughtcrime.securesms.util.concurrent.SimpleTask
import java.io.IOException
import java.util.UUID
import java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeUnit
private val TAG = Log.tag(JumboEmoji::class.java)
@@ -21,30 +27,67 @@ private val TAG = Log.tag(JumboEmoji::class.java)
*/
object JumboEmoji {
+ private val executor = ThreadUtil.trace(SignalExecutors.newCachedSingleThreadExecutor("jumbo-emoji"))
+
const val MAX_JUMBOJI_COUNT = 5
+ private const val JUMBOMOJI_SUPPORTED_VERSION = 5
+
private val cache: MutableMap = SoftHashMap(16)
- private val tasks: MutableMap> = hashMapOf()
private val versionToFormat: MutableMap = hashMapOf()
+ private val downloadedJumbos: MutableSet = mutableSetOf()
+
+ private val networkCheckThrottle: Long = TimeUnit.MINUTES.toMillis(1)
+ private var lastNetworkCheck: Long = 0
+ private var canDownload: Boolean = false
+
+ @Volatile
+ private var currentVersion: Int = -1
+
+ @JvmStatic
+ @MainThread
+ fun updateCurrentVersion(context: Context) {
+ SignalExecutors.BOUNDED.execute {
+ val version: EmojiFiles.Version = EmojiFiles.Version.readVersion(context, true) ?: return@execute
+
+ if (EmojiFiles.getLatestEmojiData(context, version)?.format != null) {
+ currentVersion = version.version
+ ThreadUtil.runOnMain { downloadedJumbos.addAll(SignalStore.emojiValues().getJumboEmojiSheets(version.version)) }
+ }
+ }
+ }
+
+ @JvmStatic
+ @MainThread
+ fun canDownloadJumbo(context: Context): Boolean {
+ val now = SystemClock.elapsedRealtime()
+ if (now - networkCheckThrottle > lastNetworkCheck) {
+ canDownload = AutoDownloadEmojiConstraint.canAutoDownloadJumboEmoji(context)
+ lastNetworkCheck = now
+ }
+ return canDownload && currentVersion >= JUMBOMOJI_SUPPORTED_VERSION
+ }
+
+ @JvmStatic
+ @MainThread
+ fun hasJumboEmoji(drawInfo: EmojiDrawInfo): Boolean {
+ return downloadedJumbos.contains(drawInfo.jumboSheet)
+ }
@Suppress("FoldInitializerAndIfToElvis")
@JvmStatic
@MainThread
- fun loadJumboEmoji(context: Context, rawEmoji: String): LoadResult {
+ fun loadJumboEmoji(context: Context, drawInfo: EmojiDrawInfo): LoadResult {
val applicationContext: Context = context.applicationContext
- val name: String = "jumbo/$rawEmoji"
- val bitmap: Bitmap? = cache[name]
- val task: ListenableFutureTask? = tasks[name]
+ val archiveName = "jumbo/${drawInfo.jumboSheet}.proto"
+ val emojiName: String = drawInfo.rawEmoji!!
+ val bitmap: Bitmap? = cache[emojiName]
if (bitmap != null) {
return LoadResult.Immediate(bitmap)
}
- if (task != null) {
- return LoadResult.Async(task)
- }
-
val newTask = ListenableFutureTask {
val version: EmojiFiles.Version? = EmojiFiles.Version.readVersion(applicationContext, true)
if (version == null) {
@@ -59,38 +102,50 @@ object JumboEmoji {
throw NoVersionData()
}
+ currentVersion = version.version
+
var jumbos: EmojiFiles.JumboCollection = EmojiFiles.JumboCollection.read(applicationContext, version)
- val uuid = jumbos.getUUIDForName(name)
+ val uuid = jumbos.getUUIDForName(emojiName)
if (uuid == null) {
- if (!AutoDownloadEmojiConstraint.canAutoDownloadEmoji(applicationContext)) {
+ if (!AutoDownloadEmojiConstraint.canAutoDownloadJumboEmoji(applicationContext)) {
throw CannotAutoDownload()
}
Log.i(TAG, "No file for emoji, downloading jumbo")
- val emojiFilesName: EmojiFiles.Name = EmojiDownloader.downloadAndVerifyImageFromRemote(applicationContext, version, version.density, name, format)
- jumbos = EmojiFiles.JumboCollection.append(applicationContext, jumbos, emojiFilesName)
+ EmojiDownloader.streamFileFromRemote(version, version.density, archiveName) { stream ->
+ stream.use { remote ->
+ val jumbomojiPack = JumbomojiPack.parseFrom(remote)
+
+ jumbomojiPack.itemsList.forEach { jumbo ->
+ val emojiNameEntry = EmojiFiles.Name(jumbo.name, UUID.randomUUID())
+ val outputStream = EmojiFiles.openForWriting(applicationContext, version, emojiNameEntry.uuid)
+
+ outputStream.use { jumbo.image.writeTo(it) }
+
+ jumbos = EmojiFiles.JumboCollection.append(applicationContext, jumbos, emojiNameEntry)
+ }
+ }
+ }
+
+ SignalStore.emojiValues().addJumboEmojiSheet(version.version, drawInfo.jumboSheet)
}
- val inputStream = EmojiFiles.openForReadingJumbo(applicationContext, version, jumbos, name)
- inputStream.use { BitmapFactory.decodeStream(it, null, BitmapFactory.Options()) }
+ EmojiFiles.openForReadingJumbo(applicationContext, version, jumbos, emojiName).use { BitmapFactory.decodeStream(it, null, BitmapFactory.Options()) }
}
- tasks[name] = newTask
-
- SimpleTask.run(SignalExecutors.SERIAL, newTask::run) {
+ SimpleTask.run(executor, newTask::run) {
try {
val newBitmap: Bitmap? = newTask.get()
if (newBitmap == null) {
Log.w(TAG, "Failed to load jumbo emoji")
} else {
- cache[name] = newBitmap
+ cache[emojiName] = newBitmap
+ downloadedJumbos.add(drawInfo.jumboSheet!!)
}
} catch (e: ExecutionException) {
- Log.d(TAG, "Failed to load jumbo emoji", e.cause)
- } finally {
- tasks.remove(name)
+ // do nothing, emoji provider will log the exception
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/AutoDownloadEmojiConstraint.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/AutoDownloadEmojiConstraint.java
index 94ff035ab4..a86927598a 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/AutoDownloadEmojiConstraint.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/AutoDownloadEmojiConstraint.java
@@ -54,11 +54,15 @@ public void applyToJobInfo(@NonNull JobInfo.Builder jobInfoBuilder) {
}
public static boolean canAutoDownloadEmoji(@NonNull Context context) {
- return getAllowedAutoDownloadTypes(context).contains(IMAGE_TYPE);
+ return getAllowedAutoDownloadTypes(context, true).contains(IMAGE_TYPE);
}
- private static @NonNull Set getAllowedAutoDownloadTypes(@NonNull Context context) {
- if (NetworkUtil.isConnectedWifi(context)) return Collections.singleton(IMAGE_TYPE);
+ public static boolean canAutoDownloadJumboEmoji(@NonNull Context context) {
+ return getAllowedAutoDownloadTypes(context, false).contains(IMAGE_TYPE);
+ }
+
+ private static @NonNull Set getAllowedAutoDownloadTypes(@NonNull Context context, boolean forceWifi) {
+ if (NetworkUtil.isConnectedWifi(context)) return forceWifi ? Collections.singleton(IMAGE_TYPE) : TextSecurePreferences.getWifiMediaDownloadAllowed(context);
else if (NetworkUtil.isConnectedRoaming(context)) return TextSecurePreferences.getRoamingMediaDownloadAllowed(context);
else if (NetworkUtil.isConnectedMobile(context)) return TextSecurePreferences.getMobileMediaDownloadAllowed(context);
else return Collections.emptySet();
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/DownloadLatestEmojiDataJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/DownloadLatestEmojiDataJob.java
index b7e2a81742..e9ca56468e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/DownloadLatestEmojiDataJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/DownloadLatestEmojiDataJob.java
@@ -21,6 +21,7 @@
import org.thoughtcrime.securesms.emoji.EmojiPageCache;
import org.thoughtcrime.securesms.emoji.EmojiRemote;
import org.thoughtcrime.securesms.emoji.EmojiSource;
+import org.thoughtcrime.securesms.emoji.JumboEmoji;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.AutoDownloadEmojiConstraint;
@@ -158,6 +159,7 @@ protected void onRun() throws Exception {
clearOldEmojiData(context, targetVersion);
markComplete(targetVersion);
EmojiSource.refresh();
+ JumboEmoji.updateCurrentVersion(context);
} else {
Log.d(TAG, "Server has an older version than we do. Skipping.");
}
@@ -359,6 +361,10 @@ private static void clearOldEmojiData(@NonNull Context context, @Nullable EmojiF
.forEach(FileUtils::deleteDirectory);
EmojiPageCache.INSTANCE.clear();
+
+ if (version != null) {
+ SignalStore.emojiValues().clearJumboEmojiSheets(version.getVersion());
+ }
}
public static final class Factory implements Job.Factory {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java
index f4b6658cfe..cb50884e21 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java
@@ -37,6 +37,7 @@
import org.thoughtcrime.securesms.migrations.DatabaseMigrationJob;
import org.thoughtcrime.securesms.migrations.DeleteDeprecatedLogsMigrationJob;
import org.thoughtcrime.securesms.migrations.DirectoryRefreshMigrationJob;
+import org.thoughtcrime.securesms.migrations.EmojiDownloadMigrationJob;
import org.thoughtcrime.securesms.migrations.KbsEnclaveMigrationJob;
import org.thoughtcrime.securesms.migrations.MigrationCompleteJob;
import org.thoughtcrime.securesms.migrations.PassingMigrationJob;
@@ -168,6 +169,7 @@ public static Map getJobFactories(@NonNull Application appl
put(DatabaseMigrationJob.KEY, new DatabaseMigrationJob.Factory());
put(DeleteDeprecatedLogsMigrationJob.KEY, new DeleteDeprecatedLogsMigrationJob.Factory());
put(DirectoryRefreshMigrationJob.KEY, new DirectoryRefreshMigrationJob.Factory());
+ put(EmojiDownloadMigrationJob.KEY, new EmojiDownloadMigrationJob.Factory());
put(KbsEnclaveMigrationJob.KEY, new KbsEnclaveMigrationJob.Factory());
put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory());
put(PinOptOutMigration.KEY, new PinOptOutMigration.Factory());
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java
index c947dd7e51..2941e45052 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java
@@ -65,16 +65,17 @@ public static ProfileKeySendJob create(@NonNull Context context, long threadId,
if (queueLimits) {
return new ProfileKeySendJob(new Parameters.Builder()
- .setQueue(conversationRecipient.getId().toQueueKey())
+ .setQueue("ProfileKeySendJob_" + conversationRecipient.getId().toQueueKey())
+ .setMaxInstancesForQueue(1)
.addConstraint(NetworkConstraint.KEY)
+ .addConstraint(DecryptionsDrainedConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED)
.build(), threadId, recipients);
} else {
return new ProfileKeySendJob(new Parameters.Builder()
- .setQueue("ProfileKeySendJob_" + conversationRecipient.getId().toQueueKey())
+ .setQueue(conversationRecipient.getId().toQueueKey())
.addConstraint(NetworkConstraint.KEY)
- .addConstraint(DecryptionsDrainedConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED)
.build(), threadId, recipients);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/EmojiValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/EmojiValues.java
index cbf9018145..cd23edda63 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/EmojiValues.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/EmojiValues.java
@@ -11,7 +11,9 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
public class EmojiValues extends SignalStoreValues {
@@ -28,6 +30,7 @@ public class EmojiValues extends SignalStoreValues {
private static final String SEARCH_VERSION = PREFIX + "search_version";
private static final String SEARCH_LANGUAGE = PREFIX + "search_language";
private static final String LAST_SEARCH_CHECK = PREFIX + "last_search_check";
+ private static final String JUMBO_EMOJI_DOWNLOAD = PREFIX + "jumbo_emoji_v";
public static final String NO_LANGUAGE = "NO_LANGUAGE";
@@ -131,4 +134,22 @@ public long getLastSearchIndexCheck() {
public void setLastSearchIndexCheck(long time) {
putLong(LAST_SEARCH_CHECK, time);
}
+
+ public void addJumboEmojiSheet(int version, String sheet) {
+ Set sheets = getJumboEmojiSheets(version);
+ sheets.add(sheet);
+ getStore().beginWrite()
+ .putString(JUMBO_EMOJI_DOWNLOAD + version, Util.join(sheets, ","))
+ .apply();
+ }
+
+ public HashSet getJumboEmojiSheets(int version) {
+ return new HashSet<>(Arrays.asList(getStore().getString(JUMBO_EMOJI_DOWNLOAD + version, "").split(",")));
+ }
+
+ public void clearJumboEmojiSheets(int version) {
+ getStore().beginWrite()
+ .remove(JUMBO_EMOJI_DOWNLOAD + version)
+ .apply();
+ }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java
index 426c5c0d64..21c41d4558 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java
@@ -285,7 +285,8 @@ private void handleMessage(@NonNull SignalServiceContent content, long timestamp
.enqueue();
} else if (!threadRecipient.isGroup()) {
Log.i(TAG, "Message was to a 1:1. Ensuring this user has our profile key.");
- ApplicationDependencies.getJobManager().startChain(new RefreshAttributesJob(false))
+ ApplicationDependencies.getJobManager()
+ .startChain(new RefreshAttributesJob(false))
.then(ProfileKeySendJob.create(context, SignalDatabase.threads().getOrCreateThreadIdFor(threadRecipient), true))
.enqueue();
}
@@ -1722,13 +1723,12 @@ private void handleProfileKey(@NonNull SignalServiceContent content,
@NonNull byte[] messageProfileKeyBytes,
@NonNull Recipient senderRecipient)
{
- log(content.getTimestamp(), "Profile key.");
-
RecipientDatabase database = SignalDatabase.recipients();
ProfileKey messageProfileKey = ProfileKeyUtil.profileKeyOrNull(messageProfileKeyBytes);
if (messageProfileKey != null) {
if (database.setProfileKey(senderRecipient.getId(), messageProfileKey)) {
+ log(content.getTimestamp(), "Profile key on message from " + senderRecipient.getId() + " didn't match our local store. It has been updated.");
ApplicationDependencies.getJobManager().add(RetrieveProfileJob.forRecipient(senderRecipient.getId()));
}
} else {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java
index db35a8cf50..3fad1d2d42 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java
@@ -25,7 +25,7 @@
* Manages application-level migrations.
*
* Migrations can be slotted to occur based on changes in the canonical version code
- * (see {@link Util#getCanonicalVersionCode()}).
+ * (see {@link Util#getSignalCanonicalVersionCode()}).
*
* Migrations are performed via {@link MigrationJob}s. These jobs are durable and are run before any
* other job, allowing you to schedule safe migrations. Furthermore, you may specify that a
@@ -84,9 +84,11 @@ static final class Version {
//static final int CHANGE_NUMBER_CAPABILITY_3 = 49;
static final int PNI = 50;
static final int FIX_DEPRECATION = 51; // Only used to trigger clearing the 'client deprecated' flag
+ //static final int JUMBOMOJI_DOWNLOAD = 52;
+ static final int FIX_EMOJI_QUALITY = 53;
}
- public static final int CURRENT_VERSION = 51;
+ public static final int CURRENT_VERSION = 53;
/**
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
@@ -355,6 +357,10 @@ private static LinkedHashMap getMigrationJobs(@NonNull Co
jobs.put(Version.PNI, new PniMigrationJob());
}
+ if (lastSeenVersion < Version.FIX_EMOJI_QUALITY) {
+ jobs.put(Version.FIX_EMOJI_QUALITY, new EmojiDownloadMigrationJob());
+ }
+
return jobs;
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/EmojiDownloadMigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/EmojiDownloadMigrationJob.java
new file mode 100644
index 0000000000..d9bbebf78c
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/EmojiDownloadMigrationJob.java
@@ -0,0 +1,51 @@
+package org.thoughtcrime.securesms.migrations;
+
+import androidx.annotation.NonNull;
+
+import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
+import org.thoughtcrime.securesms.jobmanager.Data;
+import org.thoughtcrime.securesms.jobmanager.Job;
+import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob;
+
+/**
+ * Schedules a emoji download job to get the latest version.
+ */
+public final class EmojiDownloadMigrationJob extends MigrationJob {
+
+ public static final String KEY = "EmojiDownloadMigrationJob";
+
+ EmojiDownloadMigrationJob() {
+ this(new Parameters.Builder().build());
+ }
+
+ private EmojiDownloadMigrationJob(@NonNull Parameters parameters) {
+ super(parameters);
+ }
+
+ @Override
+ public boolean isUiBlocking() {
+ return false;
+ }
+
+ @Override
+ public @NonNull String getFactoryKey() {
+ return KEY;
+ }
+
+ @Override
+ public void performMigration() {
+ ApplicationDependencies.getJobManager().add(new DownloadLatestEmojiDataJob(false));
+ }
+
+ @Override
+ boolean shouldRetry(@NonNull Exception e) {
+ return false;
+ }
+
+ public static class Factory implements Job.Factory {
+ @Override
+ public @NonNull EmojiDownloadMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
+ return new EmojiDownloadMigrationJob(parameters);
+ }
+ }
+}
diff --git a/app/src/main/proto/Emoji.proto b/app/src/main/proto/Emoji.proto
new file mode 100644
index 0000000000..8cd0006227
--- /dev/null
+++ b/app/src/main/proto/Emoji.proto
@@ -0,0 +1,15 @@
+syntax = "proto3";
+
+package signal;
+
+option java_package = "org.thoughtcrime.securesms.emoji.protos";
+option java_multiple_files = true;
+
+message JumbomojiPack {
+ repeated JumbomojiItem items = 1;
+}
+
+message JumbomojiItem {
+ string name = 1;
+ bytes image = 2;
+}
diff --git a/app/src/main/res/drawable/ic_video_solid_24_tinted.xml b/app/src/main/res/drawable/ic_video_solid_24_tinted.xml
index d48cd6d77b..95ef92483c 100644
--- a/app/src/main/res/drawable/ic_video_solid_24_tinted.xml
+++ b/app/src/main/res/drawable/ic_video_solid_24_tinted.xml
@@ -5,5 +5,5 @@
android:viewportHeight="24">
+ android:pathData="m23,8.06v7.88c0.0018,0.7313 -0.7563,1.2172 -1.42,0.91L18,15.2V8.8L21.58,7.15C22.2437,6.8428 23.0018,7.3287 23,8.06ZM16.5,17V7c0,-1.6569 -1.3431,-3 -3,-3H4C2.3431,4 1,5.3431 1,7v10c0,1.6569 1.3431,3 3,3h9.5c1.6569,0 3,-1.3431 3,-3z"/>
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
index 3720ad0c18..b3c5a76fd0 100644
--- a/app/src/main/res/values-bg/strings.xml
+++ b/app/src/main/res/values-bg/strings.xml
@@ -778,6 +778,7 @@
Изглежда нямате приложения, с които да можете да сподялите.
Научете повече
+ Прочети повече
Съобщението не е намерено
Съобщение от %1$s
@@ -1046,6 +1047,7 @@
Отбелязахте числото си за сигурност с %s като непотвърдени
Отбелязахте числот си за сугорност с %s като непотвърдени от друго устройство
Съобщение от %s не успя да бъде доставено
+ %1$s смени номера си с нов номер.
%1$s започна групово разговор · %2$s
%1$s е в групов разговор · %2$s
@@ -1301,6 +1303,7 @@
Условия и Политика за поверителност
Направихте прекалено много опити за регистриране на този номер. Моля, опитайте отново по-късно.
Неуспешно свързване с услугата. Моля, проверете мрежовата връзка и опитайте отново.
+ Нестандартен формат на номера
- Сега сте %d стъпка от изпращането на дебъг лога.
- Сега сте на %d стъпки от изпращането на дебъг лог.
@@ -1841,6 +1844,9 @@
Приплъзнете надолу, за да отговорите
Няколко проблема изискват вашето внимание.
+ Изпратено:
+ Получено:
+ Чрез:
В очакване
Изпрати на
@@ -2821,6 +2827,7 @@
Промени Номер
+ Промени номер
Редакция на номер
@@ -3021,7 +3028,13 @@
SMS
. %1$s
+ Добави съобщение
+
+ - Съобщението е изпратено
+ - Съобщенията са изпратени
+
+ Добави съобщение
Отказ
Мъгли
Върни
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index 348bd23b5e..78ae2c27fd 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -3,14 +3,14 @@
Ja
Nej
Slet
- Vent venligst…
+ Vent venligst …
Gem
Egen note
Ny besked
- Molly opdaterer…
+ Molly opdaterer …
For øjeblikket: %s
Du har endnu ikke indtastet en adgangssætning!
@@ -18,7 +18,7 @@
Dette låser permanent op for Molly og besked-notifikationer.
Deaktiver
Afregistrering
- Afregistrerer fra Molly-beskeder og opkald…
+ Afregistrerer fra Molly-beskeder og opkald …
Deaktiver Molly-beskeder og opkald?
Deaktiver Molly-beskeder og opkald ved at afregistrere fra serveren. Det er nødvendigt at registrere telefonnummeret igen for at bruge det fremadrettet.
Fejl ved oprettelse af forbindelse til serveren
@@ -68,9 +68,9 @@
Molly kræver tilladelse til at tilgå din placering, for at kunne vedhæfte placeringer, hvilket er blevet nægtet. Gå venligst til appindstillinger, vælg \"Tilladelser\" og tilvælg \"Placering\".
Uploader mediefil…
- Komprimerer video…
+ Komprimerer video …
- Tjekker efter beskeder…
+ Tjekker efter beskeder …
Blokerede brugere
Tilføj blokeret bruger
@@ -179,7 +179,7 @@
fra %s
til %s
Læs mere
- Download mere
+ Hent mere
Afventer
Denne besked blev slettet.
Du slettede denne besked.
@@ -271,12 +271,12 @@
- Gemmer vedhæftning…
- Gemmer %1$d vedhæftninger i lagerplads…
- Sender…
+ Afventer …
Data (Signal)
MMS
SMS
Sletter
- Sletter beskeder…
+ Sletter beskeder …
Slet for mig
Slet for alle
Denne besked slettes for alle i samtalen, hvis de har den seneste version af Signal. De kan se at du har slettet en besked.
@@ -308,7 +308,7 @@
- Dette sletter %1$d valgte samtaler permanent
Sletter
- Sletter valgte samtaler…
+ Sletter valgte samtaler …
- Samtale arkiveret
- %d samtaler arkiveret
@@ -409,8 +409,8 @@
Deaktiver
For at gendanne fra sikkerhedskopi skal du installere Molly igen. Åbn appen og tryk på \"Gendan sikkerhedskopi.\" Vælg derefter en sikkerhedskopifil. %1$s
Læs mere
- I gang…
- %1$d indtil videre…
+ I gang …
+ %1$d indtil videre …
%1$s%% indtil videre…
Molly kræver tilladelse til at tilgå ekstern lagerplads for at oprette sikkerhedskopier, hvilket er blevet nægtet. Gå venligst til appindstillinger, vælg \"Tilladelser\" og tilvælg \"Lagerplads\".
@@ -438,7 +438,7 @@
Enheden vil ikke længere være i stand til at sende og modtage beskeder, hvis den frakobles.
Forbindelse til netværk fejlede
Prøv igen
- Fjerner forbindelse til enhed…
+ Fjerner forbindelse til enhed …
Frakobler enhed
Netværksfejl!
@@ -687,7 +687,7 @@
Dette er en usikker MMS-gruppe. Inviter dine kontakter til Signal for at skrive og tale privat.
Inviter nu
mere
- Tilføj gruppebeskrivelse…
+ Tilføj gruppebeskrivelse …
Underret mig ved omtaler
Vil du modtage notifikationer, når du omtales i ignorerede samtaler?
@@ -789,9 +789,9 @@
Del
Del med kontakter
- Del via…
+ Del via …
Afbryd
- Sender…
+ Sender …
Invitationer sendt!
Inviter til Molly
Send SMS (%d)
@@ -827,9 +827,9 @@
- Dette vil slette alle %1$d markerede filer permanent. Alle beskedtekster associeret med disse filer vil også blive slettet
Sletter
- Sletter beskeder…
+ Sletter beskeder …
Vælg alle
- Samler vedhæftninger…
+ Samler vedhæftninger …
Sortér efter
Nyeste
Ældste
@@ -889,13 +889,13 @@
Ikke nu
Multimediebesked
- Downloader MMS…
- MMS-besked kunne ikke downloades, tryk for at prøve igen
+ Henter MMS-besked …
+ MMS-besked kunne ikke hentes, tryk for at prøve igen
Send til %s
Åbn kamera
- Tilføj en billedtekst…
+ Tilføj en billedtekst …
Et element blev fjernet, fordi det oversteg max. grænsen
Et element blev fjernet, fordi det havde en ukendt type
Et element blev fjernet, fordi det overskred størrelsesgrænsen eller havde en ukendt type
@@ -903,7 +903,7 @@
Besked til %s
Besked
Vælg modtagere
- Molly kræver tilladelse til at tilgå dine kontakter for at kunne vise dem.
+ Molly har brug for tilladelse til at tilgå dine kontakter for at kunne vise dem.
Molly kræver tilladelse til at tilgå dine kontakter, hvilket er blevet nægtet. Gå venligst til appindstillinger, vælg \"Tilladelser\" og tilvælg \"Kontakter\".
- Du kan ikke dele flere end %d fil.
@@ -1149,7 +1149,7 @@
\n• Send beskeder i dit navn
Tilføjer enhed
- Forbinder ny enhed…
+ Forbinder ny enhed …
Enhed godkendt!
Ingen enhed fundet
Netværksfejl.
@@ -1203,7 +1203,7 @@
Opret ny pinkode
Advarsel
- Hvis du deaktiverer pinkode mister du alle data, når du omregistrerer Signal, medmindre du sikkerhedskopierer og gendanner manuelt. Du kan ikke aktivere registreringslås, mens pinkoden er deaktiveret.
+ Hvis du deaktiverer pinkode mister du alle data, når du omregistrerer Signal. Medmindre du sikkerhedskopierer og gendanner manuelt. Du kan ikke aktivere registreringslås, mens pinkoden er deaktiveret.
Deaktiver pinkode
Bedøm appen
@@ -1229,9 +1229,9 @@
Unavngivet gruppe
- Besvarer…
- Afslutter opkald…
- Ringer…
+ Besvarer …
+ Afslutter opkald …
+ Ringer …
Optaget
Modtager utilgængelig
Netværksfejl!
@@ -1242,7 +1242,7 @@
Tryk her for at starte video
For at ringe til %1$s kræver Molly tilladelse til at tilgå din kamera
Molly %1$s
- Ringer op…
+ Ringer op …
Gruppe er for stor til at ringe til alle deltagerne.
Signal-opkald
@@ -1253,8 +1253,8 @@
Det maksimale antal på %1$d deltagere er nået for dette opkald. Prøv igen senere.
Vis deltagere
Din video er slået fra
- Genopretter forbindelse…
- Forbinder…
+ Genopretter forbindelse …
+ Forbinder …
Afbrudt
Signal vil ringe til %1$s
Signal vil ringe til %1$s og %2$s
@@ -1514,7 +1514,7 @@ nummer (%s) er ugyldigt
Din kontakt bruger en gammel version af Signal. Bed dem om at opdatere før verifikation af dit sikkerhedsnummer.
Din kontakt bruger en nyere version af Signal med en inkompatibel QR-kode. Opdatér venligst for at sammenligne.
Den skannede QR-kode er ikke korrekt formateret som verifikationskode for sikkerhedsnummer. Prøv at skanne igen.
- Del sikkerhedsnummer via…
+ Del sikkerhedsnummer via …
Vores Signal-sikkerhedsnummer:
Det lader til at du ikke har nogen apps at dele til.
Sikkerhedsnummer til sammenligning findes ikke i udklipsholderen
@@ -1531,7 +1531,7 @@ nummer (%s) er ugyldigt
Slå notifikationer fra
- Importerer…
+ Importerer …
Importerer tekstbeskeder
Import afsluttet
Import af systemdatabase er afsluttet
@@ -1657,7 +1657,7 @@ nummer (%s) er ugyldigt
Vis telefontaster
Ingen kontakter.
- Indlæser kontakter…
+ Indlæser kontakter …
Kontaktbillede
@@ -1725,7 +1725,7 @@ nummer (%s) er ugyldigt
Udløbstiden for forsvindende besked indstilles til %1$s, når du sender beskeder til dem.
Afspil … Pause
- Download
+ Hent
Lyd
Video
@@ -1768,7 +1768,7 @@ nummer (%s) er ugyldigt
For at modtage opkaldsnotifikationer, tryk på Indstillinger og aktiver notifikationer og sørg for, at lyd og pop-op er aktiveret.
For at modtage opkaldsnotifikationer, tryk på Indstillinger og aktiver baggrundsaktivitet i \"Batteri\"-indstillinger.
- Indlæser lande…
+ Indlæser lande …
Søg
Ingen matchende lande
@@ -1864,7 +1864,7 @@ nummer (%s) er ugyldigt
Gruppebeskrivelser vil være synlige for medlemmer af denne gruppe og personer, der er blevet inviteret.
Om
- Skriv et par ord om dig selv…
+ Skriv et par ord om dig selv …
%1$d/%2$d
Tal frit
Krypteret
@@ -1892,7 +1892,7 @@ nummer (%s) er ugyldigt
Tryk for at skanne
Vellykket match
Kunne ikke verificere sikkerhedsnummer
- Henter…
+ Indlæser …
Markér som verificeret
Fjern verifikation
@@ -2084,7 +2084,7 @@ nummer (%s) er ugyldigt
Samtalefarve & baggrund
Deaktiver pinkode
Aktiver pinkode
- Hvis du deaktiverer pinkoden mister du alle data når du omregistrerer Signal, medmindre du sikkerhedskopierer og gendanner manuelt. Du kan ikke aktivere registreringslås, mens pinkoden er deaktiveret.
+ Hvis du deaktiverer pinkoden mister du alle data, når du omregistrerer Signal. Medmindre du sikkerhedskopierer og gendanner manuelt. Du kan ikke aktivere registreringslås, mens pinkoden er deaktiveret.
Pinkoder sørger for at information lagret hos Signal er krypteret, så kun du kan få adgang til den. Din profil, indstillinger og kontakter gendannes når du geninstallerer. Du har ikke brug for din pinkode for at åbne appen.
Systemstandard
Sprog
@@ -2106,7 +2106,7 @@ nummer (%s) er ugyldigt
Ved anvendelse af mobildata
Ved brug af WiFi
Ved roaming
- Automatisk download af mediefiler
+ Automatisk hentning af mediefiler
Beskedhistorik
Brug af lagerplads
Billeder
@@ -2133,7 +2133,7 @@ nummer (%s) er ugyldigt
Deaktiver Signals indbyggede emoji-understøttelse
Videresend alle opkald gennem Signal-serveren, for at undgå at afsløre din IP-adresse over for din kontakt. Opkaldskvaliteten vil blive forringet.
Videresend altid opkald
- Hvem kan…
+ Hvem kan …
App-adgang
Kommunikation
Samtaler
@@ -2188,7 +2188,7 @@ nummer (%s) er ugyldigt
Nu introduceres betalinger (Beta)
Brug Molly til at sende og modtage MobileCoin, en ny privatlivsfokuseret digital valuta. Aktivér for at komme i gang.
Aktiver Betalinger
- Aktiverer betalinger…
+ Aktiverer betalinger …
Gendan betalingskonto
Ingen nylig aktivitet endnu
Afventende anmodninger
@@ -2233,8 +2233,8 @@ nummer (%s) er ugyldigt
Detaljer
Status
- Indsender betaling…
- Bearbejder betaling…
+ Indsender betaling …
+ Bearbejder betaling …
Betaling gennemført
Betaling fejlede
Netværksgebyr
@@ -2298,8 +2298,8 @@ nummer (%s) er ugyldigt
Til
Samlet beløb
Saldo: %1$s
- Indsender betaling…
- Bearbejder betaling…
+ Indsender betaling …
+ Bearbejder betaling …
Betaling gennemført
Betaling fejlede
Behandling af betaling vil fortsætte
@@ -2315,7 +2315,7 @@ nummer (%s) er ugyldigt
- Ny besked til…
+ Ny besked til …
Blokér bruger
Føj til gruppe
@@ -2484,7 +2484,7 @@ nummer (%s) er ugyldigt
Din pinkode blev ikke gemt. Vi vil minde dig om at oprette en pinkode senere.
Pinkode oprettet.
Indtast din pinkode igen
- Opretter pinkode…
+ Opretter pinkode …
Nu introduceres pinkoder
Pinkoder sørger for at information opbevaret hos Signal er krypteret, så kun du har adgang til den. Din profil, indstillinger og kontakter vil blive gendannet når du geninstallerer. Du behøver ikke din pinkode for at åbne appen.
@@ -2547,8 +2547,8 @@ nummer (%s) er ugyldigt
Opret pinkode
Transport-ikon
- Henter…
- Forbinder…
+ Indlæser …
+ Forbinder …
Tilladelse påkrævet
Signal kræver tilladelse til at tilgå dine SMS\'er for at kunne sende SMS\'er, hvilket er blevet nægtet. Gå venligst til appindstillinger, vælg \"Tilladelser\" og tilvælg \"SMS\".
Fortsæt
@@ -2574,8 +2574,8 @@ nummer (%s) er ugyldigt
Gendan
Kan ikke importere sikkerhedskopier fra nyere versioner af Signal
Forkert adgangssætning til sikkerhedskopi
- Tjekker…
- %d beskeder indtil videre…
+ Tjekker …
+ %d beskeder indtil videre …
Gendan fra sikkerhedskopi?
Gendan dine beskeder og mediefiler fra en lokal sikkerhedskopi. Hvis du ikke gendanner nu kan det ikke gøres senere.
Størrelse på sikkerhedskopi: %s
@@ -2594,7 +2594,7 @@ nummer (%s) er ugyldigt
Verificér
Du indtastede den korrekte adgangssætning til sikkerhedskopi
Forkert adgangssætning
- Opretter Molly-sikkerhedskopi…
+ Opretter Molly-sikkerhedskopi …
Sikkerhedskopiering mislykkedes
Din mappe med sikkerhedskopier er blevet slettet eller flyttet.
Din sikkerhedskopifil er for stor til at kunne gemmes på denne lagerenhed.
@@ -2662,9 +2662,9 @@ nummer (%s) er ugyldigt
3.
Tryk på \"Overfør konto\" og derefter \"Fortsæt\" på begge enheder
- Forbereder forbindelse til den gamle Android-enhed…
+ Forbereder forbindelse til den gamle Android-enhed …
Tager et øjeblik. Vil snart være klar
- Venter på, at den gamle Android-enhed forbinder…
+ Venter på, at den gamle Android-enhed forbinder …
Molly kræver placeringstilladelsen for at opspore og forbinde din gamle Android-enhed.
Molly kræver placeringstjenester aktiveret for at opspore og forbinde med din gamle Android-enhed.
Molly kræver Wi-Fi til for at opspore og forbinde med din gamle Android-enhed. Wi-Fi skal være tændt, men det behøver ikke at være forbundet til et Wi-Fi-netværk.
@@ -2672,7 +2672,7 @@ nummer (%s) er ugyldigt
Gendan en sikkerhedskopi
Der opstod en uventet fejl under forbindelsesforsøg til din gamle Android-enhed.
- Søger efter ny Android-enhed…
+ Søger efter ny Android-enhed …
Molly kræver placeringstilladelsen for at opspore og forbinde med din nye Android-enhed.
Molly kræver placeringstjenester aktiveret for at opspore og forbinde med din nye Android-enhed.
Molly kræver Wi-Fi til for at opspore og forbinde med din nye Android-enhed. Wi-Fi skal være tændt, men det behøver ikke at være forbundet til et Wi-Fi-netværk.
@@ -2709,13 +2709,13 @@ nummer (%s) er ugyldigt
Prøv igen
Venter på anden enhed
Tryk på Fortsæt på din anden enhed for at starte overførslen.
- Tryk på Fortsæt på din anden enhed…
+ Tryk på Fortsæt på din anden enhed …
Der kan ikke overføres fra en nyere version af Signal
Overfører data
Hold begge enheder tæt ved hinanden. Sluk ikke for enhederne, og hold Molly åbent. Overførsler er end-to-end krypteret
- %1$d beskeder indtil videre…
+ %1$d beskeder indtil videre …
%1$s%% af beskeder indtil videre…
Annullér
@@ -2747,12 +2747,12 @@ nummer (%s) er ugyldigt
Fortsæt registreringen
Kontooverførsel
- Forbereder forbindelse til den anden Android-enhed…
- Forbereder forbindelse til den anden Android-enhed…
- Søger efter din anden Android-enhed…
- Forbinder til den anden Android-enhed…
+ Forbereder forbindelse til den anden Android-enhed …
+ Forbereder forbindelse til den anden Android-enhed …
+ Søger efter din anden Android-enhed …
+ Forbinder til den anden Android-enhed …
Bekræftelse er påkrævet
- Overfører kontoen…
+ Overfører konto …
Gennemfør registreringen på din nye enhed
Din Signal-konto er blevet overført til din nye enhed, men du skal gennemføre registreringen på den for at fortsætte. Signal vil være inaktivt på denne enhed.
@@ -2868,9 +2868,9 @@ nummer (%s) er ugyldigt
Lokale data kunne ikke slettes. Du kan rydde det manuelt i systemets applikationsindstillinger.
Åbn appindstillinger
- Forlader grupper…
+ Forlader grupper …
- Sletter konto…
+ Sletter konto …
Afhængigt af antallet af grupper, du er med i, kan dette tage et par minutter
@@ -3069,7 +3069,7 @@ nummer (%s) er ugyldigt
Opdatér pinkode
Behold gammel pinkode?
- Det ser ud til, at du forsøgte at ændre dit nummer, men vi kunne ikke fastslå, om det lykkedes.\n\nTjekker igen nu…
+ Det ser ud til, at du forsøgte at ændre dit nummer, men vi kunne ikke fastslå, om det lykkedes.\n\nTjekker igen nu …
Ændringsstatus bekræftet
Dit nummer er blevet bekræftet som %1$s. Hvis dette ikke er dit nye nummer, bedes du genstarte processen med at ændre nummer igen.
Ændringsstatus ubekræftet
@@ -3085,7 +3085,7 @@ nummer (%s) er ugyldigt
Beskeder
Opkald
- Underret når…
+ Underret når …
Kontakt begynder at bruge Signal
Notifikationsprofiler
@@ -3410,7 +3410,7 @@ nummer (%s) er ugyldigt
Tilføj et Signal Boost
%1$s/måned
Fornyes %1$s
- Behandler transaktion…
+ Behandler transaktion …
Kunne ikke tilføje emblem. %1$s
Kontakt venligst support.
@@ -3429,7 +3429,7 @@ nummer (%s) er ugyldigt
Kontakt venligst support for at få mere information.
Kontakt Support
Opnå ret til et %1$s-emblem
- Bearbejder betaling…
+ Bearbejder betaling …
Fejl ved behandling af betaling
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 3fdedb7162..38c6e4a215 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -1980,7 +1980,7 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del
Durante 8 horas
Durante un día
Durante 7 días
- Siempre
+ Para siempre
Ajustes predeterminados
Activado
Desactivado
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index 04189488c3..6ad5f36175 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -332,15 +332,15 @@
- برداشتن سنجاق
- - برداشتن سنجاق
+ - حذف سنجاق
- بستن
- - بستن
+ - بیصدا
- باز کردن
- - باز کردن
+ - صدادار
انتخاب
@@ -361,9 +361,9 @@
- %d انتخاب شد
- پروفایل اعلان
+ پروفایل اعلانات
- پروفایل اعلان خود را اینجا خاموش یا روشن کنید.
+ پروفایل اعلانات خود را اینجا روشن یا خاموش کنید.
%1$s روشن
@@ -409,7 +409,7 @@
خاموش
برای بازگردانی یک پشتیبان، یک نسخهٔ جدید سیگنال را نصب کنید. برنامه را باز کرده و روی «بازگردانی پشتیبان» ضربه بزنید، سپس فایل پشتیبان را مشخص کنید. %1$s
بیشتر یاد بگیرید
- در حال پیشرفت…
+ در حال پیشروی…
%1$d تا بحال…
%1$s%% تا کنون…
@@ -495,7 +495,7 @@
- دعوتنامه ارسال شد
- - %d دعوتنامه ارسال شدند
+ - %d دعوتنامه ها ارسال شدند
«%1$s» نمیتواند به صورت خودکار توسط شما به این گروه اضافه شود.\n\nآنها برای پیوستن دعوت شدهاند و پیامهای گروهی را تا زمانی که دعوت را نپذیرند، نخواهند دید.
این کاربران نمیتوانند به صورت خودکار توسط شما به این گروه اضافه شوند.\n\nآنها برای پیوستن به گروه دعوت شدهاند و پیامهای گروهی را تا زمانی که دعوت را بپذیرند، نخواهند دید.
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index 0908045b9a..26dcf50e47 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -3243,9 +3243,11 @@ broj telefona
Najčešća pitanja
Donirajte jednokratnom uplatom
Unesi prilagođeni iznos
+ Dodaj Signal Boost
Molimo Vas da kontaktirate podršku
Postanite pretplatnik
+ Dodaj Boost
Ne sada
Kontaktirajte podršku
Obrada uplate…
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
index 2d6d5099d6..4c29845f75 100644
--- a/app/src/main/res/values-iw/strings.xml
+++ b/app/src/main/res/values-iw/strings.xml
@@ -395,7 +395,7 @@
- מחק
- מחק
- בחר הכל
+ בחר הכול
- %d נבחר
- %d נבחרו
@@ -747,7 +747,7 @@
כבוי
מופעל
הצג את כל חברי הקבוצה
- ראה הכל
+ ראה הכול
- חבר קבוצה %d התווסף.
- %d חברי קבוצה התווספו.
@@ -913,7 +913,7 @@
מדיה
קבצים
שמע
- הכל
+ הכול
- למחוק פריט נבחר?
- למחוק פריטים נבחרים?
@@ -928,7 +928,7 @@
מוחק
מוחק הודעות…
- בחר הכל
+ בחר הכול
אוסף צרופות…
מיין לפי
החדש ביותר
@@ -938,7 +938,7 @@
תצוגת סורג
תצוגת רשימה
נבחר
- בחר הכל
+ בחר הכול
- שמור
- שמור
@@ -1482,7 +1482,7 @@
קוד וידוא יישלח אל:
תקבל שיחה כדי לוודא מספר זה.
האם מספר הטלפון שלך למעלה נכון?
- עריכת המספר
+ ערוך מספר
שירותי Google Play חסרים
במכשיר זה חסרים שירותי Google Play. אתה יכול עדין להשתמש ב־Molly, אבל תצורה זו עשויה לגרום לאמינות מופחתת או לביצועים מופחתים.\n\nאם אינך משתמש מתקדם, אינך מריץ ROM של Android שהותקן לאחר רכישה, או שאתה מאמין שאתה רואה זאת בטעות, אנא צור קשר עם support@molly.im לעזרה באיתור תקלות.
אני מבין
@@ -1712,7 +1712,7 @@
שגיאה במסירת הודעה.
מסירת הודעה הושהתה.
וודא כדי להמשיך להתכתב ב־Molly.
- סמן שהכל נקרא
+ סמן הכול כנקרא
סמן כנקרא
כבה התראות אלו
תמונה לצפייה חד־פעמית
@@ -2064,7 +2064,7 @@
למד עוד.]]>
הקש כדי לסרוק
התאמה מוצלחת
- נכשל בוידוא מספר ביטחון
+ נכשל בווידוא מספר ביטחון
טוען…
סמן כמוֻדא
נקה וידוא
@@ -2296,7 +2296,7 @@
זה ימחק לצמיתות את כל היסטורית ההודעות והמדיה מהמכשיר שלך.
האם אתה בטוח שאתה רוצה למחוק את כל היסטורית ההודעות?
כל היסטורית ההודעות תימחק לצמיתות. פעולה זו אינה ניתנת לביטול.
- מחק הכל כעת
+ מחק הכול עכשיו
לנצח
שנה 1
6 חודשים
@@ -2357,7 +2357,7 @@
כל הפעילות
- הכל
+ הכול
נשלח
התקבל
היכרות עם תשלומים (בטא)
@@ -2368,7 +2368,7 @@
אין פעילות מהזמן האחרון עדיין
בקשות ממתינות
פעילות מהזמן האחרון
- ראה הכל
+ ראה הכול
הוסף כספים
שלח
%1$s נשלחו
@@ -2546,7 +2546,7 @@
מחק נבחרים
הצמד נבחרים
בטל הצמדת נבחרים
- בחר הכל
+ בחר הכול
אחסן בארכיון נבחרים
הוצא מארכיון נבחרים
סמן כנקרא
@@ -2590,7 +2590,7 @@
קבוצה חדשה
הגדרות
נעל
- סמן הכל כנקרא
+ סמן הכול כנקרא
הזמן חברים
העתק ללוח העריכה
@@ -2690,7 +2690,7 @@
למד עוד
הכנס את ה־PIN שלך
- הכנס את ה־PIN שיצרת עבור החשבון שלך. הוא שונה מקוד הוידוא שלך במסרון.
+ הכנס את ה־PIN שיצרת עבור החשבון שלך. הוא שונה מקוד הווידוא שלך במסרון.
הכנס PIN אלפאנומרי
הכנס PIN מספרי
PIN שגוי. נסה שוב.
@@ -3239,24 +3239,24 @@
המשך
מספר הטלפון שלך השתנה.
- שינוי המספר
+ שנה מספר
המספר הישן שלך
- מספר טלפון ישן
+ מספר ישן של טלפון
המספר החדש שלך
- מספר טלפון חדש
+ מספר חדש של טלפון
מספר הטלפון שהכנסת אינו תואם אל מספר הטלפון של החשבון שלך.
אתה חייב לציין את קוד המדינה של המספר הישן שלך
אתה חייב לציין את מספר הטלפון הישן שלך
אתה חייב לציין את קוד המדינה של המספר החדש שלך
אתה חייב לציין את מספר הטלפון החדש שלך
- שינוי המספר
+ שנה מספר
מוודא את %1$s
אתגר מענה דרוש
שנה מספר
אתה עומד לשנות את מספר הטלפון שלך מן %1$s אל %2$s.\n\nלפני המשכה, אנא וודא שהמספר למטה נכון.
- עריכת המספר
+ ערוך מספר
שינוי מספר של Signal - צריך עזרה עם PIN עבור Android (v2 PIN)
@@ -3339,7 +3339,7 @@
איכות מדיה
הגדר איכות מדיה
שליחת מדיה באיכות גבוהה תשתמש ביותר נתונים.
- גבוה
+ גבוהה
תקנית
שיחות
@@ -3433,7 +3433,7 @@
שחרר חסימה
שחרר חסימת קבוצה
הוסף אל קבוצה
- ראה הכל
+ ראה הכול
הוסף חברי קבוצה
הרשאות
בקשות והזמנות
@@ -3729,7 +3729,7 @@
תזמן
- ראה הכל
+ ראה הכול
הוסף תזמון
diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml
index 421f84e7f1..7e72628a5e 100644
--- a/app/src/main/res/values-lt/strings.xml
+++ b/app/src/main/res/values-lt/strings.xml
@@ -413,7 +413,7 @@
Archyvuoti pokalbiai (%d)
- Patvirtinta(-s)
+ Patvirtinta(s)
Jūs
+%1$d
@@ -1913,7 +1913,7 @@
Išeiti iš skambučio
Gali būti, kad šie žmonės iš naujo įdiegė programėlę ar pakeitė įrenginį. Patvirtinkite savo saugumo numerį su kiekvienu iš jų, kad užtikrintumėte privatumą.
Rodyti
- Anksčiau patvirtinta(-s)
+ Anksčiau patvirtinta(s)
Pranešimai apie skambučius įjungti.
Įjungti pranešimus apie skambučius
diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml
index cd92affca7..a4390a6a81 100644
--- a/app/src/main/res/values-mk/strings.xml
+++ b/app/src/main/res/values-mk/strings.xml
@@ -5,7 +5,7 @@
Избриши
Ве молиме почекајте…
Зачувај
- Забелешка за себе
+ Белешка за себе
Нова порака
@@ -1084,7 +1084,7 @@
Го обележавте сигурносниот број со %s како непроверен
Го обележавте Вашиот сигурносен број со %s како непроверен од друг уред.
Порака од %s не може да биде испорачана
- %1$s го смени бројот во нов број.
+ %1$s го смени бројот со нов број.
%1$s започна групен повик · %2$s
%1$s е во групниот повик · %2$s
@@ -1940,7 +1940,7 @@
Вклучи запис за дебагирање.
Што е ова?
Како се чувствувате? (Опционално)
- Кажете ни зошто не контактирате.
+ Кажете ни зошто нѐ контактирате.
Информации за поддршка
Барање за поддршка за Signal Android
Запис за дебагирање:
@@ -2283,7 +2283,7 @@
0
<
Backspace
- Додај порака
+ Додај белешка
Конверзиите се само проценки и можеби не се точни.
Белешка
diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml
index 2b5edd5935..610f67515a 100644
--- a/app/src/main/res/values-ml/strings.xml
+++ b/app/src/main/res/values-ml/strings.xml
@@ -298,6 +298,7 @@
%s-മായുള്ള നിങ്ങളുടെ സുരക്ഷാ നമ്പർ മാറി.
%s -മായുള്ള നിങ്ങളുടെ സുരക്ഷാ നമ്പർ മാറി. അവർ Signal വീണ്ടും ഇൻസ്റ്റാൾ ചെയ്തതുകൊണ്ടോ ഡിവൈസ് മാറിയതുകൊണ്ടോ ആവാമിത്. പുതിയ സുരക്ഷാ നമ്പർ ഉറപ്പുവരുത്താൻ \'ഉറപ്പാക്കു\' ടാപ് ചെയ്യുക. ഇത് ഓപ്ഷണൽ ആണ്.
+ %1$sസജീവം
- തിരഞ്ഞെടുത്ത സംഭാഷണം ഇല്ലാതാക്കണോ?
- തിരഞ്ഞെടുത്ത സംഭാഷണങ്ങൾ ഇല്ലാതാക്കണോ?
@@ -317,12 +318,54 @@
- സംഭാഷണം ഇൻബോക്സിലേക്ക് നീക്കി
- %d സംഭാഷണങ്ങൾ ഇൻബോക്സിലേക്ക് നീക്കി
+
+ - വായിച്ചു
+ - വായിച്ചു
+
+
+ - വായിച്ചില്ല
+ - വായിച്ചില്ല
+
+
+ - പിൻ
+ - പിൻ
+
+
+ - അൺപിൻ ചെയ്യുക
+ - അൺപിൻ ചെയ്യുക
+
+
+ - നിശബ്ദമാക്കുക
+ - നിശബ്ദമാക്കുക
+
+
+ - ശബ്ദിപ്പിക്കുക
+ - ശബ്ദിപ്പിക്കുക
+
തിരഞ്ഞെടുക്കൂ
+
+ - ആർക്കൈവ്
+ - ആർക്കൈവ്
+
+
+ - അൺആർക്കൈവ്
+ - അൺആർക്കൈവ്
+
+
+ - ഇല്ലാതാക്കുക
+ - ഇല്ലാതാക്കുക
+
എല്ലാം തിരഞ്ഞെടുക്കുക
+
+ - %d തിരഞ്ഞെടുത്തു
+ - %d തിരഞ്ഞെടുത്തു
+
+ അറിയിപ്പ് രൂപരേഖ
നിങ്ങൾക്കുള്ള അറിയിപ്പുകൾ ഇവിടെ ഓണാക്കുക അല്ലെങ്കിൽ ഓഫ് ചെയ്യുക.
+ %1$sസജീവം
കീ കൈമാറ്റ സന്ദേശം
@@ -369,6 +412,7 @@
പുരോഗതിയിൽ…
%1$d ഇതുവരെ….
+ %1$s%% ഇതുവരെ…
ബാക്കപ്പുകൾ സൃഷ്ടിക്കുന്നതിന് Molly-ന് ബാഹ്യ സ്റ്റോറജ് അനുമതി ആവശ്യമാണ്, പക്ഷേ ഇത് ശാശ്വതമായി നിരസിക്കപ്പെട്ടു. അപ്ലിക്കേഷൻ ക്രമീകരണങ്ങളിലേക്കു പോയി , \"അനുമതികൾ\" തിരഞ്ഞെടുത്ത് \"സ്റ്റോറജ്\" ഉപയോഗാനുമതി നൽകുക .
Custom ഉപയോഗിക്കുന്നു: %s
@@ -745,10 +789,12 @@
പങ്കിടുക
കോൺടാക്റ്റുകളുമായി പങ്കിടുക
+ ഇതുവഴി പങ്കിടുക…
റദ്ദാക്കുക
അയയ്ക്കുന്നു…
ക്ഷണങ്ങൾ അയച്ചു!
Molly-ലേക്ക് ക്ഷണിക്കുക
+ (%d) SMS അയയ്ക്കുക
- %d SMS ക്ഷണം അയയ്ക്കണോ?
- %d SMS ക്ഷണങ്ങൾ അയയ്ക്കണോ?
@@ -793,6 +839,18 @@
ലിസ്റ്റ് കാഴ്ച
തിരഞ്ഞെടുത്തു
എല്ലാം തിരഞ്ഞെടുക്കുക
+
+ - സൂക്ഷിക്കുക
+ - സൂക്ഷിക്കുക
+
+
+ - ഇല്ലാതാക്കുക
+ - ഇല്ലാതാക്കുക
+
+
+ - %1$d തിരഞ്ഞെടുത്തു (%2$s)
+ - %1$d തിരഞ്ഞെടുത്തു (%2$s)
+
ഫയൽ
ഓഡിയോ
വീഡിയോ
@@ -817,7 +875,9 @@
Signal കോൾ പുരോഗതിയിലാണ്
Signal കോൾ സ്ഥാപിക്കുന്നു
ഇൻകമിംഗ് സിഗ്നൽ കോൾ
+ Signal ഗ്രൂപ്പ് കോള് വരുന്നു
Signal കോൾ സേവനം നിർത്തുന്നു
+ കോൾ നിരസിക്കുക
കോൾ സ്വീകരിക്കുക
കോൾ അവസാനിപ്പിക്കുക
കോൾ റദ്ദാക്കുക
@@ -1024,6 +1084,7 @@
%s-മായുള്ള നിങ്ങളുടെ സുരക്ഷാ നമ്പർ നിങ്ങൾ സ്ഥിരീകരിച്ചിട്ടില്ലതായി അടയാളപ്പെടുത്തി
%s-മായുള്ള നിങ്ങളുടെ സുരക്ഷാ നമ്പർ നിങ്ങൾ മറ്റൊരു ഉപകരണത്തിൽ നിന്ന് സ്ഥിരീകരിച്ചിട്ടില്ലതായി അടയാളപ്പെടുത്തി
%s-ൽ നിന്നുള്ള ഒരു സന്ദേശം ഡെലിവർ ചെയ്യാൻ കഴിഞ്ഞില്ല
+ %1$s അവരുടെ നമ്പർ ഒരു പുതിയ നമ്പറിലേക്ക് മാറ്റി.
%1$s ഒരു ഗ്രൂപ്പ് കോൾ ആരംഭിച്ചു . %2$s
%1$s ഗ്രൂപ്പ് കോളിലാണ് · %2$s
@@ -1181,6 +1242,7 @@
%1$s-യെ വിളിക്കാൻ, Molly-ന് നിങ്ങളുടെ ക്യാമറയിലേക്ക് ആക്സസ് ആവശ്യമാണ്
Molly %1$s
വിളിക്കുന്നു…
+ പങ്കെടുക്കുന്നവരെ വിളിക്കാൻ കഴിയാത്തത്ര വലുതാണ് ഗ്രൂപ്പ്.
Signal വിളി
Signal വീഡിയോ കോൾ
@@ -1193,18 +1255,45 @@
റീകണക്ടിംഗ്…
ചേരുന്നു…
വിച്ഛേദിച്ചു
+ Signal %1$s നെ വിളിക്കും
+ Signal %1$s നെയും %2$s നെയും വിളിക്കും
+
+ - Signal %1$s, %2$s നെയും മറ്റ് %3$d പേരെയും വിളിക്കും
+ - Signal %1$s, %2$s നെയും മറ്റ് %3$d പേരെയും വിളിക്കും
+
+ %1$s യെ അറിയിക്കും
+ %1$s യെയും %2$s യെയും അറിയിക്കും
+
+ - %1$s, %2$s യെയും മറ്റ് %3$d പേരെയും അറിയിക്കും
+ - %1$s, %2$s യെയും മറ്റ് %3$d പേരെയും അറിയിക്കും
+
+ %1$s നെ വിളിയ്ക്കുന്നു
+ %1$s നെയും %2$s നെയും വിളിയ്ക്കുന്നു
+
+ - %1$s, %2$s നെയും മറ്റ് %3$d പേരെയും വിളിക്കുന്നു
+ - %1$s, %2$s നെയും മറ്റ് %3$d പേരെയും വിളിക്കുന്നു
+
%1$sനിങ്ങളെ വിളിക്കുന്നു
+ %1$s നിങ്ങളെയും %2$s-നെയും വിളിക്കുന്നു
+ %1$s നിങ്ങളെയും %2$s-നെയും %3$s-നെയും വിളിക്കുന്നു
+
+ - %1$s നിങ്ങളെയും %2$s-നെയും %3$s-നെയും മറ്റ് %4$d പേരെയും വിളിക്കുന്നു
+ - %1$s നിങ്ങളെയും %2$s-നെയും %3$s-നെയും മറ്റ് %4$d പേരെയും വിളിക്കുന്നു
+
മറ്റാരുമില്ല
%1$s ഈ കോളിലുണ്ട്
+ %1$s പേർ ഈ കോളിലുണ്ട്
%1$s - ഉം %2$s - ഉം ഈ കോളിലുണ്ട്
%1$sഅവതരിപ്പിക്കുന്നു
- %1$s- ഉം, %2$s - ഉം പിന്നെ %3$d - ഉം ഈ കോളിലുണ്ട്
- %1$s - ഉം, %2$s - ഉം പിന്നെ %3$d ആളുകളും ഈ കോളിലുണ്ട്
+ തിരിക്കൂ
സ്പീക്കർ
ക്യാമറ
നിശബ്ദമാക്കുക
+ റിംഗ്
കോൾ അവസാനിപ്പിക്കൂ
@@ -1246,8 +1335,13 @@
Play Services പിശക്
Google Play Services അപ്ഡേറ്റുചെയ്യുന്നു അല്ലെങ്കിൽ താൽക്കാലികമായി ലഭ്യമല്ല. ദയവായി വീണ്ടും ശ്രമിക്കുക.
നിബന്ധനകളും സ്വകാര്യതാ നയവും
+ സുഹൃത്തുക്കളുമായി ബന്ധപ്പെടാനും സന്ദേശങ്ങള് അയക്കാനും നിങ്ങളെ സഹായിക്കുന്നതിന് Signal-ന് കോൺടാക്റ്റുകൾ, മീഡിയ അനുമതികൾ ആവശ്യമാണ്. Signal-ന്റെ സ്വകാര്യ കോൺടാക്റ്റ് കണ്ടെത്തൽ ഉപയോഗിച്ചാണ് നിങ്ങളുടെ കോൺടാക്റ്റുകൾ അപ്ലോഡ് ചെയ്തിരിക്കുന്നത്, അതിനർത്ഥം അവ എൻഡ്-ടു-എൻഡ് എൻക്രിപ്റ്റ് ചെയ്തതും Signal സേവനത്തിന് ഒരിക്കലും ദൃശ്യമാകാത്തതുമാണ്.
+ സുഹൃത്തുക്കളുമായി ബന്ധപ്പെടാൻ നിങ്ങളെ സഹായിക്കുന്നതിന് Signal-ന് കോൺടാക്റ്റുകൾ അനുമതി ആവശ്യമാണ്. Signal-ന്റെ സ്വകാര്യ കോൺടാക്റ്റ് കണ്ടെത്തൽ ഉപയോഗിച്ചാണ് നിങ്ങളുടെ കോൺടാക്റ്റുകൾ അപ്ലോഡ് ചെയ്തിരിക്കുന്നത്, അതിനർത്ഥം അവ എൻഡ്-ടു-എൻഡ് എൻക്രിപ്റ്റ് ചെയ്തതും Signal സേവനത്തിന് ഒരിക്കലും ദൃശ്യമാകാത്തതുമാണ്.
ഈ നമ്പർ രജിസ്റ്റർ ചെയ്യുന്നതിന് നിങ്ങൾ വളരെയധികം ശ്രമങ്ങൾ നടത്തി. പിന്നീട് വീണ്ടും ശ്രമിക്കുക.
സേവനത്തിലേക്ക് കണക്റ്റുചെയ്യാനായില്ല. നെറ്റ്വർക്ക് കണക്ഷൻ പരിശോധിച്ച് വീണ്ടും ശ്രമിക്കുക.
+ അംഗീകൃതമല്ലാത്ത നമ്പര് രൂപം
+ നിങ്ങൾ നൽകിയ നമ്പർ (%1$s) അംഗീകൃതമല്ലാത്ത നമ്പര് രൂപത്തില് ആണെന്ന് തോന്നുന്നു.\n\nനിങ്ങൾ ഉദ്ദേശിച്ചത് %2$s എന്നാണോ?
+ Molly ആൻഡ്രോയിഡ് - ഫോണ് നമ്പര് രുപം
- ഒരു ഡീബഗ് ലോഗ് സമർപ്പിക്കുന്നതിൽ നിന്ന് നിങ്ങൾ ഇപ്പോൾ %d പടി അകലെയാണ്.
- ഒരു ഡീബഗ് ലോഗ് സമർപ്പിക്കുന്നതിൽ നിന്ന് നിങ്ങൾ ഇപ്പോൾ %d ഘട്ടങ്ങൾ അകലെയാണ്.
@@ -1520,6 +1614,7 @@
ഉപകരണം മേലിൽ രജിസ്റ്റർ ചെയ്തിട്ടില്ല
നിങ്ങളുടെ ഫോൺ നമ്പർ മറ്റൊരു ഉപകരണത്തിൽ Signal-ൽ രജിസ്റ്റർ ചെയ്തതിനാലാകാം ഇത്. വീണ്ടും രജിസ്റ്റർ ചെയ്യാൻ ടാപ്പുചെയ്യുക.
+ ഈ കോളിന് മറുപടി നൽകാൻ, നിങ്ങളുടെ മൈക്രോഫോണ് ഉപയോഗിക്കാന് Molly-നെ അനുവദിക്കുക.
%s നിന്നുള്ള കോളിന് മറുപടി നൽകാൻ, നിങ്ങളുടെ മൈക്രോഫോണിലേക്ക് Molly-ന് ആക്സസ് നൽകുക.
കോളുകൾ വിളിക്കുന്നതിനോ സ്വീകരിക്കുന്നതിനോ Molly-ന് മൈക്രോഫോൺ, ക്യാമറ അനുമതികൾ ആവശ്യമാണ്, പക്ഷേ അവ ശാശ്വതമായി നിരസിക്കപ്പെട്ടു. അപ്ലിക്കേഷൻ ക്രമീകരണങ്ങളിൽ തുടരുക, \"അനുമതികൾ\" തിരഞ്ഞെടുത്ത് \"മൈക്രോഫോൺ\", \"ക്യാമറ\" എന്നിവ പ്രവർത്തനക്ഷമമാക്കുക.
ഒരു ബന്ധിപ്പിച്ച ഉപകരണത്തിൽ ഉത്തരം നൽകി.
@@ -1785,8 +1880,11 @@
ഒരു പേരോ നമ്പറോ നൽകുക
സ്കാൻ ചെയ്യാൻ തൊടുക
+ വിജയകരമായ ചേര്ച്ച
+ സുരക്ഷാ നമ്പർ പരിശോധിക്കുന്നതിൽ പരാജയപ്പെട്ടു
ലഭ്യമാക്കുന്നു
പരിശോധിച്ചതായി അടയാളപ്പെടുത്തുക
+ പരിശോധന പൂർത്തിയാക്കുക
സുരക്ഷാ നമ്പർ പങ്കിടുക
@@ -1796,6 +1894,10 @@
നിരസിക്കാൻ താഴേക്ക് സ്വൈപ്പുചെയ്യുക
ചില പ്രശ്നങ്ങൾക്ക് നിങ്ങളുടെ ശ്രദ്ധ ആവശ്യമാണ്.
+ അയച്ചത്:
+ സ്വീകരിച്ചത്:
+ അപ്രത്യക്ഷമാകുന്നത്:
+ വഴി:
ശേഷിക്കുന്നു
അയച്ചത്
@@ -1837,6 +1939,16 @@
ഡീബഗ് ലോഗ്:
ലോഗുകൾ അപ്ലോഡുചെയ്യാനായില്ല
പ്രശ്നം മനസിലാക്കാൻ ഞങ്ങളെ സഹായിക്കുന്നതിന് ദയവായി കഴിയുന്നത്ര വിവരണാത്മകമായിരിക്കുക.
+
+ - \-\- ദയവായി ഒരു ഓപ്ഷൻ തിരഞ്ഞെടുക്കുക \-\-
+ - എന്തോ പ്രവർത്തിക്കുന്നില്ല
+ - ഫീച്ചർ അഭ്യർത്ഥന
+ - ചോദ്യം
+ - പ്രതികരണം
+ - മറ്റുള്ളവ
+ - പേയ്മെന്റുകൾ(മൊബൈൽകോയിൻ)
+ - പരിപാലകരും Signal ബൂസ്റ്റും
+
ഈ സന്ദേശം
അടുത്തിടെ ഉപയോഗിച്ചവ
@@ -1933,6 +2045,8 @@
സഹായം
വിപുലമായ
Molly-ന് സംഭാവന ചെയ്യുക
+ Signal പരിപാലകര് ആകുക
+ Signal ബൂസ്റ്റ്
സ്വകാര്യത
MMS യൂസർ ഏജൻറ്
സ്വമേധയാലുള്ള MMS ക്രമീകരണങ്ങൾ
@@ -2215,6 +2329,10 @@
ഒന്നിലധികം തിരഞ്ഞെടുക്കുക
+
+ - %d തിരഞ്ഞെടുത്തു
+ - %d തിരഞ്ഞെടുത്തു
+
സംരക്ഷിക്കൂ
@@ -2466,6 +2584,7 @@
പരിശോധിക്കുക
നിങ്ങൾ ബാക്കപ്പിന്റെ രഹസ്യവാചകം വിജയകരമായി നൽകി
രഹസ്യവാചകം ശരിയായിരുന്നില്ല
+ Molly ബാക്കപ്പ് സൃഷ്ടിക്കുന്നു…
ബാക്കപ്പ് പരാജയപ്പെട്ടു
നിങ്ങളുടെ ബാക്കപ്പ് ഡയറക്ടറി ഇല്ലാതാക്കി അല്ലെങ്കിൽ നീക്കി.
ഈ വോളിയത്തിൽ സംഭരിക്കാനുംമാത്രം നിങ്ങളുടെ ബാക്കപ്പ് ഫയൽ വളരെ വലുതാണ്.
@@ -2588,6 +2707,7 @@
രണ്ട് ഉപകരണങ്ങളും പരസ്പരം അടുത്ത് വയ്ക്കുക. ഉപകരണങ്ങൾ ഓഫ് ചെയ്യരുത്, Molly തുറന്നിടുക. ട്രാൻസ്ഫറുകൾ എൻഡ്-ടു-എൻഡ് എൻക്രിപ്റ്റ് ചെയ്തിരിക്കുന്നു.
ഇതുവരെ %1$d സന്ദേശങ്ങൾ
+ ഇതുവരെ %1$s %% സന്ദേശങ്ങള് …
റദ്ദാക്കൂ
വീണ്ടും ശ്രമിക്കുക
കൈമാറ്റം നിർത്തണോ?
@@ -2638,6 +2758,7 @@
തടഞ്ഞത് മാറ്റുക
കോൺടാക്റ്റുകളിൽ ചേർക്കൂ
+ കോൺടാക്റ്റുകൾ തുറക്കാൻ കഴിയുന്ന ഒരു അപ്ലിക്കേഷൻ കണ്ടെത്താനായില്ല.
ഒരു ഗ്രൂപ്പിലേക്ക് ചേർക്കുക
മറ്റൊരു ഗ്രൂപ്പിലേക്ക് ചേർക്കുക
സുരക്ഷാ നമ്പർ കാണുക
@@ -2737,7 +2858,9 @@
പ്രാദേശിക ഡാറ്റ ഇല്ലാതാക്കാൻ പരാജയപെട്ടു. സിസ്റ്റം അപ്ലിക്കേഷൻ ക്രമീകരണങ്ങളിൽ നിങ്ങൾക്ക് ഇത് സ്വമേധയാ ഇല്ലാതാക്കാം.
ആപ്പ് ക്രമീകരണങ്ങൾ തുറക്കുക
+ ഗ്രൂപ്പുകള് വിട്ടുപോകുന്നു…
+ അക്കൌണ്ട് ഇല്ലാതാക്കുന്നു…
@@ -2902,6 +3025,7 @@
നിങ്ങളുടെ ഫോൺ നമ്പർ വീണ്ടും Signal-ൽ രജിസ്റ്റർ ചെയ്യുന്നതിന് നിങ്ങളുടെ Signal പിൻ ആവശ്യമാണ്
ഫോൺ നമ്പർ മാറ്റുക
+ നിങ്ങളുടെ നിലവിലെ ഫോൺ നമ്പർ ഒരു പുതിയ ഫോൺ നമ്പറിലേക്ക് മാറ്റാൻ ഇത് ഉപയോഗിക്കുക. നിങ്ങൾക്ക് ഈ മാറ്റം പഴയപടിയാക്കാനാകില്ല.\n\nതുടരുന്നതിന് മുമ്പ്, നിങ്ങളുടെ പുതിയ നമ്പറിന് SMS അല്ലെങ്കിൽ കോളുകൾ ലഭിക്കുമെന്ന് ഉറപ്പാക്കുക.
തുടരുക
നിങ്ങളുടെ ഫോൺ നമ്പർ മാറ്റി
@@ -2911,11 +3035,17 @@
നിങ്ങളുടെ പുതിയ ഫോൺ നമ്പർ
പുതിയ ഫോൺ നമ്പർ
നിങ്ങൾ നൽകിയ ഫോൺ നമ്പർ നിങ്ങളുടെ അക്കൗണ്ടുമായി പൊരുത്തപ്പെടുന്നില്ല.
+ നിങ്ങളുടെ പഴയ നമ്പറിന്റെ രാജ്യ കോഡ് വ്യക്തമാക്കണം
+ നിങ്ങളുടെ പഴയ ഫോൺ നമ്പർ വ്യക്തമാക്കണം
+ നിങ്ങളുടെ പുതിയ നമ്പറിന്റെ രാജ്യ കോഡ് വ്യക്തമാക്കണം
+ നിങ്ങളുടെ പുതിയ ഫോൺ നമ്പർ വ്യക്തമാക്കണം
നമ്പർ മാറ്റുക
%1$sഉറപ്പാക്കുന്നു
+ ക്യാപ്ച ആവശ്യമാണ്
നമ്പർ മാറ്റുക
+ നിങ്ങളുടെ ഫോൺ നമ്പർ %1$s-ൽ നിന്ന് %2$s-ലേക്ക് മാറ്റാൻ പോകുകയാണ്.\n\nതുടരുന്നതിന് മുമ്പ്, ചുവടെയുള്ള നമ്പർ ശരിയാണോയെന്ന് പരിശോധിക്കുക.
നമ്പർ തിരുത്തുക
@@ -3103,6 +3233,7 @@
.5x
1x
+ 1.5x
2x
പുതിയ പേയ്മെന്റ്
@@ -3154,10 +3285,19 @@
- സന്ദേശം അയയ്ക്കുന്നത് പരാജയപ്പെട്ടു.
- സന്ദേശങ്ങള് അയയ്ക്കുന്നത് പരാജയപ്പെട്ടു.
+
+ - സന്ദേശം ലഭ്യമല്ലാത്തതിനാൽ ഫോർവേഡ് ചെയ്യാനായില്ല.
+ - സന്ദേശങ്ങള് ഇനി ലഭ്യമല്ലാത്തതിനാൽ ഫോർവേഡ് ചെയ്യാനായില്ല.
+
+ പരിധി എത്തി
ഒരു സന്ദേശം ചേര്ക്കുക
ഒരു മറുപടി ചേർക്കുക
റദ്ദാക്കൂ
+ വരയ്ക്കുക
+ വാചകം എഴുതുക
+ സ്റ്റിക്കർ ചേർക്കുക
+ മങ്ങിക്കുക
എല്ലാം മായ്ക്കുക
തിരിച്ചാക്കുക
ഇല്ലാതാക്കൂ
@@ -3166,9 +3306,12 @@
കളയുക
പ്രിവ്യൂ
നിങ്ങളെപ്പോലുള്ളവരാണ് Signal പ്രവർത്തിപ്പിക്കുന്നത്.
+ നാണയം
+ പണമടയ്ക്കാനുള്ള കൂടുതല് മാർഗങ്ങൾ
ഇപ്പോൾ വേണ്ട
സ്ഥിരീകരിക്കുക
അപ്ഡേറ്റ്
+ നിങ്ങളുടെ പിന്തുണയ്ക്ക് നന്ദി!
ചെയ്തു
ബാഡ്ജുകള്
ഇഷ്ടാനുസൃത തുക നൽകുക
@@ -3268,6 +3411,7 @@
+ ഒരു പ്രൊഫൈൽ സൃഷ്ടിക്കുക
ഇപ്പോൾ വേണ്ട
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index ea2a19a4a6..5beb2fa990 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -233,8 +233,8 @@
Annuleren
Gesprek verwijderen?
Groep verwijderen en verlaten?
- Dit gesprek zal worden gewist van al je apparaten.
- Je zult deze groep verlaten en het gesprek zal van al je apparaten worden gewist.
+ Dit gesprek zal op al je eigen apparaten worden gewist.
+ Je zult deze groep verlaten en het gesprek zal op al je eigen apparaten worden gewist.
Verwijderen
Verwijderen en verlaten
Molly heeft toegang nodig tot je microfoon om %1$s te bellen.
@@ -1550,7 +1550,7 @@
Molly heeft toegang tot externe opslag nodig om iets op te slaan op de externe opslag, maar deze is permanent geweigerd. Ga naar de instellingen voor deze app, tik op ‘App-machtigingen’ en schakel ‘Opslagruimte’ in.
Kan niet opslaan naar externe opslag zonder machtiging
Bericht wissen?
- Dit bericht zal alleen voor jou onherroepelijk gewist worden, maar dus niet voor je gesprekspartner(s).
+ Dit bericht zal alleen voor jou onherroepelijk worden gewist, maar dus niet voor je gesprekspartner(s).
%1$s naar %2$s
Media niet langer beschikbaar.
Geen app gevonden waarmee dit bestand met anderen gedeeld kan worden.
@@ -2513,7 +2513,7 @@
Meer lezen
Voer je pincode in
- Voer de pincode in welke je eerder hebt aangemaakt om informatie versleuteld op te slaan op Signals servers. Dit is niet dezelfde code als je sms-verificatiecode.
+ Voer de pincode in welke je eerder hebt aangemaakt om informatie versleuteld op te slaan op Signals servers. Dit is niet dezelfde code als de telefoonnummer verificatie code die je per sms of telefoonoproep hebt ontvangen.
Alfanumerieke pincode invoeren
Voer numerieke pincode in.
Foutieve pincode, probeer het opnieuw.
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 9c6b335de6..61d70934be 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -1084,7 +1084,7 @@
Marcou como não verificado o seu número de segurança com %s
Marcou como não verificado o seu número de segurança com %s a partir de outro dispositivo
Não pode ser entregue uma mensagem de %s
- %1$s alterou o número de telemóvel para um novo número.
+ %1$s alterou o número de telefone para um número novo.
%1$s iniciou uma chamada em grupo · %2$s
%1$s está na chamada de grupo · %2$s
@@ -1505,7 +1505,7 @@
Os nomes de utilizadores não podem começar com um número.
Nome de utilizador inválido.
Os nomes de utilizadores devem ter entre %1$d e %2$d caracteres.
- Os nomes de utilizadores no Signal são opcionais. Se escolher criar um nome de utilizador, os outros utilizadores do Signal serão capazes de encontrá-lo através desse nome de utilizador e contactá-lo sem saber o seu número de telemóvel.
+ Os nomes de utilizadores no Signal são opcionais. Se escolher criar um nome de utilizador, os outros utilizadores do Signal serão capazes de encontrá-lo através desse nome de utilizador e contactá-lo sem saber o seu número de telefone.
- %d contacto está no Signal!
- %d contactos estão no Signal!
@@ -1888,7 +1888,7 @@
Introduza um nome ou número
- Mais informações.]]>
+ Mais informações.]]>
Toque para ler
Correspondência bem sucedida
Falha o verificar o número de segurança
@@ -2504,7 +2504,7 @@
PIN incorreto. Tente novamente.
Conta bloqueada
- A sua conta foi bloqueada para proteger a sua privacidade e segurança. Após %1$d dias de inatividade ser-lhe-á permitido voltar a registar este número de telemóvel sem necessitar do seu PIN. Todo o conteúdo será eliminado.
+ A sua conta foi bloqueada para proteger a sua privacidade e segurança. Após %1$d dias de inatividade, ser-lhe-á permitido voltar a registar este número de telefone sem necessitar do seu PIN. Todo o conteúdo será eliminado.
Seguinte
Saber mais
@@ -2608,13 +2608,13 @@
Código Incorrecto
Nunca
Desconhecido
- Ver o meu número de telemóvel
- Encontrar-me através do número de telemóvel
+ Ver o meu número de telefone
+ Encontrar-me através do número de telefone
Todos
Os meus contactos
Ninguém
- O seu número de telemóvel ficará visível para todas as pessoas e grupos com quem troque mensagens.
- Qualquer pessoa que tenha o seu número de telemóvel nos seus contactos irá vê-lo como contacto no Signal. As outras pessoas poderão encontrá-lo através da pesquisa.
+ O seu número de telefone ficará visível para todas as pessoas e grupos com quem troque mensagens.
+ Qualquer pessoa que tenha o seu número de telefone nos seus contactos irá vê-lo como contacto no Signal. As outras pessoas poderão encontrá-lo através da pesquisa.
Bloqueio de ecrã
Bloqueie o acesso ao Signal utilizando o bloqueio de ecrã do Android ou a impressão digital
Bloqueio de ecrã devido a inatividade
@@ -2623,7 +2623,7 @@
Alterar o seu PIN
Lembretes de PIN
Os PINs mantém encriptada a informação guardada no Signal de forma a que apenas você lhe possa aceder. O seu perfil, definições e contactos serão restaurados quando reinstalar o Signal.
- Adicione uma segurança extra ao pedir o seu PIN do Signal para registar o seu número de telemóvel novamente com o Signal.
+ Adicione uma segurança extra ao pedir o seu PIN do Signal para registar o seu número de telefone novamente com o Signal.
Os lembretes ajudam-no a lembrar-se do seu PIN, pois ele não pode ser recuperado. Este, ser-lhe-á solicitado com menos frequência ao longo do tempo.
Desativar
Confirmar PIN
@@ -2861,7 +2861,7 @@
Eliminar %1$s na sua conta de pagamentos
Sem código de país especificado
Sem número especificado
- O número de telemóvel que introduziu não coincide com as suas contas.
+ O número de telefone que introduziu não coincide com as suas contas.
Deseja realmente eliminar a sua conta?
Isto irá eliminar a sua conta do Signal e repor a aplicação. A aplicação irá encerrar depois do processo estar completo.
Falha ao eliminar conta. Tem alguma ligação à rede?
@@ -3036,29 +3036,29 @@
Conta
Ser-lhe-á pedido menos frequentemente ao longo do tempo
Exija o seu PIN do Signal para registar novamente o seu número de telefone com o Signal.
- Alterar o número de telemóvel
+ Alterar o número de telefone
- Utilize isto para alterar o seu número atual para um número de telemóvel novo. Não pode desfazer esta alteração.\n\nAntes de continuar, assegure-se que o seu número novo pode receber SMS ou chamadas.
+ Utilize isto para alterar o seu número atual para um número de telefone novo. Não pode desfazer esta alteração.\n\nAntes de continuar, assegure-se que o seu número novo pode receber SMS ou chamadas.
Continuar
- O seu número de telemóvel foi alterado.
+ O seu número de telefone foi alterado.
Alterar número
O seu número antigo
- Número de telemóvel antigo
+ Número de telefone antigo
O seu número novo
- Número de telemóvel novo
- O número de telemóvel que introduziu não coincide com as suas contas.
+ Número de telefone novo
+ O número de telefone que introduziu não coincide com as suas contas.
Deverá especificar o código do país do seu número antigo
- Deverá especificar o seu número de telemóvel antigo
+ Deverá especificar o seu número de telefone antigo
Deverá especificar o código do país do seu número novo
- Deverá especificar o seu número de telemóvel novo
+ Deverá especificar o seu número de telefone novo
Alterar número
A verificar %1$s
Captcha necessário
Alterar número
- Está prestes a alterar p seu número de telemóvel de %1$s para %2$s.\n\n\Antes de prosseguir, confirme que o número abaixo se encontra correto.
+ Está prestes a alterar o seu número de telefone de %1$s para %2$s.\n\n\Antes de prosseguir, confirme que o número abaixo se encontra correto.
Editar número
Alterar o número do Signal - Necessita de ajuda com o PIN para Android (v2 PIN)
@@ -3069,7 +3069,7 @@
Atualizar PIN
Manter o PIN antigo?
- Parece que você tentou mudar o seu número de telemóvel. Aguardamos a confirmação de que a mudança foi bem-sucedida.\n\nA verificar agora…
+ Parece que você tentou mudar o seu número de telefone. Aguardamos a confirmação de que a mudança foi bem-sucedida.\n\nA verificar agora…
Estado da alteração confirmado
Seu número foi confirmado como %1$s. Se esse não for o seu novo número, refaça o processo de alteração de número.
Estado da alteração não confirmado
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 10ab8d8a02..d65d2509a8 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -2806,7 +2806,7 @@ Am primit mesajul conform căruia schimbul de chei a avut loc pentru o versiune
Nu se poate transfera dintr-o versiune mai nouă de Signal
Se transmit datele
- Ține ambele dispozitive unul lângă altul. Nu opri dispozitivele și menține Molly deschis. Transferurile sunt criptate integral.
+ Ține ambele dispozitive unul lângă celălalt. Nu opri dispozitivele și menține Molly deschis. Transferul este criptat integral.
%1$d mesaje până acum…
%1$s%% din mesaje până acum…
@@ -2833,7 +2833,7 @@ Am primit mesajul conform căruia schimbul de chei a avut loc pentru o versiune
Datele tale Signal au fost transferate pe noul dispozitiv. Pentru a finaliza procesul de transfer, trebuie să continui înregistrarea pe noul dispozitiv.
Închide
- Transferul a fost făcut cu succes
+ Transferul a reușit
Transfer finalizat
Pentru a finaliza procesul de transfer, trebuie să continui înregistrarea.
Continuă înregistrarea
@@ -3153,7 +3153,7 @@ Am primit mesajul conform căruia schimbul de chei a avut loc pentru o versiune
Captcha este necesară
Schimbă Numărul
- Urmează să îți schimbi numărul de telefon din %1$s în %2$s.\n\nÎnainte de a continua, te rugăm să te asiguri că numărul de mai jos este corect.
+ Urmează să îți schimbi numărul de telefon din %1$s în %2$s.\n\nÎnainte de a continua, te rugăm să verifici dacă numărul de mai jos este corect.
Editează număr
Schimbare Număr Signal - Am nevoie de asistență pentru PIN Android (v2 PIN)
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index 456fbd3587..6e0147f544 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -2427,6 +2427,11 @@
Изабери више
+
+ - Одабрано %d
+ - Одабрано %d
+ - Одабрано %d
+
Сачувај
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 19ed925b39..aec0aedca3 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -1293,6 +1293,7 @@
Högtalare
Kamera
Tysta
+ Ring
Avsluta samtal
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 066203f7da..d6cfb967e2 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -412,6 +412,7 @@
Devam ediyor…
Şu ana kadar %1$d…
+ Şu ana kadar %1$s%% …
Molly, yedek oluşturabilmek için Depolama iznine ihtiyaç duyar, fakat bu izin kalıcı olarak reddedilmiş. Lütfen uygulama ayarları menüsüne girip \"İzinler\" kısmını seçin, ve \"Depolama\"yı etkinleştirin.
Özelleştirilmiş \'%s\' kullanılıyor
@@ -2338,6 +2339,10 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı.
Çoklu seçim
+
+ - %d seçili
+ - %d seçili
+
Kaydet
@@ -2711,6 +2716,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı.
Her iki aygıtı birbirine yakın tutun. Aygıtları kapatmayın ve Molly\'i açık tutun. Aktarımlar uçtan uça şifrelidir.
Şimdiye kadar %1$d ileti…
+ Şu ana kadar %1$s%% ileti…
İptal
Tekrar dene
Aktarımı durdur?
@@ -3427,7 +3433,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı.
Ödemenin işlenmesinde hata
Ödemenin işlenmesinde hata. %1$s
- Rozetin hesabına eklenemedi ama ödediğin ücret alınmış olabilir. Lütfen destek ekibiyle iletişime geç.
+ Rozetiniz hesabınıza eklenemedi fakat ücret ödenmiş olabilir. Lütfen destek ekibiyle iletişime geçin.
Ödemeniz işlenemedi ve sizden ödeme alınmadı. Lütfen tekrar deneyin.
İşlem sürüyor
Rozet eklenemedi
@@ -3438,10 +3444,10 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı.
Abonelik iptal edilemedi
Abonelik iptali için internet bağlantısı gerekir.
Cihazın Google Pay\'i desteklemiyor, bu yüzden abone olup rozet kazanamazsın. Yine de websitemizden bağışta bulunarak Signal\'ı destekleyebilirsin.
- Ağ hatası. Bağlantını kontrol edip tekrar dene.
+ Ağ hatası. Bağlantınızı kontrol edip tekrar deneyin.
Tekrar dene
- Profilinizi isimlendirin
+ Profilinizi adlandırın
Profil adı
@@ -3455,16 +3461,17 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı.
Profili düzenleyin
- Bu isimle bir profil mevcut
+ Bu ada sahip bir profil zaten mevcut
İş
- uyku
+ Uyku
+ Araç sürme
- Dinlenme süresi
+ Dinlenme
- Odaklan
+ Odaklanma
Bir ad olmalı
@@ -3486,7 +3493,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı.
Profili sil
- \"%1$s\" kaldırıldı.
+ \"%1$s\" çıkarıldı.
Geri al
@@ -3510,19 +3517,19 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı.
Tüm bahsetmeleri bildir
- Zamanlama
+ Zamanlayıcı
Tümünü gör
- Zamanlama ekle
+ Zamanlayıcı ekle
- Bu bildirim profilini otomatik olarak aktifleştirmek için bir program kurun.
+ Bu bildirim profilini otomatik olarak etkinleştirmek için zamanlayıcı ayarlayın
- Zamanlama
+ Zamanlayıcı
- Başlat
+ Başlangıç
- Son
+ Bitiş
P
@@ -3564,7 +3571,7 @@ Geçersiz protokol sürümünde anahtar değişim iletisi alındı.
1 saatliğine
- %1$s tarihine kadar
+ Saat %1$s olana kadar
Ayarları görüntüle
diff --git a/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiJsonParserTest.kt b/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiJsonParserTest.kt
index 84bf452f1b..bc2816fb42 100644
--- a/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiJsonParserTest.kt
+++ b/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiJsonParserTest.kt
@@ -51,6 +51,30 @@ private const val SAMPLE_JSON_WITH_OBSOLETE = """
}
"""
+private const val SAMPLE_JSON_WITH_JUMBOS = """
+ {
+ "emoji": {
+ "Places_1": [["0002"], ["0003", "0004", "0005"]],
+ "Places_2": [["0003"], ["0008", "0009", "0000"]],
+ "Foods": [["0001"], ["0002", "0003", "0004"]]
+ },
+ "jumbomoji": {
+ "People_0": ["d83dde00","d83dde03","d83dde04", "d83dde01"],
+ "People_1": ["ad83dde00","ad83dde03","ad83dde04", "ad83dde01"]
+ },
+ "obsolete": [
+ {"obsoleted": "0012", "replace_with": "0023"}
+ ],
+ "metrics": {
+ "raw_height": 64,
+ "raw_width": 64,
+ "per_row": 16
+ },
+ "densities": [ "xhdpi" ],
+ "format": "png"
+ }
+"""
+
private val SAMPLE_JSON_WITHOUT_OBSOLETE_EXPECTED = listOf(
StaticEmojiPageModel(EmojiCategory.FOODS, listOf(Emoji("\u0001"), Emoji("\u0002", "\u0003", "\u0004")), Uri.parse("file:///Foods")),
StaticEmojiPageModel(EmojiCategory.PLACES, listOf(Emoji("\ud83c\udf0d"), Emoji("\u0003", "\u0004", "\u0005")), Uri.parse("file:///Places"))
@@ -128,6 +152,23 @@ class EmojiJsonParserTest {
Assert.assertEquals(result.format, "png")
}
+ @Test
+ fun `Given sample with jumbo, when I parse, then I expect source with jumbo map`() {
+ val result: ParsedEmojiData = EmojiJsonParser.parse(SAMPLE_JSON_WITH_JUMBOS.byteInputStream(), this::uriFactory).getOrThrow()
+
+ val jumboPages = result.jumboPages
+
+ Assert.assertEquals("People_0", jumboPages["d83dde00"])
+ Assert.assertEquals("People_0", jumboPages["d83dde03"])
+ Assert.assertEquals("People_0", jumboPages["d83dde04"])
+ Assert.assertEquals("People_0", jumboPages["d83dde01"])
+
+ Assert.assertEquals("People_1", jumboPages["ad83dde00"])
+ Assert.assertEquals("People_1", jumboPages["ad83dde03"])
+ Assert.assertEquals("People_1", jumboPages["ad83dde04"])
+ Assert.assertEquals("People_1", jumboPages["ad83dde01"])
+ }
+
private fun uriFactory(sprite: String, format: String) = Uri.parse("file:///$sprite")
private fun EmojiPageModel.isSameAs(other: EmojiPageModel) =
diff --git a/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiSourceTest.kt b/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiSourceTest.kt
index 33d270b374..3e213b7afe 100644
--- a/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiSourceTest.kt
+++ b/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiSourceTest.kt
@@ -10,7 +10,7 @@ class EmojiSourceTest {
@Test
fun `Given a bunch of data pages with max value 100100, when I get the maxEmojiLength, then I expect 6`() {
- val emojiDataFake = ParsedEmojiData(EmojiMetrics(-1, -1, -1), listOf(), "png", listOf(), dataPages = generatePages(), listOf())
+ val emojiDataFake = ParsedEmojiData(EmojiMetrics(-1, -1, -1), listOf(), "png", listOf(), dataPages = generatePages(), emptyMap(), listOf())
val testSubject = EmojiSource(0f, emojiDataFake) { uri -> EmojiPage.Disk(uri) }
Assert.assertEquals(6, testSubject.maxEmojiLength)
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java
index a29fee16eb..782f294e2a 100644
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java
@@ -1623,7 +1623,7 @@ private SendMessageResult sendMessage(SignalServiceAddress recipient,
if (!unidentifiedAccess.isPresent()) {
try {
SendMessageResponse response = new MessagingService.SendResponseProcessor<>(messagingService.send(messages, Optional.absent()).blockingGet()).getResultOrThrow();
- return SendMessageResult.success(recipient, messages.getDevices(), false, response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent());
+ return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent());
} catch (WebSocketUnavailableException e) {
Log.i(TAG, "[sendMessage][" + timestamp + "] Pipe unavailable, falling back... (" + e.getClass().getSimpleName() + ": " + e.getMessage() + ")");
} catch (IOException e) {
@@ -1633,7 +1633,7 @@ private SendMessageResult sendMessage(SignalServiceAddress recipient,
} else if (unidentifiedAccess.isPresent()) {
try {
SendMessageResponse response = new MessagingService.SendResponseProcessor<>(messagingService.send(messages, unidentifiedAccess).blockingGet()).getResultOrThrow();
- return SendMessageResult.success(recipient, messages.getDevices(), true, response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent());
+ return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent());
} catch (WebSocketUnavailableException e) {
Log.i(TAG, "[sendMessage][" + timestamp + "] Unidentified pipe unavailable, falling back... (" + e.getClass().getSimpleName() + ": " + e.getMessage() + ")");
} catch (IOException e) {
@@ -1648,7 +1648,7 @@ private SendMessageResult sendMessage(SignalServiceAddress recipient,
SendMessageResponse response = socket.sendMessage(messages, unidentifiedAccess);
- return SendMessageResult.success(recipient, messages.getDevices(), unidentifiedAccess.isPresent(), response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent());
+ return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent());
} catch (InvalidKeyException ike) {
Log.w(TAG, ike);
@@ -1745,12 +1745,12 @@ private List sendGroupMessage(DistributionId dist
.filter(r -> !r.isSuccess())
.collect(Collectors.toList());
- Set failedAddresses = trueFailures.stream()
- .map(SendMessageResult::getAddress)
- .collect(Collectors.toSet());
+ Set failedAddresses = trueFailures.stream()
+ .map(result -> result.getAddress().getAci())
+ .collect(Collectors.toSet());
List fakeNetworkFailures = recipients.stream()
- .filter(r -> !failedAddresses.contains(r))
+ .filter(r -> !failedAddresses.contains(r.getAci()))
.map(SendMessageResult::networkFailure)
.collect(Collectors.toList());
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java
index f98dcd880f..11e7e63e62 100644
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java
@@ -36,6 +36,7 @@
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.UntrustedIdentityException;
import org.whispersystems.libsignal.groups.GroupCipher;
+import org.whispersystems.libsignal.logging.Log;
import org.whispersystems.libsignal.protocol.CiphertextMessage;
import org.whispersystems.libsignal.protocol.PreKeySignalMessage;
import org.whispersystems.libsignal.protocol.SignalMessage;
@@ -202,9 +203,15 @@ private Plaintext decrypt(SignalServiceEnvelope envelope, byte[] ciphertext)
DecryptionResult result = sealedSessionCipher.decrypt(certificateValidator, ciphertext, envelope.getServerReceivedTimestamp());
SignalServiceAddress resultAddress = new SignalServiceAddress(ACI.parseOrThrow(result.getSenderUuid()), result.getSenderE164());
Optional groupId = result.getGroupId();
+ boolean needsReceipt = true;
+
+ if (envelope.hasSourceUuid()) {
+ Log.w(TAG, "[" + envelope.getTimestamp() + "] Received a UD-encrypted message sent over an identified channel. Marking as needsReceipt=false");
+ needsReceipt = false;
+ }
paddedMessage = result.getPaddedMessage();
- metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), true, envelope.getServerGuid(), groupId);
+ metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), needsReceipt, envelope.getServerGuid(), groupId);
} else {
throw new InvalidMetadataMessageException("Unknown type: " + envelope.getType());
}
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java
index cc11b22f6b..0655ba734f 100644
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java
@@ -2,6 +2,7 @@
import com.google.protobuf.ByteString;
+import org.whispersystems.libsignal.logging.Log;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalWebSocket;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
@@ -52,9 +53,11 @@ public Single> send(OutgoingPushMessageList
.build();
ResponseMapper responseMapper = DefaultResponseMapper.extend(SendMessageResponse.class)
- .withResponseMapper((status, body, getHeader) -> {
- SendMessageResponse sendMessageResponse = Util.isEmpty(body) ? new SendMessageResponse(false)
+ .withResponseMapper((status, body, getHeader, unidentified) -> {
+ SendMessageResponse sendMessageResponse = Util.isEmpty(body) ? new SendMessageResponse(false, unidentified)
: JsonUtil.fromJsonResponse(body, SendMessageResponse.class);
+ sendMessageResponse.setSentUnidentfied(unidentified);
+
return ServiceResponse.forResult(sendMessageResponse, status, body);
})
.withCustomError(404, (status, body, getHeader) -> new UnregisteredUserException(list.getDestination(), new NotFoundException("not found")))
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java
index b8384ba74a..4586c953cb 100644
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java
@@ -125,7 +125,7 @@ public ProfileResponseMapper(SignalServiceProfile.RequestType requestType, Profi
}
@Override
- public ServiceResponse map(int status, String body, Function getHeader)
+ public ServiceResponse map(int status, String body, Function getHeader, boolean unidentified)
throws MalformedResponseException
{
try {
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponse.java
index d82af3ccd8..c73763c9f9 100644
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponse.java
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponse.java
@@ -11,7 +11,7 @@
import io.reactivex.rxjava3.core.Single;
/**
- * Encapsulates a parsed APi response regardless of where it came from (WebSocket or REST). Not only
+ * Encapsulates a parsed API response regardless of where it came from (WebSocket or REST). Not only
* includes the success result but also any application errors encountered (404s, parsing, etc.) or
* execution errors encountered (IOException, etc.).
*/
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java
index 1930704360..495a304018 100644
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java
@@ -505,7 +505,7 @@ public SendMessageResponse sendMessage(OutgoingPushMessageList bundle, Optional<
try {
String responseText = makeServiceRequest(String.format(MESSAGE_PATH, bundle.getDestination()), "PUT", JsonUtil.toJson(bundle), NO_HEADERS, unidentifiedAccess);
- if (responseText == null) return new SendMessageResponse(false);
+ if (responseText == null) return new SendMessageResponse(false, unidentifiedAccess.isPresent());
else return JsonUtil.fromJson(responseText, SendMessageResponse.class);
} catch (NotFoundException nfe) {
throw new UnregisteredUserException(bundle.getDestination(), nfe);
@@ -516,7 +516,7 @@ public Future submitMessage(OutgoingPushMessageList bundle,
ListenableFuture response = submitServiceRequest(String.format(MESSAGE_PATH, bundle.getDestination()), "PUT", JsonUtil.toJson(bundle), NO_HEADERS, unidentifiedAccess);
return FutureTransformers.map(response, body -> {
- return body == null ? new SendMessageResponse(false)
+ return body == null ? new SendMessageResponse(false, unidentifiedAccess.isPresent())
: JsonUtil.fromJson(body, SendMessageResponse.class);
});
}
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendMessageResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendMessageResponse.java
index ac129f2716..36457ceb48 100644
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendMessageResponse.java
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendMessageResponse.java
@@ -1,16 +1,30 @@
package org.whispersystems.signalservice.internal.push;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
public class SendMessageResponse {
+ @JsonProperty
private boolean needsSync;
+ private boolean sentUnidentfied;
+
public SendMessageResponse() {}
- public SendMessageResponse(boolean needsSync) {
- this.needsSync = needsSync;
+ public SendMessageResponse(boolean needsSync, boolean sentUnidentified) {
+ this.needsSync = needsSync;
+ this.sentUnidentfied = sentUnidentified;
}
public boolean getNeedsSync() {
return needsSync;
}
+
+ public boolean sentUnidentified() {
+ return sentUnidentfied;
+ }
+
+ public void setSentUnidentfied(boolean value) {
+ this.sentUnidentfied = value;
+ }
}
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/DefaultResponseMapper.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/DefaultResponseMapper.java
index e622be739f..3b39dd20e1 100644
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/DefaultResponseMapper.java
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/DefaultResponseMapper.java
@@ -41,12 +41,12 @@ private DefaultResponseMapper(Class clazz, CustomResponseMapper map(int status, String body, Function getHeader) {
+ public ServiceResponse map(int status, String body, Function getHeader, boolean unidentified) {
Throwable applicationError = errorMapper.parseError(status, body, getHeader);
if (applicationError == null) {
try {
if (customResponseMapper != null) {
- return Objects.requireNonNull(customResponseMapper.map(status, body, getHeader));
+ return Objects.requireNonNull(customResponseMapper.map(status, body, getHeader, unidentified));
}
return ServiceResponse.forResult(JsonUtil.fromJsonResponse(body, clazz), status, body);
} catch (MalformedResponseException e) {
@@ -81,6 +81,6 @@ public ResponseMapper build() {
}
public interface CustomResponseMapper {
- ServiceResponse map(int status, String body, Function getHeader) throws MalformedResponseException;
+ ServiceResponse map(int status, String body, Function getHeader, boolean unidentified) throws MalformedResponseException;
}
}
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/ResponseMapper.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/ResponseMapper.java
index 0b202ae65b..1ef4c23075 100644
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/ResponseMapper.java
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/ResponseMapper.java
@@ -15,9 +15,9 @@
* @param - The final type the API response will map into.
*/
public interface ResponseMapper {
- ServiceResponse map(int status, String body, Function getHeader);
+ ServiceResponse map(int status, String body, Function getHeader, boolean unidentified);
default ServiceResponse map(WebsocketResponse response) {
- return map(response.getStatus(), response.getBody(), response::getHeader);
+ return map(response.getStatus(), response.getBody(), response::getHeader, response.isUnidentified());
}
}
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java
index 9185991d12..dc7a883c7a 100644
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java
@@ -262,7 +262,8 @@ public synchronized void onMessage(WebSocket webSocket, ByteString payload) {
if (listener != null) {
listener.onSuccess(new WebsocketResponse(message.getResponse().getStatus(),
new String(message.getResponse().getBody().toByteArray()),
- message.getResponse().getHeadersList()));
+ message.getResponse().getHeadersList(),
+ !credentialsProvider.isPresent()));
if (message.getResponse().getStatus() >= 400) {
healthMonitor.onMessageError(message.getResponse().getStatus(), credentialsProvider.isPresent());
}
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebsocketResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebsocketResponse.java
index 3ee0282f64..9169ff613b 100644
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebsocketResponse.java
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebsocketResponse.java
@@ -10,11 +10,13 @@ public class WebsocketResponse {
private final int status;
private final String body;
private final Map headers;
+ private final boolean unidentified;
- WebsocketResponse(int status, String body, List headers) {
- this.status = status;
- this.body = body;
- this.headers = parseHeaders(headers);
+ WebsocketResponse(int status, String body, List headers, boolean unidentified) {
+ this.status = status;
+ this.body = body;
+ this.headers = parseHeaders(headers);
+ this.unidentified = unidentified;
}
public int getStatus() {
@@ -29,6 +31,10 @@ public String getHeader(String key) {
return headers.get(Preconditions.checkNotNull(key.toLowerCase()));
}
+ public boolean isUnidentified() {
+ return unidentified;
+ }
+
private static Map parseHeaders(List rawHeaders) {
Map headers = new HashMap<>(rawHeaders.size());