Browse Source

Version 4.5-beta6

Threema 5 năm trước cách đây
mục cha
commit
29816f75fb
70 tập tin đã thay đổi với 1620 bổ sung730 xóa
  1. 8 8
      app/build.gradle
  2. 1 1
      app/src/main/AndroidManifest.xml
  3. 357 0
      app/src/main/java/ch/threema/app/NamedFileProvider.java
  4. 0 2
      app/src/main/java/ch/threema/app/ThreemaApplication.java
  5. 1 1
      app/src/main/java/ch/threema/app/activities/AddContactActivity.java
  6. 2 2
      app/src/main/java/ch/threema/app/activities/ContactDetailActivity.java
  7. 18 16
      app/src/main/java/ch/threema/app/activities/GroupDetailActivity.java
  8. 1 1
      app/src/main/java/ch/threema/app/activities/MediaGalleryActivity.java
  9. 96 87
      app/src/main/java/ch/threema/app/activities/RecipientListBaseActivity.java
  10. 1 1
      app/src/main/java/ch/threema/app/activities/SendMediaActivity.java
  11. 11 194
      app/src/main/java/ch/threema/app/activities/ballot/BallotWizardActivity.java
  12. 1 1
      app/src/main/java/ch/threema/app/activities/wizard/WizardRestoreIDActivity.java
  13. 10 2
      app/src/main/java/ch/threema/app/adapters/decorators/ChatAdapterDecorator.java
  14. 3 9
      app/src/main/java/ch/threema/app/adapters/decorators/TextChatAdapterDecorator.java
  15. 10 7
      app/src/main/java/ch/threema/app/adapters/decorators/VideoChatAdapterDecorator.java
  16. 15 4
      app/src/main/java/ch/threema/app/dialogs/MessageDetailDialog.java
  17. 5 0
      app/src/main/java/ch/threema/app/emojis/EmojiMarkupUtil.java
  18. 25 5
      app/src/main/java/ch/threema/app/fragments/ComposeMessageFragment.java
  19. 1 1
      app/src/main/java/ch/threema/app/fragments/MessageSectionFragment.java
  20. 27 3
      app/src/main/java/ch/threema/app/fragments/MyIDFragment.java
  21. 1 1
      app/src/main/java/ch/threema/app/fragments/mediaviews/VideoViewFragment.java
  22. 16 8
      app/src/main/java/ch/threema/app/mediaattacher/MediaAttachActivity.java
  23. 1 1
      app/src/main/java/ch/threema/app/mediaattacher/MediaAttachViewModel.java
  24. 24 30
      app/src/main/java/ch/threema/app/mediaattacher/labeling/ImageLabelingWorker.java
  25. 4 5
      app/src/main/java/ch/threema/app/routines/SynchronizeContactsRoutine.java
  26. 1 1
      app/src/main/java/ch/threema/app/services/FileService.java
  27. 29 21
      app/src/main/java/ch/threema/app/services/FileServiceImpl.java
  28. 9 13
      app/src/main/java/ch/threema/app/services/MessageServiceImpl.java
  29. 3 0
      app/src/main/java/ch/threema/app/services/PreferenceService.java
  30. 1 1
      app/src/main/java/ch/threema/app/services/messageplayer/AudioMessagePlayer.java
  31. 1 1
      app/src/main/java/ch/threema/app/services/messageplayer/FileMessagePlayer.java
  32. 16 0
      app/src/main/java/ch/threema/app/ui/MediaItem.java
  33. 0 2
      app/src/main/java/ch/threema/app/ui/TranscoderView.java
  34. 1 5
      app/src/main/java/ch/threema/app/utils/AndroidContactUtil.java
  35. 20 0
      app/src/main/java/ch/threema/app/utils/AnimationUtil.java
  36. 90 0
      app/src/main/java/ch/threema/app/utils/BallotUtil.java
  37. 7 2
      app/src/main/java/ch/threema/app/utils/EditTextUtil.java
  38. 25 4
      app/src/main/java/ch/threema/app/utils/FileUtil.java
  39. 6 3
      app/src/main/java/ch/threema/app/utils/MessageUtil.java
  40. 3 3
      app/src/main/java/ch/threema/app/utils/QRScannerUtil.java
  41. 35 44
      app/src/main/java/ch/threema/app/video/VideoTranscoder.java
  42. 15 7
      app/src/main/java/ch/threema/app/voip/services/VideoContext.java
  43. 46 33
      app/src/main/java/ch/threema/app/voip/services/VoipCallService.java
  44. 7 3
      app/src/main/java/ch/threema/app/voip/util/VideoCapturerUtil.java
  45. 1 1
      app/src/main/java/ch/threema/app/webclient/activities/SessionsActivity.java
  46. 1 0
      app/src/main/java/ch/threema/app/webclient/converter/MessageState.java
  47. 21 0
      app/src/main/res/drawable/bubble_fade_recv_selector_dark.xml
  48. 21 0
      app/src/main/res/drawable/bubble_fade_recv_selector_light.xml
  49. 21 0
      app/src/main/res/drawable/bubble_fade_send_selector_dark.xml
  50. 21 0
      app/src/main/res/drawable/bubble_fade_send_selector_light.xml
  51. 3 1
      app/src/main/res/layout/conversation_list_item_quote.xml
  52. 17 13
      app/src/main/res/layout/conversation_list_item_send.xml
  53. 28 0
      app/src/main/res/layout/dialog_message_detail.xml
  54. 2 2
      app/src/main/res/layout/item_contact_list.xml
  55. 48 15
      app/src/main/res/values-cs/strings.xml
  56. 2 0
      app/src/main/res/values-de/strings.xml
  57. 40 7
      app/src/main/res/values-es/strings.xml
  58. 41 8
      app/src/main/res/values-fr/strings.xml
  59. 21 1
      app/src/main/res/values-it/strings.xml
  60. 42 9
      app/src/main/res/values-nl-rNL/strings.xml
  61. 38 7
      app/src/main/res/values-pl/strings.xml
  62. 38 5
      app/src/main/res/values-pt-rBR/strings.xml
  63. 68 10
      app/src/main/res/values-rm/strings.xml
  64. 38 5
      app/src/main/res/values-ru/strings.xml
  65. 28 12
      app/src/main/res/values-tr/strings.xml
  66. 116 99
      app/src/main/res/values-zh-rCN/strings.xml
  67. 2 0
      app/src/main/res/values/attrs.xml
  68. 2 0
      app/src/main/res/values/strings.xml
  69. 4 0
      app/src/main/res/values/themes.xml
  70. 2 2
      app/src/store_threema/java/ch/threema/app/activities/DownloadApkActivity.java

+ 8 - 8
app/build.gradle

@@ -75,8 +75,8 @@ android {
         vectorDrawables.useSupportLibrary = true
         applicationId "ch.threema.app"
         testApplicationId 'ch.threema.app.test'
-        versionCode 659
-        versionName "4.5-beta5"
+        versionCode 660
+        versionName "4.5-beta6"
         resValue "string", "version_name_suffix", ""
         resValue "string", "app_name", "Threema"
         resValue "string", "uri_scheme", "threema"
@@ -86,7 +86,7 @@ android {
         resValue "string", "package_name", applicationId
         resValue "string", "contacts_mime_type", "vnd.android.cursor.item/vnd.ch.threema.app.profile"
         resValue "string", "call_mime_type", "vnd.android.cursor.item/vnd.ch.threema.app.call"
-        resValue "integer", "max_group_size", "100"
+        resValue "integer", "max_group_size", "256"
         resValue "string", "shop_download_filename", "Threema-update.apk"
         buildConfigField "String", "CHAT_SERVER_PREFIX", "\"g-\""
         buildConfigField "String", "CHAT_SERVER_IPV6_PREFIX", "\"ds.\""
@@ -141,7 +141,7 @@ android {
         }
         store_threema { }
         store_google_work {
-            versionName "4.5k-beta5"
+            versionName "4.5k-beta6"
             applicationId "ch.threema.app.work"
             testApplicationId 'ch.threema.app.work.test'
             resValue "string", "package_name", applicationId
@@ -178,7 +178,7 @@ android {
             buildConfigField "byte[]", "SERVER_PUBKEY_ALT", "new byte[] {(byte) 0x5a, (byte) 0x98, (byte) 0xf2, (byte) 0x3d, (byte) 0xe6, (byte) 0x56, (byte) 0x05, (byte) 0xd0, (byte) 0x50, (byte) 0xdc, (byte) 0x00, (byte) 0x64, (byte) 0xbe, (byte) 0x07, (byte) 0xdd, (byte) 0xdd, (byte) 0x81, (byte) 0x1d, (byte) 0xa1, (byte) 0x16, (byte) 0xa5, (byte) 0x43, (byte) 0xce, (byte) 0x43, (byte) 0xaa, (byte) 0x26, (byte) 0x87, (byte) 0xd1, (byte) 0x9f, (byte) 0x20, (byte) 0xaf, (byte) 0x3c }"
         }
         sandbox_work {
-            versionName "4.5k-beta5"
+            versionName "4.5k-beta6"
             applicationId "ch.threema.app.sandbox.work"
             testApplicationId 'ch.threema.app.sandbox.work.test'
 
@@ -189,7 +189,7 @@ android {
             resValue "string", "contact_action_url", "threema.id"
             resValue "string", "contacts_mime_type", "vnd.android.cursor.item/vnd.ch.threema.app.work.profile"
             resValue "string", "call_mime_type", "vnd.android.cursor.item/vnd.ch.threema.app.work.call"
-            resValue "integer", "max_group_size", "100"
+            resValue "integer", "max_group_size", "256"
             resValue "string", "shop_download_filename", ""
 
             buildConfigField "String", "CHAT_SERVER_PREFIX", "\"w-\""
@@ -208,7 +208,7 @@ android {
             ]
         }
         red { // Essentially like sandbox work, but with a different icon and accent color, used for internal testing
-            versionName "4.5r-beta5"
+            versionName "4.5r-beta6"
             applicationId "ch.threema.app.red"
             testApplicationId 'ch.threema.app.red.test'
 
@@ -219,7 +219,7 @@ android {
             resValue "string", "contact_action_url", "threema.id"
             resValue "string", "contacts_mime_type", "vnd.android.cursor.item/vnd.ch.threema.app.redwork.profile"
             resValue "string", "call_mime_type", "vnd.android.cursor.item/vnd.ch.threema.app.redwork.call"
-            resValue "integer", "max_group_size", "100"
+            resValue "integer", "max_group_size", "256"
             resValue "string", "shop_download_filename", ""
 
             buildConfigField "String", "CHAT_SERVER_PREFIX", "\"w-\""

+ 1 - 1
app/src/main/AndroidManifest.xml

@@ -942,7 +942,7 @@
 
 		<!-- content providers -->
 		<provider
-			android:name="androidx.core.content.FileProvider"
+			android:name=".NamedFileProvider"
 			android:authorities="${applicationId}.fileprovider"
 			android:exported="false"
 			android:grantUriPermissions="true">

+ 357 - 0
app/src/main/java/ch/threema/app/NamedFileProvider.java

@@ -0,0 +1,357 @@
+/*  _____ _
+ * |_   _| |_  _ _ ___ ___ _ __  __ _
+ *   | | | ' \| '_/ -_) -_) '  \/ _` |_
+ *   |_| |_||_|_| \___\___|_|_|_\__,_(_)
+ *
+ * Threema for Android
+ * Copyright (c) 2019-2021 Threema GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package ch.threema.app;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.OpenableColumns;
+import android.text.TextUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.collection.SimpleArrayMap;
+import androidx.core.content.ContextCompat;
+import androidx.core.content.FileProvider;
+import ch.threema.app.utils.TestUtil;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+/**
+ * This is a copy of androidx.core.content.FileProvider that adds the option of providing a filename override.
+ *
+ * The default implementation always uses the actual filename of the file on the file system.
+ * But there are cases when we do not want to use the real filename as it may just be a temporary file with a random name.
+ */
+
+public class NamedFileProvider extends FileProvider {
+	private static final String
+		META_DATA_FILE_PROVIDER_PATHS = "android.support.FILE_PROVIDER_PATHS";
+
+	private static final String TAG_ROOT_PATH = "root-path";
+	private static final String TAG_FILES_PATH = "files-path";
+	private static final String TAG_CACHE_PATH = "cache-path";
+	private static final String TAG_EXTERNAL = "external-path";
+	private static final String TAG_EXTERNAL_FILES = "external-files-path";
+	private static final String TAG_EXTERNAL_CACHE = "external-cache-path";
+	private static final String TAG_EXTERNAL_MEDIA = "external-media-path";
+
+	private static final String ATTR_NAME = "name";
+	private static final String ATTR_PATH = "path";
+
+	private static final File DEVICE_ROOT = new File("/");
+
+	private static final String[] COLUMNS = { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE };
+
+	private PathStrategy mStrategy;
+
+	@GuardedBy("sCache")
+	private static final HashMap<String,PathStrategy> sCache = new HashMap<>();
+	private static final SimpleArrayMap<Uri, String> sUriToDisplayNameMap =	new SimpleArrayMap<>();
+
+	@Override
+	public void attachInfo(@NonNull Context context, @NonNull ProviderInfo info) {
+		super.attachInfo(context, info);
+
+		mStrategy = getPathStrategy(context, info.authority);
+	}
+
+	@Override
+	public Cursor query(@NonNull final Uri uri, String[] projection, final String selection,
+	                    final String[] selectionArgs, final String sortOrder) {
+		if (projection == null) {
+			projection = COLUMNS;
+		}
+
+		final File file = mStrategy.getFileForUri(uri);
+
+		String[] cols = new String[projection.length];
+		Object[] values = new Object[projection.length];
+		int i = 0;
+		for (String col : projection) {
+			if (OpenableColumns.DISPLAY_NAME.equals(col)) {
+				cols[i] = OpenableColumns.DISPLAY_NAME;
+				synchronized (sUriToDisplayNameMap) {
+					if (TestUtil.empty(sUriToDisplayNameMap.get(uri))) {
+						values[i++] = file.getName();
+					} else {
+						values[i++] = sUriToDisplayNameMap.get(uri);
+					}
+				}
+			} else if (OpenableColumns.SIZE.equals(col)) {
+				cols[i] = OpenableColumns.SIZE;
+				values[i++] = file.length();
+			}
+		}
+
+		cols = copyOf(cols, i);
+		values = copyOf(values, i);
+
+		final MatrixCursor cursor = new MatrixCursor(cols, 1);
+		cursor.addRow(values);
+		return cursor;
+	}
+
+	/**
+	 * Return a content URI for a given {@link File}. Specific temporary
+	 * permissions for the content URI can be set with
+	 * {@link Context#grantUriPermission(String, Uri, int)}, or added
+	 * to an {@link Intent} by calling {@link Intent#setData(Uri) setData()} and then
+	 * {@link Intent#setFlags(int) setFlags()}; in both cases, the applicable flags are
+	 * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
+	 * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. A FileProvider can only return a
+	 * <code>content</code> {@link Uri} for file paths defined in their <code>&lt;paths&gt;</code>
+	 * meta-data element. See the Class Overview for more information.
+	 *
+	 * @param context A {@link Context} for the current component.
+	 * @param authority The authority of a {@link FileProvider} defined in a
+	 *            {@code <provider>} element in your app's manifest.
+	 * @param file A {@link File} pointing to the filename for which you want a
+	 * <code>content</code> {@link Uri}.
+	 * @param filename File name to be used for this file. Will be provided to consumers in the DISPLAY_NAME xolumn
+	 * @return A content URI for the file.
+	 * @throws IllegalArgumentException When the given {@link File} is outside
+	 * the paths supported by the provider.
+	 */
+	public static Uri getUriForFile(@NonNull Context context, @NonNull String authority,
+	                                @NonNull File file, @Nullable String filename) {
+		final Uri uri = FileProvider.getUriForFile(context, authority, file);
+		if (!TestUtil.empty(filename)) {
+			synchronized (sUriToDisplayNameMap) {
+				sUriToDisplayNameMap.put(uri, filename);
+			}
+		}
+		return uri;
+	}
+
+	/**
+	 * Strategy for mapping between {@link File} and {@link Uri}.
+	 * <p>
+	 * Strategies must be symmetric so that mapping a {@link File} to a
+	 * {@link Uri} and then back to a {@link File} points at the original
+	 * target.
+	 * <p>
+	 * Strategies must remain consistent across app launches, and not rely on
+	 * dynamic state. This ensures that any generated {@link Uri} can still be
+	 * resolved if your process is killed and later restarted.
+	 *
+	 * @see SimplePathStrategy
+	 */
+	interface PathStrategy {
+		/**
+		 * Return a {@link File} that represents the given {@link Uri}.
+		 */
+		File getFileForUri(Uri uri);
+	}
+
+	/**
+	 * Strategy that provides access to files living under a narrow whitelist of
+	 * filesystem roots. It will throw {@link SecurityException} if callers try
+	 * accessing files outside the configured roots.
+	 * <p>
+	 * For example, if configured with
+	 * {@code addRoot("myfiles", context.getFilesDir())}, then
+	 * {@code context.getFileStreamPath("foo.txt")} would map to
+	 * {@code content://myauthority/myfiles/foo.txt}.
+	 */
+	static class SimplePathStrategy implements PathStrategy {
+		private final HashMap<String, File> mRoots = new HashMap<String, File>();
+
+		SimplePathStrategy(String authority) {
+		}
+
+		/**
+		 * Add a mapping from a name to a filesystem root. The provider only offers
+		 * access to files that live under configured roots.
+		 */
+		void addRoot(String name, File root) {
+			if (TextUtils.isEmpty(name)) {
+				throw new IllegalArgumentException("Name must not be empty");
+			}
+
+			try {
+				// Resolve to canonical path to keep path checking fast
+				root = root.getCanonicalFile();
+			} catch (IOException e) {
+				throw new IllegalArgumentException(
+					"Failed to resolve canonical path for " + root, e);
+			}
+
+			mRoots.put(name, root);
+		}
+
+		@Override
+		public File getFileForUri(Uri uri) {
+			String path = uri.getEncodedPath();
+
+			final int splitIndex = path.indexOf('/', 1);
+			final String tag = Uri.decode(path.substring(1, splitIndex));
+			path = Uri.decode(path.substring(splitIndex + 1));
+
+			final File root = mRoots.get(tag);
+			if (root == null) {
+				throw new IllegalArgumentException("Unable to find configured root for " + uri);
+			}
+
+			File file = new File(root, path);
+			try {
+				file = file.getCanonicalFile();
+			} catch (IOException e) {
+				throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
+			}
+
+			if (!file.getPath().startsWith(root.getPath())) {
+				throw new SecurityException("Resolved path jumped beyond configured root");
+			}
+
+			return file;
+		}
+	}
+
+
+	/**
+	 * Return {@link PathStrategy} for given authority, either by parsing or
+	 * returning from cache.
+	 */
+	private static PathStrategy getPathStrategy(Context context, String authority) {
+		PathStrategy strat;
+		synchronized (sCache) {
+			strat = sCache.get(authority);
+			if (strat == null) {
+				try {
+					strat = parsePathStrategy(context, authority);
+				} catch (IOException e) {
+					throw new IllegalArgumentException(
+						"Failed to parse " + META_DATA_FILE_PROVIDER_PATHS + " meta-data", e);
+				} catch (XmlPullParserException e) {
+					throw new IllegalArgumentException(
+						"Failed to parse " + META_DATA_FILE_PROVIDER_PATHS + " meta-data", e);
+				}
+				sCache.put(authority, strat);
+			}
+		}
+		return strat;
+	}
+
+	/**
+	 * Parse and return {@link PathStrategy} for given authority as defined in
+	 * {@link #META_DATA_FILE_PROVIDER_PATHS} {@code <meta-data>}.
+	 */
+	private static PathStrategy parsePathStrategy(Context context, String authority)
+		throws IOException, XmlPullParserException {
+		final SimplePathStrategy strat = new SimplePathStrategy(authority);
+
+		final ProviderInfo info = context.getPackageManager()
+			.resolveContentProvider(authority, PackageManager.GET_META_DATA);
+		if (info == null) {
+			throw new IllegalArgumentException(
+				"Couldn't find meta-data for provider with authority " + authority);
+		}
+
+		final XmlResourceParser in = info.loadXmlMetaData(
+			context.getPackageManager(), META_DATA_FILE_PROVIDER_PATHS);
+		if (in == null) {
+			throw new IllegalArgumentException(
+				"Missing " + META_DATA_FILE_PROVIDER_PATHS + " meta-data");
+		}
+
+		int type;
+		while ((type = in.next()) != END_DOCUMENT) {
+			if (type == START_TAG) {
+				final String tag = in.getName();
+
+				final String name = in.getAttributeValue(null, ATTR_NAME);
+				String path = in.getAttributeValue(null, ATTR_PATH);
+
+				File target = null;
+				if (TAG_ROOT_PATH.equals(tag)) {
+					target = DEVICE_ROOT;
+				} else if (TAG_FILES_PATH.equals(tag)) {
+					target = context.getFilesDir();
+				} else if (TAG_CACHE_PATH.equals(tag)) {
+					target = context.getCacheDir();
+				} else if (TAG_EXTERNAL.equals(tag)) {
+					target = Environment.getExternalStorageDirectory();
+				} else if (TAG_EXTERNAL_FILES.equals(tag)) {
+					File[] externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null);
+					if (externalFilesDirs.length > 0) {
+						target = externalFilesDirs[0];
+					}
+				} else if (TAG_EXTERNAL_CACHE.equals(tag)) {
+					File[] externalCacheDirs = ContextCompat.getExternalCacheDirs(context);
+					if (externalCacheDirs.length > 0) {
+						target = externalCacheDirs[0];
+					}
+				} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
+					&& TAG_EXTERNAL_MEDIA.equals(tag)) {
+					File[] externalMediaDirs = context.getExternalMediaDirs();
+					if (externalMediaDirs.length > 0) {
+						target = externalMediaDirs[0];
+					}
+				}
+
+				if (target != null) {
+					strat.addRoot(name, buildPath(target, path));
+				}
+			}
+		}
+		return strat;
+	}
+
+	private static File buildPath(File base, String... segments) {
+		File cur = base;
+		for (String segment : segments) {
+			if (segment != null) {
+				cur = new File(cur, segment);
+			}
+		}
+		return cur;
+	}
+
+	private static String[] copyOf(String[] original, int newLength) {
+		final String[] result = new String[newLength];
+		System.arraycopy(original, 0, result, 0, newLength);
+		return result;
+	}
+
+	private static Object[] copyOf(Object[] original, int newLength) {
+		final Object[] result = new Object[newLength];
+		System.arraycopy(original, 0, result, 0, newLength);
+		return result;
+	}
+}

+ 0 - 2
app/src/main/java/ch/threema/app/ThreemaApplication.java

@@ -213,7 +213,6 @@ public class ThreemaApplication extends MultiDexApplication implements DefaultLi
 	public static final String EXTRA_FLIP = "flip";
 	public static final String EXTRA_EXIF_ORIENTATION = "rotateExif";
 	public static final String EXTRA_EXIF_FLIP = "flipExif";
-	public static final String INTENT_DATA_FORWARD_AS_FILE = "forward_as_file";
 	public static final String INTENT_DATA_CHECK_ONLY = "check";
 	public static final String INTENT_DATA_ANIM_CENTER = "itemPos";
 	public static final String INTENT_DATA_PICK_FROM_CAMERA = "useCam";
@@ -305,7 +304,6 @@ public class ThreemaApplication extends MultiDexApplication implements DefaultLi
 				.detectLeakedSqlLiteObjects()
 				.detectLeakedClosableObjects()
 				.penaltyLog()
-				.penaltyDeath()
 				.penaltyListener(Executors.newSingleThreadExecutor(), v -> {
 					logger.info("STRICTMODE VMPolicy: " + v.getCause());
 					logStackTrace(v.getStackTrace());

+ 1 - 1
app/src/main/java/ch/threema/app/activities/AddContactActivity.java

@@ -41,7 +41,7 @@ import java.util.Date;
 
 import androidx.annotation.NonNull;
 import androidx.fragment.app.DialogFragment;
-import ch.threema.app.QRScannerUtil;
+import ch.threema.app.utils.QRScannerUtil;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
 import ch.threema.app.dialogs.GenericAlertDialog;

+ 2 - 2
app/src/main/java/ch/threema/app/activities/ContactDetailActivity.java

@@ -63,7 +63,7 @@ import androidx.lifecycle.LifecycleOwner;
 import androidx.palette.graphics.Palette;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
-import ch.threema.app.QRScannerUtil;
+import ch.threema.app.utils.QRScannerUtil;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
 import ch.threema.app.adapters.ContactDetailAdapter;
@@ -104,7 +104,7 @@ import ch.threema.base.VerificationLevel;
 import ch.threema.storage.models.ContactModel;
 import ch.threema.storage.models.GroupModel;
 
-import static ch.threema.app.QRScannerUtil.REQUEST_CODE_QR_SCANNER;
+import static ch.threema.app.utils.QRScannerUtil.REQUEST_CODE_QR_SCANNER;
 
 public class ContactDetailActivity extends ThreemaToolbarActivity
 		implements LifecycleOwner,

+ 18 - 16
app/src/main/java/ch/threema/app/activities/GroupDetailActivity.java

@@ -153,6 +153,23 @@ public class GroupDetailActivity extends GroupEditActivity implements SelectorDi
 		}
 	};
 
+	private final AvatarEditView.AvatarEditListener avatarEditViewListener = new AvatarEditView.AvatarEditListener() {
+		@Override
+		public void onAvatarSet(File avatarFile1) {
+			groupDetailViewModel.setAvatarFile(avatarFile1);
+			groupDetailViewModel.setIsAvatarRemoved(false);
+			setScrimColor();
+		}
+
+		@Override
+		public void onAvatarRemoved() {
+			groupDetailViewModel.setAvatarFile(null);
+			groupDetailViewModel.setIsAvatarRemoved(true);
+			avatarEditView.setDefaultAvatar(null, groupModel);
+			setScrimColor();
+		}
+	};
+
 	private class SelectorInfo {
 		public View view;
 		public ContactModel contactModel;
@@ -313,22 +330,7 @@ public class GroupDetailActivity extends GroupEditActivity implements SelectorDi
 				this.avatarEditView.loadAvatarForModel(null, groupModel);
 			}
 		}
-		this.avatarEditView.setListener(new AvatarEditView.AvatarEditListener() {
-			@Override
-			public void onAvatarSet(File avatarFile1) {
-				groupDetailViewModel.setAvatarFile(avatarFile1);
-				groupDetailViewModel.setIsAvatarRemoved(false);
-				setScrimColor();
-			}
-
-			@Override
-			public void onAvatarRemoved() {
-				groupDetailViewModel.setAvatarFile(null);
-				groupDetailViewModel.setIsAvatarRemoved(true);
-				avatarEditView.setDefaultAvatar(null, groupModel);
-				setScrimColor();
-			}
-		});
+		this.avatarEditView.setListener(this.avatarEditViewListener);
 
 		((AppBarLayout) findViewById(R.id.appbar)).addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
 			@Override

+ 1 - 1
app/src/main/java/ch/threema/app/activities/MediaGalleryActivity.java

@@ -620,7 +620,7 @@ public class MediaGalleryActivity extends ThreemaToolbarActivity implements Adap
 			@Override
 			public void complete(File decodedFile) {
 				hideProgressBar(progressBar);
-				messageService.viewMediaMessage(getApplicationContext(), m, fileService.getShareFileUri(decodedFile));
+				messageService.viewMediaMessage(getApplicationContext(), m, fileService.getShareFileUri(decodedFile, null));
 			}
 
 			@Override

+ 96 - 87
app/src/main/java/ch/threema/app/activities/RecipientListBaseActivity.java

@@ -59,6 +59,8 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
 
 import androidx.annotation.AnyThread;
 import androidx.annotation.NonNull;
@@ -69,6 +71,7 @@ import androidx.appcompat.app.ActionBar;
 import androidx.appcompat.widget.SearchView;
 import androidx.core.app.ActivityCompat;
 import androidx.core.app.TaskStackBuilder;
+import androidx.core.content.ContextCompat;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentManager;
 import androidx.fragment.app.FragmentPagerAdapter;
@@ -96,6 +99,7 @@ import ch.threema.app.services.DistributionListService;
 import ch.threema.app.services.FileService;
 import ch.threema.app.services.GroupService;
 import ch.threema.app.services.MessageService;
+import ch.threema.app.services.PreferenceService;
 import ch.threema.app.services.UserService;
 import ch.threema.app.ui.MediaItem;
 import ch.threema.app.ui.SingleToast;
@@ -117,6 +121,7 @@ import ch.threema.storage.models.DistributionListModel;
 import ch.threema.storage.models.GroupModel;
 import ch.threema.storage.models.MessageType;
 import ch.threema.storage.models.data.LocationDataModel;
+import java8.util.concurrent.CompletableFuture;
 
 import static ch.threema.app.activities.SendMediaActivity.MAX_SELECTABLE_IMAGES;
 import static ch.threema.app.ui.MediaItem.TYPE_TEXT;
@@ -161,6 +166,27 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 	private MessageService messageService;
 	private FileService fileService;
 
+	private final Runnable copyFilesRunnable = new Runnable() {
+		@Override
+		public void run() {
+			for (int i = 0; i < mediaItems.size(); i++) {
+				MediaItem mediaItem = mediaItems.get(i);
+				mediaItem.setFilename(FileUtil.getFilenameFromUri(getContentResolver(), mediaItem));
+
+				if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(mediaItem.getUri().getScheme())) {
+					try {
+						File file = fileService.createTempFile("rcpt", null);
+						FileUtil.copyFile(mediaItem.getUri(), file, getContentResolver());
+						mediaItem.setUri(Uri.fromFile(file));
+						mediaItem.setDeleteAfterUse(true);
+					} catch (IOException e) {
+						logger.error("Unable to copy to tmp dir", e);
+					}
+				}
+			}
+		}
+	};
+
 	@Override
 	public boolean onQueryTextSubmit(String query) {
 		// Do something
@@ -363,10 +389,7 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 		if (intent != null) {
 			setIntent(intent);
 
-			// set this flag to prevent file messages with image & video mime types to be forwarded as media
-			boolean isForwardAsFile = false;
 			try {
-				isForwardAsFile = intent.getBooleanExtra(ThreemaApplication.INTENT_DATA_FORWARD_AS_FILE, false);
 				this.hideRecents = intent.getBooleanExtra(ThreemaApplication.INTENT_DATA_HIDE_RECENTS, false);
 				this.multiSelect = intent.getBooleanExtra(INTENT_DATA_MULTISELECT, true);
 			} catch (BadParcelableException e) {
@@ -457,13 +480,6 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 									}
 								}
 
-								// any other mime type
-								if (isForwardAsFile) {
-									// TODO
-									// force forwarding of jpegs as a file if requested
-									type = "x-threema/file";
-								}
-
 								// if text was shared along with the media item, add that too
 								String textIntent = getTextFromIntent(intent);
 								addMediaItem(type, uri, textIntent);
@@ -568,14 +584,20 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 					String type = intent.getType();
 
 					ArrayList<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
-					if (uris != null && uris.size() > 0) {
-						for (Uri uri : uris) {
-							if (uri != null) {
-								String mimeType = FileUtil.getMimeTypeFromUri(this, uri);
-								if (mimeType == null) {
-									mimeType = type;
+					if (uris != null) {
+						for (int i = 0; i < uris.size(); i++) {
+							if (i < MAX_SELECTABLE_IMAGES) {
+								Uri uri = uris.get(i);
+								if (uri != null) {
+									String mimeType = FileUtil.getMimeTypeFromUri(this, uri);
+									if (mimeType == null) {
+										mimeType = type;
+									}
+									addMediaItem(mimeType, uri, null);
 								}
-								addMediaItem(mimeType, uri, null);
+							} else {
+								Toast.makeText(getApplicationContext(), getString(R.string.max_selectable_media_exceeded, MAX_SELECTABLE_IMAGES), Toast.LENGTH_LONG).show();
+								break;
 							}
 						}
 
@@ -752,21 +774,7 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 	 */
 	@WorkerThread
 	private void copySelectedFiles() {
-		for (int i = 0; i < mediaItems.size(); i++) {
-			MediaItem mediaItem = mediaItems.get(i);
-			mediaItem.setFilename(FileUtil.getFilenameFromUri(getContentResolver(), mediaItem));
 
-			if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(mediaItem.getUri().getScheme())) {
-				try {
-					File file = fileService.createTempFile("rcpt", null);
-					FileUtil.copyFile(mediaItem.getUri(), file, getContentResolver());
-					mediaItem.setUri(Uri.fromFile(file));
-					mediaItem.setDeleteAfterUse(true);
-				} catch (IOException e) {
-					logger.error("Unable to copy to tmp dir", e);
-				}
-			}
-		}
 	}
 
 	private void sendSharedMedia(final MessageReceiver[] messageReceivers, final Intent intent) {
@@ -804,14 +812,14 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 						}
 						switch (messageModel.getType()) {
 							case IMAGE:
-								sendMediaMessage(messageReceivers, uri, captionText, MediaItem.TYPE_IMAGE, null, FileData.RENDERING_MEDIA);
+								sendForwardedMedia(messageReceivers, uri, captionText, MediaItem.TYPE_IMAGE, null, FileData.RENDERING_MEDIA, null);
 								break;
 							case VIDEO:
-								sendMediaMessage(messageReceivers, uri, captionText, MediaItem.TYPE_VIDEO, null, FileData.RENDERING_MEDIA);
+								sendForwardedMedia(messageReceivers, uri, captionText, MediaItem.TYPE_VIDEO, null, FileData.RENDERING_MEDIA, null);
 								break;
 							case VOICEMESSAGE:
 								// voice messages should always be forwarded as files in order not to appear to be recorded by the forwarder
-								sendMediaMessage(messageReceivers, uri, captionText, MediaItem.TYPE_FILE, MimeUtil.MIME_TYPE_AUDIO_AAC, FileData.RENDERING_DEFAULT);
+								sendForwardedMedia(messageReceivers, uri, captionText, MediaItem.TYPE_FILE, MimeUtil.MIME_TYPE_AUDIO_AAC, FileData.RENDERING_DEFAULT, null);
 								break;
 							case FILE:
 								int mediaType = MediaItem.TYPE_FILE;
@@ -832,7 +840,7 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 										renderingType = FileData.RENDERING_DEFAULT;
 									}
 								}
-								sendMediaMessage(messageReceivers, uri, captionText, mediaType, mimeType, renderingType);
+								sendForwardedMedia(messageReceivers, uri, captionText, mediaType, mimeType, renderingType, messageModel.getFileData().getFileName());
 								break;
 							case LOCATION:
 								sendLocationMessage(messageReceivers, messageModel.getLocationData());
@@ -886,14 +894,13 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 		}
 	}
 
-	@SuppressLint("StaticFieldLeak")
 	public void prepareForwardingOrSharing(final ArrayList<Object> models) {
 		if (mediaItems.size() > 0 || originalMessageModels.size() > 0) {
 			String recipientName = "";
 
 			if (!((mediaItems.size() == 1 && MimeUtil.isTextFile(mediaItems.get(0).getMimeType()))
-					|| (originalMessageModels.size() == 1 && originalMessageModels.get(0).getType() == MessageType.TEXT))) {
-				for(Object model :models) {
+				|| (originalMessageModels.size() == 1 && originalMessageModels.get(0).getType() == MessageType.TEXT))) {
+				for (Object model : models) {
 					if (recipientName.length() > 0) {
 						recipientName += ", ";
 					}
@@ -928,60 +935,49 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 				} else {
 					// content shared by external apps may be referred to by content URIs. we have to copy these files first in order to be able to access it in another activity
 					String finalRecipientName = recipientName;
-					new AsyncTask<Void, Void, Void>() {
-						@Override
-						protected void onPreExecute() {
-							GenericProgressDialog.newInstance(R.string.importing_files, R.string.please_wait).show(getSupportFragmentManager(), DIALOG_TAG_FILECOPY);
-						}
-
-						@Override
-						protected Void doInBackground(Void... voids) {
-							copySelectedFiles();
-							return null;
-						}
-
-						@Override
-						protected void onPostExecute(Void aVoid) {
-							int numEditableMedia = 0;
-							for (MediaItem mediaItem : mediaItems) {
-								String mimeType = mediaItem.getMimeType();
-								if (MimeUtil.isImageFile(mimeType) || MimeUtil.isVideoFile(mimeType)) {
-									numEditableMedia++;
+					GenericProgressDialog.newInstance(R.string.importing_files, R.string.please_wait).show(getSupportFragmentManager(), DIALOG_TAG_FILECOPY);
+					try {
+						CompletableFuture
+							.runAsync(copyFilesRunnable, Executors.newSingleThreadExecutor())
+							.thenRunAsync(() -> {
+								int numEditableMedia = 0;
+								for (MediaItem mediaItem : mediaItems) {
+									String mimeType = mediaItem.getMimeType();
+									if (MimeUtil.isImageFile(mimeType) || MimeUtil.isVideoFile(mimeType)) {
+										numEditableMedia++;
+									}
 								}
-							}
-
-							DialogUtil.dismissDialog(getSupportFragmentManager(), DIALOG_TAG_FILECOPY, true);
 
-							if (numEditableMedia == mediaItems.size()) { // all files are images or videos
-								int size = mediaItems.size();
-								if (size > MAX_SELECTABLE_IMAGES) {
-									mediaItems.subList(MAX_SELECTABLE_IMAGES, size).clear();
-									Toast.makeText(getApplicationContext(), getString(R.string.max_selectable_media_exceeded, MAX_SELECTABLE_IMAGES),Toast.LENGTH_LONG).show();
-								}
+								DialogUtil.dismissDialog(getSupportFragmentManager(), DIALOG_TAG_FILECOPY, true);
 
-								// all files are either images or videos => redirect to SendMediaActivity
-								recipientMessageReceivers.clear();
-								for (Object model: models) {
-									MessageReceiver messageReceiver = getMessageReceiver(model);
-									if (validateSendingPermission(messageReceiver)) {
-										recipientMessageReceivers.add(messageReceiver);
+								if (numEditableMedia == mediaItems.size()) { // all files are images or videos
+									// all files are either images or videos => redirect to SendMediaActivity
+									recipientMessageReceivers.clear();
+									for (Object model : models) {
+										MessageReceiver messageReceiver = getMessageReceiver(model);
+										if (validateSendingPermission(messageReceiver)) {
+											recipientMessageReceivers.add(messageReceiver);
+										}
 									}
-								}
 
-								if (recipientMessageReceivers.size() > 0) {
-									Intent intent = IntentDataUtil.addMessageReceiversToIntent(new Intent(RecipientListBaseActivity.this, SendMediaActivity.class), recipientMessageReceivers.toArray(new MessageReceiver[0]));
-									intent.putExtra(SendMediaActivity.EXTRA_MEDIA_ITEMS, (ArrayList<MediaItem>) mediaItems);
-									intent.putExtra(ThreemaApplication.INTENT_DATA_TEXT, finalRecipientName);
-									startActivityForResult(intent, ThreemaActivity.ACTIVITY_ID_SEND_MEDIA);
+									if (recipientMessageReceivers.size() > 0) {
+										Intent intent = IntentDataUtil.addMessageReceiversToIntent(new Intent(RecipientListBaseActivity.this, SendMediaActivity.class), recipientMessageReceivers.toArray(new MessageReceiver[0]));
+										intent.putExtra(SendMediaActivity.EXTRA_MEDIA_ITEMS, (ArrayList<MediaItem>) mediaItems);
+										intent.putExtra(ThreemaApplication.INTENT_DATA_TEXT, finalRecipientName);
+										startActivityForResult(intent, ThreemaActivity.ACTIVITY_ID_SEND_MEDIA);
+									}
+								} else {
+									// mixed media
+									ExpandableTextEntryDialog alertDialog = ExpandableTextEntryDialog.newInstance(getString(R.string.really_send, finalRecipientName), R.string.add_caption_hint, null, R.string.send, R.string.cancel, false);
+									alertDialog.setData(models);
+									alertDialog.show(getSupportFragmentManager(), null);
 								}
-							} else {
-								// mixed media
-								ExpandableTextEntryDialog alertDialog = ExpandableTextEntryDialog.newInstance(getString(R.string.really_send, finalRecipientName), R.string.add_caption_hint, null, R.string.send, R.string.cancel, false);
-								alertDialog.setData(models);
-								alertDialog.show(getSupportFragmentManager(), null);
-							}
-						}
-					}.execute();
+							}, ContextCompat.getMainExecutor(getApplicationContext()));
+					} catch (Exception e) {
+						logger.error("Exception", e);
+						finish();
+						return;
+					}
 				}
 				return;
 			}
@@ -1129,7 +1125,7 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 	 */
 
 	@AnyThread
-	private void sendMediaMessage(final MessageReceiver[] messageReceivers, final Uri uri, final String caption, final int type, @Nullable final String mimeType, @FileData.RenderingType final int renderingType) {
+	private void sendForwardedMedia(final MessageReceiver[] messageReceivers, final Uri uri, final String caption, final int type, @Nullable final String mimeType, @FileData.RenderingType final int renderingType, final String filename) {
 		final MediaItem mediaItem = new MediaItem(uri, type);
 		if (mimeType != null) {
 			mediaItem.setMimeType(mimeType);
@@ -1138,6 +1134,19 @@ public class RecipientListBaseActivity extends ThreemaToolbarActivity implements
 			mediaItem.setRenderingType(renderingType);
 		}
 		mediaItem.setCaption(caption);
+		if (!TestUtil.empty(filename)) {
+			mediaItem.setFilename(filename);
+		}
+		if (renderingType == FileData.RENDERING_MEDIA) {
+			if (type == MediaItem.TYPE_VIDEO) {
+				// do not re-transcode forwarded videos
+				mediaItem.setVideoSize(PreferenceService.VideoSize_ORIGINAL);
+			}
+			else if (type == MediaItem.TYPE_IMAGE) {
+				// do not scale forwarded images
+				mediaItem.setImageScale(PreferenceService.ImageScale_ORIGINAL);
+			}
+		}
 		messageService.sendMediaAsync(Collections.singletonList(mediaItem), Arrays.asList(messageReceivers));
 	}
 

+ 1 - 1
app/src/main/java/ch/threema/app/activities/SendMediaActivity.java

@@ -739,7 +739,7 @@ public class SendMediaActivity extends ThreemaToolbarActivity implements
 			}
 
 			cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
-			cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileService.getShareFileUri(cameraFile));
+			cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileService.getShareFileUri(cameraFile, null));
 			cameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
 			requestCode = ThreemaActivity.ACTIVITY_ID_PICK_CAMERA_EXTERNAL;
 		}

+ 11 - 194
app/src/main/java/ch/threema/app/activities/ballot/BallotWizardActivity.java

@@ -21,11 +21,9 @@
 
 package ch.threema.app.activities.ballot;
 
-import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.content.Intent;
 import android.content.res.Configuration;
-import android.os.AsyncTask;
 import android.os.Bundle;
 import android.view.View;
 import android.widget.Button;
@@ -36,12 +34,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.lang.ref.WeakReference;
-import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Random;
 
-import androidx.annotation.NonNull;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentManager;
 import androidx.fragment.app.FragmentStatePagerAdapter;
@@ -49,30 +44,20 @@ import androidx.viewpager.widget.ViewPager;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
 import ch.threema.app.activities.ThreemaActivity;
-import ch.threema.app.collections.Functional;
-import ch.threema.app.collections.IPredicateNonNull;
 import ch.threema.app.exceptions.NotAllowedException;
 import ch.threema.app.managers.ServiceManager;
-import ch.threema.app.messagereceiver.ContactMessageReceiver;
-import ch.threema.app.messagereceiver.GroupMessageReceiver;
 import ch.threema.app.messagereceiver.MessageReceiver;
-import ch.threema.app.routines.UpdateFeatureLevelRoutine;
 import ch.threema.app.services.ContactService;
 import ch.threema.app.services.GroupService;
 import ch.threema.app.services.MessageService;
 import ch.threema.app.services.ballot.BallotService;
 import ch.threema.app.ui.StepPagerStrip;
+import ch.threema.app.utils.BallotUtil;
 import ch.threema.app.utils.ConfigUtils;
 import ch.threema.app.utils.IntentDataUtil;
-import ch.threema.app.utils.LoadingUtil;
-import ch.threema.app.utils.NameUtil;
-import ch.threema.app.utils.RuntimeUtil;
 import ch.threema.app.utils.TestUtil;
 import ch.threema.base.ThreemaException;
 import ch.threema.client.APIConnector;
-import ch.threema.client.MessageTooLongException;
-import ch.threema.client.ThreemaFeature;
-import ch.threema.storage.models.ContactModel;
 import ch.threema.storage.models.ballot.BallotChoiceModel;
 import ch.threema.storage.models.ballot.BallotModel;
 
@@ -92,9 +77,8 @@ public class BallotWizardActivity extends ThreemaActivity {
 	private ImageView nextButton, copyButton, prevButton;
 	private Button nextText;
 	private MessageReceiver receiver;
-	private BallotModel ballotModel = null;
 
-	private List<BallotChoiceModel> ballotChoiceModelList = new ArrayList<>();
+	private final List<BallotChoiceModel> ballotChoiceModelList = new ArrayList<>();
 	private String ballotTitle;
 	private BallotModel.Type ballotType;
 	private BallotModel.Assessment ballotAssessment;
@@ -102,6 +86,12 @@ public class BallotWizardActivity extends ThreemaActivity {
 	private MessageService messageService;
 
 	private final List<WeakReference<BallotWizardFragment>> fragmentList = new ArrayList<>();
+	private final Runnable createBallotRunnable = new Runnable() {
+		@Override
+		public void run() {
+			BallotUtil.createBallot(receiver, ballotTitle, ballotType, ballotAssessment, ballotChoiceModelList);
+		}
+	};
 
 	@Override
 	protected void onCreate(Bundle savedInstanceState) {
@@ -202,70 +192,6 @@ public class BallotWizardActivity extends ThreemaActivity {
 
 	private void handleIntent() {
 		this.receiver = IntentDataUtil.getMessageReceiverFromIntent(this, getIntent());
-
-		if (this.receiver != null) {
-			this.validateNewBallot();
-		}
-	}
-
-	/**
-	 * Check the Feature Ballot of every user
-	 */
-	private void validateNewBallot() {
-
-		UpdateFeatureLevelRoutine routine = new UpdateFeatureLevelRoutine(this.contactService,
-				this.apiConnector,
-				this.ballotService.getParticipants(this.receiver),
-				new UpdateFeatureLevelRoutine.Request() {
-					@Override
-					public boolean requestToServer(int featureLevel) {
-						return !ThreemaFeature.canBallot(featureLevel);
-					}
-				});
-
-		routine.addStatusResult(new UpdateFeatureLevelRoutine.StatusResult() {
-			@Override
-			public void onFinished(final List<ContactModel> handledContacts) {
-				RuntimeUtil.runOnUiThread(() -> {
-					//ok
-					List<ContactModel> notSupportedContactModels = Functional.filter(handledContacts, new IPredicateNonNull<ContactModel>() {
-						@Override
-						public boolean apply(@NonNull ContactModel type) {
-							return !ThreemaFeature.canBallot(type.getFeatureMask());
-						}
-					});
-
-					if (notSupportedContactModels.size() > 0) {
-						String warning = null;
-						if(notSupportedContactModels.size() == 1) {
-							warning = getString(R.string.ballot_one_contact_not_supported,
-									NameUtil.getDisplayName(notSupportedContactModels.get(0)));
-						}
-						else {
-							warning = getString(R.string.ballot_x_contact_not_supported,
-									notSupportedContactModels.size());
-
-						}
-						Toast.makeText(BallotWizardActivity.this, warning, Toast.LENGTH_LONG).show();
-					}
-				});
-			}
-
-			@Override
-			public void onAbort() {
-				//use the db fields
-				RuntimeUtil.runOnUiThread(() -> {
-					//not ok, continue...
-				});
-			}
-
-			@Override
-			public void onError(final Exception x) {
-			}
-		});
-
-		//start routine
-		new Thread(routine).start();
 	}
 
 	@Override
@@ -300,15 +226,9 @@ public class BallotWizardActivity extends ThreemaActivity {
 				BallotWizardFragment1 fragment = (BallotWizardFragment1) pagerAdapter.instantiateItem(pager, pager.getCurrentItem());
 				fragment.saveUnsavedData();
 				if (this.ballotChoiceModelList.size() > 1) {
-					LoadingUtil.runInAlert(getSupportFragmentManager(),
-							R.string.ballot_create,
-							R.string.please_wait,
-							new Runnable() {
-								@Override
-								public void run() {
-									publishThread();
-								}
-							});
+					ThreemaApplication.sendMessageExecutorService.execute(createBallotRunnable);
+					setResult(RESULT_OK);
+					finish();
 				} else {
 					Toast.makeText(BallotWizardActivity.this, getString(R.string.ballot_answer_count_error), Toast.LENGTH_SHORT).show();
 				}
@@ -398,109 +318,6 @@ public class BallotWizardActivity extends ThreemaActivity {
 				this.identity);
 	}
 
-	@SuppressLint("StaticFieldLeak")
-	private void publishThread() {
-		//create a new model
-		new AsyncTask<Void, Void, Integer>() {
-			@Override
-			protected Integer doInBackground(Void... voids) {
-				try {
-					BallotModel.ChoiceType choiceType = BallotModel.ChoiceType.TEXT;
-
-					if (ballotModel == null) {
-						switch (receiver.getType()) {
-							case MessageReceiver.Type_GROUP:
-								ballotModel = ballotService.create(
-									((GroupMessageReceiver) receiver).getGroup(),
-									ballotTitle,
-									BallotModel.State.TEMPORARY,
-									ballotAssessment,
-									ballotType,
-									choiceType);
-
-								break;
-
-							case MessageReceiver.Type_CONTACT:
-								ballotModel = ballotService.create(
-									((ContactMessageReceiver) receiver).getContact(),
-									ballotTitle,
-									BallotModel.State.TEMPORARY,
-									ballotAssessment,
-									ballotType,
-									choiceType);
-								break;
-							default:
-								throw new NotAllowedException("not allowed");
-						}
-					} else {
-						ballotModel.setName(ballotTitle);
-						ballotModel.setType(ballotType);
-						ballotModel.setAssessment(ballotAssessment);
-						ballotService.update(ballotModel);
-					}
-
-					//generate ids
-					Random r = new SecureRandom();
-
-					int[] ids = new int[ballotChoiceModelList.size()];
-					for (int n = 0; n < ids.length; n++) {
-						int rId;
-						boolean exists;
-						do {
-							exists = false;
-							rId = Math.abs(r.nextInt());
-							for (int id : ids) {
-								if (id == rId) {
-									exists = true;
-									break;
-								}
-							}
-						}
-						while (exists);
-						ids[n] = rId;
-
-						BallotChoiceModel b = ballotChoiceModelList.get(n);
-						if (b != null) {
-							b.setOrder(n + 1);
-							if (b.getApiBallotChoiceId() <= 0) {
-								b.setApiBallotChoiceId(rId);
-							}
-						}
-					}
-
-					//add choices
-					for (BallotChoiceModel c : ballotChoiceModelList) {
-						ballotService.update(ballotModel, c);
-					}
-
-					try {
-						ballotService.modifyFinished(ballotModel);
-					} catch (MessageTooLongException e) {
-						ballotService.remove(ballotModel);
-						return R.string.message_too_long;
-					}
-
-				} catch (NotAllowedException e) {
-					logger.error("Exception", e);
-				}
-				return null;
-			}
-
-			@Override
-			protected void onPostExecute(Integer error) {
-				if (error != null) {
-					Toast.makeText(BallotWizardActivity.this, error, Toast.LENGTH_LONG).show();
-					setResult(RESULT_CANCELED);
-				} else {
-					Intent intent = new Intent();
-					IntentDataUtil.append(ballotModel, intent);
-					setResult(RESULT_OK, intent);
-				}
-				finish();
-			}
-		}.execute();
-	}
-
 	public void startCopy() {
 		Intent copyIntent = new Intent(this, BallotChooserActivity.class);
 		startActivityForResult(copyIntent, ThreemaActivity.ACTIVITY_ID_COPY_BALLOT);

+ 1 - 1
app/src/main/java/ch/threema/app/activities/wizard/WizardRestoreIDActivity.java

@@ -43,7 +43,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import androidx.annotation.NonNull;
-import ch.threema.app.QRScannerUtil;
+import ch.threema.app.utils.QRScannerUtil;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
 import ch.threema.app.dialogs.GenericProgressDialog;

+ 10 - 2
app/src/main/java/ch/threema/app/adapters/decorators/ChatAdapterDecorator.java

@@ -40,6 +40,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 import androidx.annotation.AttrRes;
+import androidx.annotation.Nullable;
 import androidx.fragment.app.Fragment;
 import ch.threema.app.R;
 import ch.threema.app.cache.ThumbnailCache;
@@ -392,11 +393,18 @@ abstract public class ChatAdapterDecorator extends AdapterDecorator {
 		return TextUtil.highlightMatches(this.getContext(), fullText, filterText, true, false);
 	}
 
-	CharSequence formatTextString(String string, String filterString) {
-		if (TestUtil.empty(string)) {
+	CharSequence formatTextString(@Nullable String string, String filterString) {
+		return formatTextString(string, filterString, -1);
+	}
+
+	CharSequence formatTextString(@Nullable String string, String filterString, int maxLength) {
+		if (TextUtils.isEmpty(string)) {
 			return "";
 		}
 
+		if (maxLength > 0 && string.length() > maxLength) {
+			return highlightMatches(string.substring(0, maxLength - 1), filterString);
+		}
 		return highlightMatches(string, filterString);
 	}
 

+ 3 - 9
app/src/main/java/ch/threema/app/adapters/decorators/TextChatAdapterDecorator.java

@@ -72,14 +72,11 @@ public class TextChatAdapterDecorator extends ChatAdapterDecorator {
 					messageText = quoteContent.bodyText;
 				}
 			} else {
-				holder.bodyTextView.setText(formatTextString(messageText, this.filterString));
+				holder.bodyTextView.setText(formatTextString(messageText, this.filterString, helper.getMaxBubbleTextLength() + 8));
 			}
 
 			if (holder.readOnContainer != null) {
 				if (messageText != null && messageText.length() > helper.getMaxBubbleTextLength()) {
-					if (holder.bodyTextView instanceof EmojiConversationTextView) {
-						((EmojiConversationTextView) holder.bodyTextView).setFade(true);
-					}
 					holder.readOnContainer.setVisibility(View.VISIBLE);
 					holder.readOnButton.setOnClickListener(view -> {
 						Intent intent = new Intent(helper.getFragment().getContext(), TextChatBubbleActivity.class);
@@ -87,9 +84,6 @@ public class TextChatAdapterDecorator extends ChatAdapterDecorator {
 						helper.getFragment().startActivity(intent);
 					});
 				} else {
-					if (holder.bodyTextView instanceof EmojiConversationTextView) {
-						((EmojiConversationTextView) holder.bodyTextView).setFade(false);
-					}
 					holder.readOnContainer.setVisibility(View.GONE);
 					holder.readOnButton.setOnClickListener(null);
 				}
@@ -120,7 +114,7 @@ public class TextChatAdapterDecorator extends ChatAdapterDecorator {
 
 		if (content != null) {
 			if (holder.secondaryTextView instanceof EmojiConversationTextView) {
-				holder.secondaryTextView.setText(formatTextString(content.quotedText, this.filterString));
+				holder.secondaryTextView.setText(formatTextString(content.quotedText, this.filterString, helper.getMaxQuoteTextLength() + 8));
 				((EmojiConversationTextView) holder.secondaryTextView).setFade(content.quotedText.length() > helper.getMaxQuoteTextLength());
 			}
 
@@ -154,7 +148,7 @@ public class TextChatAdapterDecorator extends ChatAdapterDecorator {
 			}
 
 			if (content.bodyText != null) {
-				holder.bodyTextView.setText(formatTextString(content.bodyText, this.filterString));
+				holder.bodyTextView.setText(formatTextString(content.bodyText, this.filterString, helper.getMaxBubbleTextLength() + 8));
 				holder.bodyTextView.setVisibility(View.VISIBLE);
 			} else {
 				holder.bodyTextView.setText("");

+ 10 - 7
app/src/main/java/ch/threema/app/adapters/decorators/VideoChatAdapterDecorator.java

@@ -178,12 +178,16 @@ public class VideoChatAdapterDecorator extends ChatAdapterDecorator {
 				}
 			}
 
-			long size = this.getMessageModel().getFileData().getFileSize();
-			if (size > 0) {
-				if (duration > 0) {
-					datePrefixString += " (" + Formatter.formatShortFileSize(getContext(), size) + ")";
-				} else {
-					datePrefixString = Formatter.formatShortFileSize(getContext(), size);
+			if (this.getMessageModel().getFileData().isDownloaded()) {
+				datePrefixString = "";
+			} else {
+				long size = this.getMessageModel().getFileData().getFileSize();
+				if (size > 0) {
+					if (duration > 0) {
+						datePrefixString += " (" + Formatter.formatShortFileSize(getContext(), size) + ")";
+					} else {
+						datePrefixString = Formatter.formatShortFileSize(getContext(), size);
+					}
 				}
 			}
 
@@ -276,7 +280,6 @@ public class VideoChatAdapterDecorator extends ChatAdapterDecorator {
 					@Override
 					public void onStatusUpdate(final int progress) {
 						RuntimeUtil.runOnUiThread(() -> {
-							logger.debug("**** Status update {}", progress);
 							holder.transcoderView.setProgress(progress);
 						});
 					}

+ 15 - 4
app/src/main/java/ch/threema/app/dialogs/MessageDetailDialog.java

@@ -23,6 +23,7 @@ package ch.threema.app.dialogs;
 
 import android.app.Activity;
 import android.os.Bundle;
+import android.text.format.Formatter;
 import android.view.View;
 import android.widget.TextView;
 
@@ -95,6 +96,8 @@ public class MessageDetailDialog extends ThreemaDialogFragment {
 			final TextView messageIdDate = dialogView.findViewById(R.id.messageid_date);
 			final TextView mimeTypeText = dialogView.findViewById(R.id.filetype_text);
 			final TextView mimeTypeMime = dialogView.findViewById(R.id.filetype_mime);
+			final TextView fileSizeText = dialogView.findViewById(R.id.filesize_text);
+			final TextView fileSizeData = dialogView.findViewById(R.id.filesize_data);
 
 			MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity(), getTheme());
 			builder.setView(dialogView);
@@ -162,10 +165,18 @@ public class MessageDetailDialog extends ThreemaDialogFragment {
 					}
 				}
 
-				if (messageModel.getType() == MessageType.FILE && messageModel.getFileData() != null && !TestUtil.empty(messageModel.getFileData().getMimeType())) {
-					mimeTypeMime.setText(messageModel.getFileData().getMimeType());
-					mimeTypeMime.setVisibility(View.VISIBLE);
-					mimeTypeText.setVisibility(View.VISIBLE);
+				if (messageModel.getType() == MessageType.FILE && messageModel.getFileData() != null) {
+					if (!TestUtil.empty(messageModel.getFileData().getMimeType())) {
+						mimeTypeMime.setText(messageModel.getFileData().getMimeType());
+						mimeTypeMime.setVisibility(View.VISIBLE);
+						mimeTypeText.setVisibility(View.VISIBLE);
+					}
+
+					if (messageModel.getFileData().getFileSize() > 0) {
+						fileSizeData.setText(Formatter.formatShortFileSize(getContext(), messageModel.getFileData().getFileSize()));
+						fileSizeData.setVisibility(View.VISIBLE);
+						fileSizeText.setVisibility(View.VISIBLE);
+					}
 				}
 
 				if (!TestUtil.empty(messageModel.getApiMessageId())) {

+ 5 - 0
app/src/main/java/ch/threema/app/emojis/EmojiMarkupUtil.java

@@ -31,6 +31,9 @@ import android.text.style.RelativeSizeSpan;
 import android.util.Pair;
 import android.widget.TextView;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.util.ArrayList;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -46,6 +49,8 @@ import ch.threema.app.utils.ConfigUtils;
 import ch.threema.app.utils.NameUtil;
 
 public class EmojiMarkupUtil {
+	private static final Logger logger = LoggerFactory.getLogger(EmojiMarkupUtil.class);
+
 	private static final int LARGE_EMOJI_SCALE_FACTOR = 2;
 	private static final int LARGE_EMOJI_THRESHOLD = 3;
 	public static final String MENTION_INDICATOR = "@";

+ 25 - 5
app/src/main/java/ch/threema/app/fragments/ComposeMessageFragment.java

@@ -1521,7 +1521,17 @@ public class ComposeMessageFragment extends Fragment implements
 						if (isQuotePanelShown() && abstractMessageModel.equals(quoteInfo.messageModel)) {
 							closeQuoteMode();
 						} else {
-							startQuoteMode(abstractMessageModel);
+							startQuoteMode(abstractMessageModel, new Runnable() {
+								@Override
+								public void run() {
+									RuntimeUtil.runOnUiThread(new Runnable() {
+										@Override
+										public void run() {
+											EditTextUtil.showSoftKeyboard(messageText);
+										}
+									});
+								}
+							});
 						}
 					}
 				}
@@ -3140,9 +3150,13 @@ public class ComposeMessageFragment extends Fragment implements
 					@Override
 					public void complete(File decryptedFile) {
 						if (decryptedFile != null) {
+							String filename = null;
+							if (messageModel.getType() == MessageType.FILE) {
+								filename = messageModel.getFileData().getFileName();
+							}
 							messageService.shareMediaMessages(activity,
 									new ArrayList<>(Collections.singletonList(messageModel)),
-									new ArrayList<>(Collections.singletonList(fileService.getShareFileUri(decryptedFile))));
+									new ArrayList<>(Collections.singletonList(fileService.getShareFileUri(decryptedFile, filename))));
 						} else {
 							messageService.shareTextMessage(activity, messageModel);
 						}
@@ -3167,7 +3181,7 @@ public class ComposeMessageFragment extends Fragment implements
 		}
 	}
 
-	private void startQuoteMode(AbstractMessageModel messageModel) {
+	private void startQuoteMode(AbstractMessageModel messageModel, Runnable onFinishRunnable) {
 		if (messageModel == null) {
 			messageModel = selectedMessages.get(0);
 		}
@@ -3218,7 +3232,7 @@ public class ComposeMessageFragment extends Fragment implements
 				}
 			}
 
-			AnimationUtil.expand(quoteInfo.quotePanel);
+			AnimationUtil.expand(quoteInfo.quotePanel, onFinishRunnable);
 		}
 	}
 
@@ -3457,6 +3471,12 @@ public class ComposeMessageFragment extends Fragment implements
 		if (TestUtil.required(this.blockMenuItem, this.blackListIdentityService, this.contactModel)) {
 			boolean state = this.blackListIdentityService.has(this.contactModel.getIdentity());
 			this.blockMenuItem.setTitle(state ? getString(R.string.unblock_contact) : getString(R.string.block_contact));
+			this.blockMenuItem.setShowAsAction(state ? MenuItem.SHOW_AS_ACTION_ALWAYS : MenuItem.SHOW_AS_ACTION_NEVER);
+			this.mutedMenuItem.setShowAsAction(state ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM);
+			this.mutedMenuItem.setVisible(!state);
+
+			this.callItem.setShowAsAction(state ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_ALWAYS);
+
 			updateVoipCallMenuItem(!state);
 		}
 	}
@@ -3945,7 +3965,7 @@ public class ComposeMessageFragment extends Fragment implements
 					mode.finish();
 					break;
 				case R.id.menu_message_quote:
-					startQuoteMode(null);
+					startQuoteMode(null, null);
 					mode.finish();
 					break;
 				case R.id.menu_show_text:

+ 1 - 1
app/src/main/java/ch/threema/app/fragments/MessageSectionFragment.java

@@ -753,7 +753,7 @@ public class MessageSectionFragment extends MainFragment
 						intent.setType(MimeUtil.MIME_TYPE_ZIP);
 						intent.putExtra(Intent.EXTRA_SUBJECT, getResources().getString(R.string.share_subject) + " " + conversationModel.getReceiver().getDisplayName());
 						intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.chat_history_attached) + "\n\n" + getString(R.string.share_conversation_body));
-						intent.putExtra(Intent.EXTRA_STREAM, fileService.getShareFileUri(tempMessagesFile));
+						intent.putExtra(Intent.EXTRA_STREAM, fileService.getShareFileUri(tempMessagesFile, null));
 						intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 
 						RuntimeUtil.runOnUiThread(() -> {

+ 27 - 3
app/src/main/java/ch/threema/app/fragments/MyIDFragment.java

@@ -155,7 +155,25 @@ public class MyIDFragment extends MainFragment
 
 	private ProfileListener profileListener = new ProfileListener() {
 		@Override
-		public void onAvatarChanged() {}
+		public void onAvatarChanged() {
+			// a profile picture has been set so it's safe to assume user wants others to see his pic
+			if (!isDisabledProfilePicReleaseSettings) {
+				if (preferenceService != null && preferenceService.getProfilePicRelease() == PreferenceService.PROFILEPIC_RELEASE_NOBODY) {
+					preferenceService.setProfilePicRelease(PreferenceService.PROFILEPIC_RELEASE_EVERYONE);
+					RuntimeUtil.runOnUiThread(new Runnable() {
+						@Override
+						public void run() {
+							if (isAdded() && !isDetached() && fragmentView != null) {
+								AppCompatSpinner spinner = fragmentView.findViewById(R.id.picrelease_spinner);
+								if (spinner != null) {
+									spinner.setSelection(preferenceService.getProfilePicRelease());
+								}
+							}
+						}
+					});
+				}
+			}
+		}
 
 		@Override
 		public void onAvatarRemoved() {}
@@ -271,6 +289,9 @@ public class MyIDFragment extends MainFragment
 
 			reloadNickname();
 		}
+
+		ListenerManager.profileListeners.add(this.profileListener);
+
 		return fragmentView;
 	}
 
@@ -278,16 +299,19 @@ public class MyIDFragment extends MainFragment
 	public void onStart() {
 		super.onStart();
 		ListenerManager.smsVerificationListeners.add(this.smsVerificationListener);
-		ListenerManager.profileListeners.add(this.profileListener);
 	}
 
 	@Override
 	public void onStop() {
-		ListenerManager.profileListeners.remove(this.profileListener);
 		ListenerManager.smsVerificationListeners.remove(this.smsVerificationListener);
 		super.onStop();
 	}
 
+	@Override
+	public void onDestroyView() {
+		ListenerManager.profileListeners.remove(this.profileListener);
+		super.onDestroyView();
+	}
 
 	private void updatePendingState(final View fragmentView, boolean force) {
 		logger.debug("*** updatePendingState");

+ 1 - 1
app/src/main/java/ch/threema/app/fragments/mediaviews/VideoViewFragment.java

@@ -142,7 +142,7 @@ public class VideoViewFragment extends AudioFocusSupportingMediaViewFragment imp
 			this.videoViewRef.get().setControllerVisibilityListener(new PlayerControlView.VisibilityListener() {
 				@Override
 				public void onVisibilityChange(int visibility) {
-//					VideoViewFragment.this.showUi(visibility == View.VISIBLE);
+					VideoViewFragment.this.showUi(visibility == View.VISIBLE);
 				}
 			});
 			this.videoViewRef.get().setVisibility(View.GONE);

+ 16 - 8
app/src/main/java/ch/threema/app/mediaattacher/MediaAttachActivity.java

@@ -61,7 +61,6 @@ import androidx.annotation.UiThread;
 import androidx.appcompat.widget.FitWindowsFrameLayout;
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.core.app.ActivityCompat;
-import ch.threema.app.QRScannerUtil;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
 import ch.threema.app.actions.LocationMessageSendAction;
@@ -75,6 +74,7 @@ import ch.threema.app.dialogs.GenericAlertDialog;
 import ch.threema.app.listeners.QRCodeScanListener;
 import ch.threema.app.locationpicker.LocationPickerActivity;
 import ch.threema.app.managers.ListenerManager;
+import ch.threema.app.messagereceiver.DistributionListMessageReceiver;
 import ch.threema.app.messagereceiver.MessageReceiver;
 import ch.threema.app.services.MessageService;
 import ch.threema.app.ui.MediaItem;
@@ -85,6 +85,7 @@ import ch.threema.app.utils.FileUtil;
 import ch.threema.app.utils.IntentDataUtil;
 import ch.threema.app.utils.LocaleUtil;
 import ch.threema.app.utils.MimeUtil;
+import ch.threema.app.utils.QRScannerUtil;
 import ch.threema.app.utils.RuntimeUtil;
 
 import static ch.threema.app.ThreemaApplication.MAX_BLOB_SIZE;
@@ -239,6 +240,10 @@ public class MediaAttachActivity extends MediaSelectionBaseActivity implements V
 			this.attachGalleryButton.setVisibility(View.GONE);
 		}
 
+		if (messageReceiver instanceof DistributionListMessageReceiver) {
+			this.attachBallotButton.setVisibility(View.GONE);
+		}
+
 		if (attachFromExternalCameraButton != null && !CameraUtil.isInternalCameraSupported()) {
 			this.attachFromExternalCameraButton.setVisibility(View.GONE);
 		}
@@ -333,13 +338,16 @@ public class MediaAttachActivity extends MediaSelectionBaseActivity implements V
 				0);
 			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 				if (attachGalleryButton.getVisibility() == View.VISIBLE) {
-					AnimationUtil.bubbleAnimate(attachGalleryButton, 75);
+					AnimationUtil.bubbleAnimate(attachGalleryButton, 25);
+				}
+				AnimationUtil.bubbleAnimate(attachFileButton, 25);
+				AnimationUtil.bubbleAnimate(attachLocationButton, 50);
+				if (attachBallotButton.getVisibility() == View.VISIBLE) {
+					AnimationUtil.bubbleAnimate(attachBallotButton, 50);
 				}
-				AnimationUtil.bubbleAnimate(attachFileButton, 75);
-				AnimationUtil.bubbleAnimate(attachQRButton, 25);
-				AnimationUtil.bubbleAnimate(attachBallotButton, 25);
-				AnimationUtil.bubbleAnimate(attachLocationButton, 25);
 				AnimationUtil.bubbleAnimate(attachContactButton, 75);
+				AnimationUtil.bubbleAnimate(attachQRButton, 75);
+				AnimationUtil.bubbleAnimate(attachFromExternalCameraButton, 100);
 			}
 		}
 	}
@@ -490,7 +498,7 @@ public class MediaAttachActivity extends MediaSelectionBaseActivity implements V
 					finish();
 					break;
 				case REQUEST_CODE_ATTACH_FROM_GALLERY:
-					onEdit(FileUtil.getUrisFromResult(intent));
+					onEdit(FileUtil.getUrisFromResult(intent, getContentResolver()));
 					break;
 				case ThreemaActivity.ACTIVITY_ID_CREATE_BALLOT:
 					// fallthrough
@@ -498,7 +506,7 @@ public class MediaAttachActivity extends MediaSelectionBaseActivity implements V
 					finish();
 					break;
 				case ThreemaActivity.ACTIVITY_ID_PICK_FILE:
-					prepareSendFileMessage(FileUtil.getUrisFromResult(intent));
+					prepareSendFileMessage(FileUtil.getUrisFromResult(intent, getContentResolver()));
 					break;
 			}
 		}

+ 1 - 1
app/src/main/java/ch/threema/app/mediaattacher/MediaAttachViewModel.java

@@ -189,7 +189,7 @@ public class MediaAttachViewModel extends AndroidViewModel {
 			final int totalMediaSize = Functional.filter(allMediaValue, (IPredicateNonNull<MediaAttachItem>) ImageLabelingWorker::mediaCanBeLabeled).size() - failedMediaCount;
 
 			final float labeledRatio = (float) labeledMediaCount / (float) totalMediaSize;
-			if (labeledRatio > 0.9) {
+			if (labeledRatio > 0.8) {
 				// More than 90% labeled. Good enough, but kick off the labeller anyways if we're not at 100%.
 				if (labeledMediaCount < totalMediaSize) {
 					this.startImageLabeler();

+ 24 - 30
app/src/main/java/ch/threema/app/mediaattacher/labeling/ImageLabelingWorker.java

@@ -27,7 +27,6 @@ import android.content.Context;
 import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.SystemClock;
-import android.text.format.DateUtils;
 
 import com.google.android.gms.tasks.Task;
 import com.google.android.gms.tasks.Tasks;
@@ -46,18 +45,17 @@ import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Timer;
-import java.util.TimerTask;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.WorkerThread;
 import androidx.core.content.ContextCompat;
 import androidx.work.ForegroundInfo;
-import androidx.work.WorkManager;
 import androidx.work.Worker;
 import androidx.work.WorkerParameters;
 import ch.threema.app.ThreemaApplication;
@@ -245,8 +243,6 @@ public class ImageLabelingWorker extends Worker {
 			int imageCounter = 0;
 			int unlabeledCounter = 0;
 			int skippedCounter = 0;
-			final Timer lockTimer = new Timer();
-			TimerTask timeoutTask = null;
 
 			for (MediaAttachItem mediaItem : allMediaCache) {
 				// Check whether we were stopped
@@ -255,22 +251,6 @@ public class ImageLabelingWorker extends Worker {
 					break;
 				}
 
-				// Schedule new lock timer for each image to be processed, cancel worker if we get stuck.
-				if (timeoutTask != null) {
-					timeoutTask.cancel();
-				}
-
-				timeoutTask = new TimerTask() {
-					@Override
-					public void run() {
-						logger.debug("cancel image labeling worker, timed out");
-						WorkManager.getInstance(getApplicationContext()).cancelUniqueWork(UNIQUE_WORK_NAME);
-						notificationService.showImageLabelingWorkerStuckNotification();
-					}
-				};
-				lockTimer.purge();
-				lockTimer.schedule(timeoutTask, 30 * DateUtils.SECOND_IN_MILLIS);
-
 				// Update notification
 				notificationService.updateImageLabelingProgressNotification(this.progress, this.mediaCount);
 
@@ -295,17 +275,34 @@ public class ImageLabelingWorker extends Worker {
 					unlabeledCounter++;
 
 					// Load image from filesystem
+					Future<InputImage> imageFuture;
 					InputImage image;
 					try {
 						final Uri uri = mediaItem.getUri();
-						// TODO(ANDR-1318): Make this logging less verbose!
 						final String hashedFilename = Utils.byteArrayToSha256HexString(mediaItem.getDisplayName().getBytes(StandardCharsets.UTF_8));
-						logger.info("Loading image {}/{} ({})", this.progress, this.mediaCount, hashedFilename.substring(0, 8));
-						image = InputImage.fromFilePath(this.appContext, uri);
-						logger.info("Loaded image {}/{} ({})", this.progress, this.mediaCount, hashedFilename.substring(0, 8));
+						imageFuture = executor.submit(() -> InputImage.fromFilePath(appContext, uri));
+
+						try {
+							// TODO(ANDR-1318): Make this logging less verbose!
+							logger.info("Loading image {}/{} ({})", this.progress, this.mediaCount, hashedFilename.substring(0, 8));
+							image = imageFuture.get(30, TimeUnit.SECONDS);    // give it a timeout of 30s, otherwise skip and remember bad item
+							logger.info("Loaded image {}/{} ({})", this.progress, this.mediaCount, hashedFilename.substring(0, 8));
+						} catch (TimeoutException e) {
+							imageFuture.cancel(true);
+							logger.info("Item " + hashedFilename.substring(0, 8) + " cannot be labeled due to timeout");
+							failedMediaDAO.insert(new FailedMediaItemEntity(mediaItem.getId(), System.currentTimeMillis()));
+							skippedCounter++;
+							continue;
+						}
+
+						if (image.getHeight() < 32 || image.getWidth() < 32 ) {
+							logger.info("Item " + hashedFilename.substring(0, 8) + " cannot be labeled due to tiny size.");
+							failedMediaDAO.insert(new FailedMediaItemEntity(mediaItem.getId(), System.currentTimeMillis()));
+							skippedCounter++;
+							continue;
+						}
 					} catch (Exception e) {
 						logger.warn("Exception, could not generate input image from file path: {}", e.getMessage());
-						logger.info("Unable to load Item " + mediaItem.getId() + ". Adding to list of failed items");
 
 						failedMediaDAO.insert(new FailedMediaItemEntity(mediaItem.getId(), System.currentTimeMillis()));
 						if (e.getCause() != null) {
@@ -349,9 +346,6 @@ public class ImageLabelingWorker extends Worker {
 				}
 			}
 
-			timeoutTask.cancel();
-			lockTimer.purge();
-
 			// Update notification
 			notificationService.updateImageLabelingProgressNotification(this.progress, this.mediaCount);
 

+ 4 - 5
app/src/main/java/ch/threema/app/routines/SynchronizeContactsRoutine.java

@@ -171,12 +171,11 @@ public class SynchronizeContactsRoutine implements Runnable {
 			Map<String, APIConnector.MatchIdentityResult> foundIds = this.apiConnector.matchIdentities(
 					emails, phoneNumbers, this.localeService.getCountryIsoCode(), false, identityStore, matchTokenStore);
 
-			final List<String> preSynchronizedIdentities = new ArrayList<String>();
+			final List<String> preSynchronizedIdentities = new ArrayList<>();
 
 			if(this.fullSync()) {
 				List<String> synchronizedIdentities = this.contactService.getSynchronizedIdentities();
 				if (synchronizedIdentities != null) {
-					preSynchronizedIdentities.clear();
 					preSynchronizedIdentities.addAll(synchronizedIdentities);
 				}
 			}
@@ -190,18 +189,18 @@ public class SynchronizeContactsRoutine implements Runnable {
 					}
 					return;
 				}
+
+				// Do not add own ID as contact
 				if (TestUtil.compare(id.getKey(), this.userService.getIdentity())) {
-					//do not save me as contact
 					continue;
 				}
 
+				// Do not sync contacts on exclude list
 				if(this.excludedSyncList != null && this.excludedSyncList.has(id.getKey())) {
-					//do not sync id
 					continue;
 				}
 
 				if(this.processingIdentities != null && this.processingIdentities.size() > 0 && !this.processingIdentities.contains(id.getKey())) {
-					//do not sync contact!
 					continue;
 				}
 

+ 1 - 1
app/src/main/java/ch/threema/app/services/FileService.java

@@ -323,7 +323,7 @@ public interface FileService {
 	 */
 	Uri copyToShareFile(AbstractMessageModel currentModel, File decodedFile);
 
-	Uri getShareFileUri(File destFile);
+	Uri getShareFileUri(File destFile, String filename);
 
 	long getInternalStorageUsage();
 

+ 29 - 21
app/src/main/java/ch/threema/app/services/FileServiceImpl.java

@@ -80,6 +80,7 @@ import androidx.appcompat.content.res.AppCompatResources;
 import androidx.core.content.FileProvider;
 import androidx.preference.PreferenceManager;
 import ch.threema.app.BuildConfig;
+import ch.threema.app.NamedFileProvider;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
 import ch.threema.app.cache.ThumbnailCache;
@@ -517,7 +518,7 @@ public class FileServiceImpl implements FileService {
 	public boolean removeMessageFiles(AbstractMessageModel messageModel, boolean withThumbnails) {
 		boolean success = false;
 
-		File messageFile = this.getMessageFile(messageModel, false);
+		File messageFile = this.getMessageFile(messageModel);
 		if(messageFile != null && messageFile.exists()) {
 			if(messageFile.delete()) {
 				success = true;
@@ -565,7 +566,7 @@ public class FileServiceImpl implements FileService {
 		return null;
 	}
 
-	public File getDecryptedMessageFile(AbstractMessageModel messageModel, @Nullable String filename) throws Exception {
+	public File getDecryptedMessageFile(@NonNull AbstractMessageModel messageModel, @Nullable String filename) throws Exception {
 		if (filename == null) {
 			return getDecryptedMessageFile(messageModel);
 		}
@@ -574,7 +575,7 @@ public class FileServiceImpl implements FileService {
 		if (is != null) {
 			FileOutputStream fos = null;
 			try {
-				File decrypted = new File(ConfigUtils.useContentUris() ? this.getTempPath() : this.getExtTmpPath(), filename);
+				File decrypted = new File(ConfigUtils.useContentUris() ? this.getTempPath() : this.getExtTmpPath(), messageModel.getApiMessageId() + "-" + filename);
 				fos = new FileOutputStream(decrypted);
 
 				IOUtils.copy(is, fos);
@@ -774,14 +775,6 @@ public class FileServiceImpl implements FileService {
 		}
 	}
 
-	private File getMessageFile(AbstractMessageModel messageModel, boolean shouldExist) {
-		String uid = this.convert(messageModel.getUid());
-		if (TestUtil.empty(uid)) {
-			return null;
-		}
-		return new File(getAppDataPathAbsolute(), "." + uid);
-	}
-
 	private String convert(String uid) {
 		if (TestUtil.empty(uid)) {
 			return uid;
@@ -804,7 +797,11 @@ public class FileServiceImpl implements FileService {
 
 	@Override
 	public File getMessageFile(AbstractMessageModel messageModel) {
-		return getMessageFile(messageModel, true);
+		String uid = this.convert(messageModel.getUid());
+		if (TestUtil.empty(uid)) {
+			return null;
+		}
+		return new File(getAppDataPathAbsolute(), "." + uid);
 	}
 
 	private File getMessageThumbnail(AbstractMessageModel messageModel) {
@@ -839,7 +836,7 @@ public class FileServiceImpl implements FileService {
 			return false;
 		}
 
-		File messageFile = this.getMessageFile(messageModel, false);
+		File messageFile = this.getMessageFile(messageModel);
 
 		if(messageFile == null) {
 			return false;
@@ -1255,27 +1252,38 @@ public class FileServiceImpl implements FileService {
 	}
 
 	@Override
-	public Uri copyToShareFile(AbstractMessageModel model, File srcFile) {
+	public Uri copyToShareFile(AbstractMessageModel messageModel, File srcFile) {
 		// copy file to public dir
-		if (model != null) {
+		if (messageModel != null) {
 			if (srcFile != null && srcFile.exists()) {
-				String destFilePrefix = FileUtil.getMediaFilenamePrefix(model);
-				String destFileExtension = getMediaFileExtension(model);
+				String destFilePrefix = FileUtil.getMediaFilenamePrefix(messageModel);
+				String destFileExtension = getMediaFileExtension(messageModel);
 				File destFile = copyUriToTempFile(Uri.fromFile(srcFile), destFilePrefix, destFileExtension, !ConfigUtils.useContentUris());
 
-				return getShareFileUri(destFile);
+				String filename = null;
+				if (messageModel.getType() == MessageType.FILE) {
+					filename = messageModel.getFileData().getFileName();
+				}
+
+				return getShareFileUri(destFile, filename);
 			}
 		}
 		return null;
 	}
 
+	/**
+	 * Get an Uri for the destination file that can be shared to other apps. On Android 5+ our own content provider will be used to serve the file.
+	 * @param destFile File to get an Uri for
+	 * @param filename Desired filename for this file. Can be different from the filename of destFile
+	 * @return Uri (Content Uri on Android 5+, File Uri otherwise)
+	 */
 	@Override
-	public Uri getShareFileUri(File destFile) {
+	public Uri getShareFileUri(@NonNull File destFile, @Nullable String filename) {
 		if (destFile != null) {
 			// see https://code.google.com/p/android/issues/detail?id=76683
 			if (ConfigUtils.useContentUris()) {
 				/* content uri */
-				return FileProvider.getUriForFile(ThreemaApplication.getAppContext(), ThreemaApplication.getAppContext().getPackageName() + ".fileprovider", destFile);
+				return NamedFileProvider.getUriForFile(ThreemaApplication.getAppContext(), ThreemaApplication.getAppContext().getPackageName() + ".fileprovider", destFile, filename);
 			} else {
 				/* file uri */
 				return Uri.fromFile(destFile);
@@ -1341,7 +1349,7 @@ public class FileServiceImpl implements FileService {
 				}
 
 				if (file != null) {
-					shareFileUris.add(getShareFileUri(file));
+					shareFileUris.add(getShareFileUri(file, null));
 					continue;
 				}
 			} catch (Exception ignore) {

+ 9 - 13
app/src/main/java/ch/threema/app/services/MessageServiceImpl.java

@@ -2971,11 +2971,6 @@ public class MessageServiceImpl implements MessageService {
 
 					intent = new Intent(Intent.ACTION_SEND);
 					intent.putExtra(Intent.EXTRA_STREAM, shareFileUri);
-					if (model.getType() == MessageType.FILE &&
-						model.getFileData() != null &&
-						model.getFileData().getRenderingType() == FileData.RENDERING_DEFAULT) {
-						intent.putExtra(ThreemaApplication.INTENT_DATA_FORWARD_AS_FILE, true);
-					}
 					intent.setType(getMimeTypeString(model));
 					if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(shareFileUri.getScheme())) {
 						intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -3493,9 +3488,6 @@ public class MessageServiceImpl implements MessageService {
 			try {
 				final byte[] contentData = generateContentData(mediaItem, resolvedReceivers, messageModels, fileDataModel);
 
-				// delete temporary file
-				deleteTemporaryFile(mediaItem);
-
 				if (contentData != null) {
 					if (encryptAndSend(resolvedReceivers, messageModels, fileDataModel, thumbnailData, contentData)) {
 						successfulMessageModel = messageModels.get(resolvedReceivers[0]);
@@ -3665,7 +3657,6 @@ public class MessageServiceImpl implements MessageService {
 			case MediaItem.TYPE_VIDEO:
 				// fallthrough
 			case MediaItem.TYPE_VIDEO_CAM:
-				fileDataModel.setFileName(FileUtil.getMediaFilenamePrefix() + ".mp4");
 				// add duration to metadata
 				long trimmedDuration = mediaItem.getDurationMs();
 				if (mediaItem.getEndTimeMs() != TIME_UNDEFINED && (mediaItem.getEndTimeMs() != 0L || mediaItem.getStartTimeMs() != 0L)) {
@@ -3795,7 +3786,7 @@ public class MessageServiceImpl implements MessageService {
 			AbstractMessageModel messageModel = messageModels.get(messageReceiver);
 			if (messageModel == null) {
 				// no messagemodel has been created for this receiver - skip
-				logger.info("Mo MessageCodel could be created for this receiver - skip");
+				logger.info("Mo MessageModel could be created for this receiver - skip");
 				continue;
 			}
 
@@ -4008,7 +3999,7 @@ public class MessageServiceImpl implements MessageService {
 		// rendering type overrides
 		switch (mediaItem.getType()) {
 			case TYPE_VOICEMESSAGE:
-				filename = FileUtil.getDefaultFilename(mediaItem.getMimeType()); // the internal temporary file name is of no use to the recipient
+				filename = FileUtil.getDefaultFilename(mimeType); // the internal temporary file name is of no use to the recipient
 				renderingType = FileData.RENDERING_MEDIA;
 				break;
 			case TYPE_GIF:
@@ -4029,7 +4020,7 @@ public class MessageServiceImpl implements MessageService {
 				} else {
 					// unlike with "real" files we override the filename for regular images with a generic one to prevent privacy leaks
 					// this mimics the behavior of traditional image messages that did not have a filename at all
-					filename = FileUtil.getDefaultFilename(mediaItem.getMimeType()); // the internal temporary file name is of no use to the recipient
+					filename = FileUtil.getDefaultFilename(mimeType); // the internal temporary file name is of no use to the recipient
 				}
 				break;
 		}
@@ -4067,8 +4058,13 @@ public class MessageServiceImpl implements MessageService {
 
 		boolean needsTrimming = videoNeedsTrimming(mediaItem);
 		int targetBitrate;
+		@PreferenceService.VideoSize int desiredVideoSize = preferenceService.getVideoSize();
+		if (mediaItem.getVideoSize() != PreferenceService.VideoSize_DEFAULT) {
+			desiredVideoSize = mediaItem.getVideoSize();
+		}
+
 		try {
-			targetBitrate = VideoConfig.getTargetVideoBitrate(context, mediaItem, preferenceService.getVideoSize());
+			targetBitrate = VideoConfig.getTargetVideoBitrate(context, mediaItem, desiredVideoSize);
 		} catch (ThreemaException e) {
 			logger.error("Error getting target bitrate", e);
 			// skip this MediaItem

+ 3 - 0
app/src/main/java/ch/threema/app/services/PreferenceService.java

@@ -53,6 +53,9 @@ public interface PreferenceService {
 	int ImageScale_ORIGINAL = 4;
 	int ImageScale_SEND_AS_FILE = 5;
 
+	@IntDef({VideoSize_DEFAULT, VideoSize_SMALL, VideoSize_MEDIUM, VideoSize_ORIGINAL})
+	@interface VideoSize {}
+	int VideoSize_DEFAULT = -1;
 	int VideoSize_SMALL = 0;
 	int VideoSize_MEDIUM = 1;
 	int VideoSize_ORIGINAL = 2;

+ 1 - 1
app/src/main/java/ch/threema/app/services/messageplayer/AudioMessagePlayer.java

@@ -142,7 +142,7 @@ public class AudioMessagePlayer extends MessagePlayer implements AudioManager.On
 
 	private void open(File decryptedFile, final boolean resume) {
 		this.decryptedFile = decryptedFile;
-		final Uri uri = fileService.getShareFileUri(decryptedFile);
+		final Uri uri = fileService.getShareFileUri(decryptedFile, null);
 		this.position = 0;
 
 		logger.debug("open uri = {}", uri);

+ 1 - 1
app/src/main/java/ch/threema/app/services/messageplayer/FileMessagePlayer.java

@@ -90,7 +90,7 @@ public class FileMessagePlayer extends MessagePlayer {
 
 				if (!TestUtil.empty(mimeType) && decryptedFile.exists()) {
 					if (!FileUtil.isImageFile(getMessageModel().getFileData()) && !FileUtil.isVideoFile(getMessageModel().getFileData())) {
-						messageService.viewMediaMessage(getContext(), getMessageModel(), fileService.getShareFileUri(decryptedFile));
+						messageService.viewMediaMessage(getContext(), getMessageModel(), fileService.getShareFileUri(decryptedFile, null));
 					}
 				}
 			});

+ 16 - 0
app/src/main/java/ch/threema/app/ui/MediaItem.java

@@ -36,7 +36,11 @@ import ch.threema.app.utils.MimeUtil;
 import ch.threema.client.file.FileData;
 
 import static ch.threema.app.services.PreferenceService.ImageScale_DEFAULT;
+import static ch.threema.app.services.PreferenceService.VideoSize_DEFAULT;
 
+/**
+ * This class holds all meta information about a media item to be sent
+ */
 public class MediaItem implements Parcelable {
 	@MediaType private int type;
 	private Uri uri;
@@ -51,6 +55,7 @@ public class MediaItem implements Parcelable {
 	private String mimeType;
 	@FileData.RenderingType int renderingType;
 	@PreferenceService.ImageScale private int imageScale; // desired image scale
+	@PreferenceService.VideoSize private int videoSize; // desired video scale factor
 	private String filename;
 	private boolean deleteAfterUse;
 
@@ -119,6 +124,7 @@ public class MediaItem implements Parcelable {
 		this.mimeType = MimeUtil.MIME_TYPE_DEFAULT;
 		this.renderingType = FileData.RENDERING_MEDIA;
 		this.imageScale = ImageScale_DEFAULT;
+		this.videoSize = VideoSize_DEFAULT;
 		this.filename = null;
 		this.deleteAfterUse = false;
 	}
@@ -138,6 +144,7 @@ public class MediaItem implements Parcelable {
 		mimeType = in.readString();
 		renderingType = in.readInt();
 		imageScale = in.readInt();
+		videoSize = in.readInt();
 		filename = in.readString();
 		deleteAfterUse = in.readInt() != 0;
 	}
@@ -157,6 +164,7 @@ public class MediaItem implements Parcelable {
 		dest.writeString(mimeType);
 		dest.writeInt(renderingType);
 		dest.writeInt(imageScale);
+		dest.writeInt(videoSize);
 		dest.writeString(filename);
 		dest.writeInt(deleteAfterUse ? 1 : 0);
 	}
@@ -292,6 +300,14 @@ public class MediaItem implements Parcelable {
 		this.imageScale = imageScale;
 	}
 
+	public @PreferenceService.VideoSize int getVideoSize() {
+		return videoSize;
+	}
+
+	public void setVideoSize(@PreferenceService.VideoSize int videoSize) {
+		this.videoSize = videoSize;
+	}
+
 	public @Nullable String getFilename() {
 		return filename;
 	}

+ 0 - 2
app/src/main/java/ch/threema/app/ui/TranscoderView.java

@@ -116,8 +116,6 @@ public class TranscoderView extends FrameLayout {
 	}
 
 	public void setProgress(int progress) {
-		logger.debug("setProgress");
-
 		if (progress > PROGRESS_MAX) {
 			progress = PROGRESS_MAX;
 		}

+ 1 - 5
app/src/main/java/ch/threema/app/utils/AndroidContactUtil.java

@@ -535,7 +535,7 @@ public class AndroidContactUtil {
 		return false;
 	}
 
-	public boolean updateNameByAndroidContact(ContactModel contactModel) {
+	public boolean updateNameByAndroidContact(@NonNull ContactModel contactModel) {
 		String androidContactId = contactModel.getAndroidContactId();
 
 		if(TestUtil.empty(androidContactId)) {
@@ -544,11 +544,7 @@ public class AndroidContactUtil {
 
 		Uri namedContactUri = ContactUtil.getAndroidContactUri(ThreemaApplication.getAppContext(), contactModel);
 		if(TestUtil.required(contactModel, namedContactUri) && !TestUtil.empty(androidContactId)) {
-			//load name
-
-			//test
 			ContactName contactName = this.getContactName(namedContactUri);
-
 			if(TestUtil.required(contactModel, contactName)) {
 				if(!TestUtil.compare(contactModel.getFirstName(), contactName.firstName)
 						|| !TestUtil.compare(contactModel.getLastName(), contactName.lastName)) {

+ 20 - 0
app/src/main/java/ch/threema/app/utils/AnimationUtil.java

@@ -62,6 +62,10 @@ public class AnimationUtil {
 	private static final Logger logger = LoggerFactory.getLogger(AnimationUtil.class);
 
 	public static void expand(final View v) {
+		expand(v, null);
+	}
+
+	public static void expand(final View v, final Runnable onFinishRunnable) {
 		v.measure(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
 		final int targetHeight = v.getMeasuredHeight();
 
@@ -85,6 +89,22 @@ public class AnimationUtil {
 
 		// 2dp/ms
 		a.setDuration((int) (targetHeight / v.getContext().getResources().getDisplayMetrics().density) * 2);
+		if (onFinishRunnable != null) {
+			a.setAnimationListener(new Animation.AnimationListener() {
+				@Override
+				public void onAnimationStart(Animation animation) {
+				}
+
+				@Override
+				public void onAnimationEnd(Animation animation) {
+					onFinishRunnable.run();
+				}
+
+				@Override
+				public void onAnimationRepeat(Animation animation) {
+				}
+			});
+		}
 		v.startAnimation(a);
 	}
 

+ 90 - 0
app/src/main/java/ch/threema/app/utils/BallotUtil.java

@@ -23,10 +23,15 @@ package ch.threema.app.utils;
 
 import android.content.Context;
 import android.content.Intent;
+import android.widget.Toast;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.security.SecureRandom;
+import java.util.List;
+import java.util.Random;
+
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentManager;
@@ -38,13 +43,18 @@ import ch.threema.app.dialogs.GenericAlertDialog;
 import ch.threema.app.dialogs.SimpleStringAlertDialog;
 import ch.threema.app.exceptions.NotAllowedException;
 import ch.threema.app.managers.ServiceManager;
+import ch.threema.app.messagereceiver.ContactMessageReceiver;
+import ch.threema.app.messagereceiver.GroupMessageReceiver;
+import ch.threema.app.messagereceiver.MessageReceiver;
 import ch.threema.app.services.UserService;
 import ch.threema.app.services.ballot.BallotService;
 import ch.threema.client.ConnectionState;
 import ch.threema.client.MessageTooLongException;
 import ch.threema.storage.models.AbstractMessageModel;
+import ch.threema.storage.models.ballot.BallotChoiceModel;
 import ch.threema.storage.models.ballot.BallotModel;
 
+@SuppressWarnings("rawtypes")
 public class BallotUtil {
 	private static final Logger logger = LoggerFactory.getLogger(BallotUtil.class);
 
@@ -174,4 +184,84 @@ public class BallotUtil {
 			);
 		}
 	}
+
+	public static void createBallot(MessageReceiver receiver, String ballotTitle, BallotModel.Type ballotType, BallotModel.Assessment ballotAssessment, List<BallotChoiceModel> ballotChoiceModelList) {
+		BallotModel ballotModel = null;
+
+		try {
+			BallotService ballotService = ThreemaApplication.getServiceManager().getBallotService();
+			BallotModel.ChoiceType choiceType = BallotModel.ChoiceType.TEXT;
+
+			switch (receiver.getType()) {
+				case MessageReceiver.Type_GROUP:
+					ballotModel = ballotService.create(
+						((GroupMessageReceiver) receiver).getGroup(),
+						ballotTitle,
+						BallotModel.State.TEMPORARY,
+						ballotAssessment,
+						ballotType,
+						choiceType);
+					break;
+
+				case MessageReceiver.Type_CONTACT:
+					ballotModel = ballotService.create(
+						((ContactMessageReceiver) receiver).getContact(),
+						ballotTitle,
+						BallotModel.State.TEMPORARY,
+						ballotAssessment,
+						ballotType,
+						choiceType);
+					break;
+				default:
+					throw new NotAllowedException("not allowed");
+			}
+
+			//generate ids
+			Random r = new SecureRandom();
+
+			int[] ids = new int[ballotChoiceModelList.size()];
+			for (int n = 0; n < ids.length; n++) {
+				int rId;
+				boolean exists;
+				do {
+					exists = false;
+					rId = Math.abs(r.nextInt());
+					for (int id : ids) {
+						if (id == rId) {
+							exists = true;
+							break;
+						}
+					}
+				}
+				while (exists);
+				ids[n] = rId;
+
+				BallotChoiceModel b = ballotChoiceModelList.get(n);
+				if (b != null) {
+					b.setOrder(n + 1);
+					if (b.getApiBallotChoiceId() <= 0) {
+						b.setApiBallotChoiceId(rId);
+					}
+				}
+			}
+
+			//add choices
+			for (BallotChoiceModel c : ballotChoiceModelList) {
+				ballotService.update(ballotModel, c);
+			}
+
+			try {
+				ballotService.modifyFinished(ballotModel);
+				RuntimeUtil.runOnUiThread(() -> Toast.makeText(ThreemaApplication.getAppContext(), R.string.ballot_created_successfully, Toast.LENGTH_LONG).show());
+
+			} catch (MessageTooLongException e) {
+				ballotService.remove(ballotModel);
+				RuntimeUtil.runOnUiThread(() -> Toast.makeText(ThreemaApplication.getAppContext(), R.string.message_too_long, Toast.LENGTH_LONG).show());
+				logger.error("Exception", e);
+			}
+		} catch (Exception e) {
+			RuntimeUtil.runOnUiThread(() -> Toast.makeText(ThreemaApplication.getAppContext(), R.string.error, Toast.LENGTH_LONG).show());
+			logger.error("Exception", e);
+		}
+	}
 }

+ 7 - 2
app/src/main/java/ch/threema/app/utils/EditTextUtil.java

@@ -25,8 +25,12 @@ import android.content.Context;
 import android.view.View;
 import android.view.inputmethod.InputMethodManager;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
 public class EditTextUtil {
-	public static void showSoftKeyboard(View view) {
+	@UiThread
+	public static void showSoftKeyboard(@Nullable View view) {
 		if (view == null) {
 			return;
 		}
@@ -34,7 +38,8 @@ public class EditTextUtil {
 		inputManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
 	}
 
-	public static void hideSoftKeyboard(View view) {
+	@UiThread
+	public static void hideSoftKeyboard(@Nullable View view) {
 		if (view == null) {
 			return;
 		}

+ 25 - 4
app/src/main/java/ch/threema/app/utils/FileUtil.java

@@ -148,7 +148,7 @@ public class FileUtil {
 				cameraIntent.putExtra(CameraActivity.EXTRA_NO_VIDEO, true);
 			} else {
 				cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
-				cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileService.getShareFileUri(cameraFile));
+				cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileService.getShareFileUri(cameraFile, null));
 				cameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
 			}
 
@@ -174,7 +174,7 @@ public class FileUtil {
 		context.startActivity(intent);
 	}
 
-	public static @NonNull ArrayList<Uri> getUrisFromResult(@NonNull Intent intent) {
+	public static @NonNull ArrayList<Uri> getUrisFromResult(@NonNull Intent intent, ContentResolver contentResolver) {
 		Uri returnData = intent.getData();
 		ClipData clipData = null;
 		ArrayList<Uri> uriList = new ArrayList<>();
@@ -185,11 +185,28 @@ public class FileUtil {
 			for (int i = 0; i < clipData.getItemCount(); i++) {
 				ClipData.Item clipItem = clipData.getItemAt(i);
 				if (clipItem != null) {
-					uriList.add(clipItem.getUri());
+					Uri uri = clipItem.getUri();
+					if (uri != null) {
+						if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(uri.getScheme())) {
+							try {
+								contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+							} catch (Exception e) {
+								logger.error("Exception", e);
+							}
+						}
+						uriList.add(uri);
+					}
 				}
 			}
 		} else {
 			if (returnData != null) {
+				if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(returnData.getScheme())) {
+					try {
+						contentResolver.takePersistableUriPermission(returnData, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+					} catch (Exception e) {
+						logger.error("Exception", e);
+					}
+				}
 				uriList.add(returnData);
 			}
 		}
@@ -481,6 +498,10 @@ public class FileUtil {
 			return "";
 		}
 
+		if (messageModel.getFileData().isDownloaded()) {
+			return "";
+		}
+
 		if (fileType != null) {
 			String datePrefixString = Formatter.formatShortFileSize(context, messageModel.getFileData().getFileSize());
 
@@ -642,7 +663,7 @@ public class FileUtil {
 	}
 
 	/**
-	 * Returns the filename of the object referred to by uriby querying the content resolver
+	 * Returns the filename of the object referred to by uri by querying the content resolver
 	 * @param contentResolver ContentResolver
 	 * @param uri Uri pointing at the object
 	 * @return A filename or null if none is found

+ 6 - 3
app/src/main/java/ch/threema/app/utils/MessageUtil.java

@@ -292,11 +292,13 @@ public class MessageUtil {
 						|| fromState == MessageState.DELIVERED;
 			case SENDFAILED:
 				return fromState == MessageState.SENDING
-						|| fromState == MessageState.PENDING;
+						|| fromState == MessageState.PENDING
+						|| fromState == MessageState.TRANSCODING;
 			case SENT:
 				return fromState == MessageState.SENDING
 						|| fromState == MessageState.SENDFAILED
-						|| fromState == MessageState.PENDING;
+						|| fromState == MessageState.PENDING
+						|| fromState == MessageState.TRANSCODING;
 			case USERACK:
 				return true;
 			case USERDEC:
@@ -305,7 +307,8 @@ public class MessageUtil {
 				return fromState == MessageState.SENDFAILED;
 			case SENDING:
 				return fromState == MessageState.SENDFAILED
-						|| fromState == MessageState.PENDING;
+						|| fromState == MessageState.PENDING
+						|| fromState == MessageState.TRANSCODING;
 			default:
 				logger.debug("message state " + toState.toString() + " not handled");
 				return false;

+ 3 - 3
app/src/main/java/ch/threema/app/QRScannerUtil.java → app/src/main/java/ch/threema/app/utils/QRScannerUtil.java

@@ -19,7 +19,7 @@
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
 
-package ch.threema.app;
+package ch.threema.app.utils;
 
 import android.app.Activity;
 import android.content.Intent;
@@ -29,11 +29,11 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import androidx.appcompat.app.AppCompatActivity;
+import ch.threema.app.R;
+import ch.threema.app.ThreemaApplication;
 import ch.threema.app.dialogs.SimpleStringAlertDialog;
 import ch.threema.app.qrscanner.activity.CaptureActivity;
 import ch.threema.app.services.QRCodeService;
-import ch.threema.app.utils.ConfigUtils;
-import ch.threema.app.utils.TestUtil;
 
 public class QRScannerUtil {
 	private static final Logger logger = LoggerFactory.getLogger(QRScannerUtil.class);

+ 35 - 44
app/src/main/java/ch/threema/app/video/VideoTranscoder.java

@@ -90,7 +90,7 @@ public class VideoTranscoder {
 	/**
 	 * How long to wait for the next buffer to become available in microseconds.
 	 */
-	private static final int TIMEOUT_USEC = 2500;
+	private static final int TIMEOUT_USEC = 10000;
 
 	private final Context mContext;
 	private final Uri mSrcUri;
@@ -420,9 +420,9 @@ public class VideoTranscoder {
 		mStats.outputFileSize = Math.round(new File(mOutputFilePath).length() / 1024. / 1000 * 10) / 10.;
 		mStats.timeToTranscode = Math.round(((System.currentTimeMillis() - mStartTime) / 1000.) * 10) / 10.;
 
-		logger.warn("Input file: {}MB", mStats.inputFileSize);
-		logger.warn("Output file: {}MB", mStats.outputFileSize);
-		logger.warn("Time to encode: {}s", mStats.timeToTranscode);
+		logger.info("Input file: {}MB", mStats.inputFileSize);
+		logger.info("Output file: {}MB", mStats.outputFileSize);
+		logger.info("Time to encode: {}s", mStats.timeToTranscode);
 	}
 
 	private void cleanup() throws Exception {
@@ -439,8 +439,7 @@ public class VideoTranscoder {
 				mInputVideoComponent.release();
 			}
 		} catch (Exception e) {
-			logger.error("error while releasing videoExtractor");
-			logger.error("Exception", e);
+			logger.error("error while releasing videoExtractor", e);
 			exception = e;
 		}
 		try {
@@ -448,8 +447,7 @@ public class VideoTranscoder {
 				mInputAudioComponent.release();
 			}
 		} catch (Exception e) {
-			logger.error("error while releasing audioExtractor");
-			logger.error("Exception", e);
+			logger.error("error while releasing audioExtractor", e);
 			if (exception == null) {
 				exception = e;
 			}
@@ -460,8 +458,7 @@ public class VideoTranscoder {
 				mVideoDecoder.release();
 			}
 		} catch (Exception e) {
-			logger.error("error while releasing videoDecoder");
-			logger.error("Exception", e);
+			logger.error("error while releasing videoDecoder", e);
 			if (exception == null) {
 				exception = e;
 			}
@@ -471,8 +468,7 @@ public class VideoTranscoder {
 				mOutputSurface.release();
 			}
 		} catch (Exception e) {
-			logger.error("error while releasing outputSurface");
-			logger.error("Exception", e);
+			logger.error("error while releasing outputSurface", e);
 			if (exception == null) {
 				exception = e;
 			}
@@ -483,8 +479,7 @@ public class VideoTranscoder {
 				mVideoEncoder.release();
 			}
 		} catch (Exception e) {
-			logger.error("error while releasing videoEncoder");
-			logger.error("Exception", e);
+			logger.error("error while releasing videoEncoder", e);
 			if (exception == null) {
 				exception = e;
 			}
@@ -495,8 +490,7 @@ public class VideoTranscoder {
 				mAudioDecoder.release();
 			}
 		} catch (Exception e) {
-			logger.error("error while releasing audioDecoder");
-			logger.error("Exception", e);
+			logger.error("error while releasing audioDecoder", e);
 			if (exception == null) {
 				exception = e;
 			}
@@ -507,8 +501,7 @@ public class VideoTranscoder {
 				mAudioEncoder.release();
 			}
 		} catch (Exception e) {
-			logger.error("error while releasing audioEncoder");
-			logger.error("Exception", e);
+			logger.error("error while releasing audioEncoder", e);
 			if (exception == null) {
 				exception = e;
 			}
@@ -519,8 +512,7 @@ public class VideoTranscoder {
 				mMuxer.release();
 			}
 		} catch (Exception e) {
-			logger.error("error while releasing muxer");
-			logger.error("Exception", e);
+			logger.error("error while releasing muxer", e);
 			if (exception == null) {
 				exception = e;
 			}
@@ -530,8 +522,7 @@ public class VideoTranscoder {
 				mInputSurface.release();
 			}
 		} catch (Exception e) {
-			logger.error("error while releasing inputSurface");
-			logger.error("Exception", e);
+			logger.error("error while releasing inputSurface", e);
 			if (exception == null) {
 				exception = e;
 			}
@@ -558,7 +549,7 @@ public class VideoTranscoder {
 			return false;
 		}
 
-		logger.debug("{} decoder: returned input buffer: {}", type, decoderInputBufferIndex);
+		logger.trace("{} decoder: returned input buffer: {}", type, decoderInputBufferIndex);
 
 		MediaExtractor extractor = component.getMediaExtractor();
 		int chunkSize = extractor.readSampleData(
@@ -568,8 +559,8 @@ public class VideoTranscoder {
 
 		long sampleTime = extractor.getSampleTime();
 
-		logger.debug("{} extractor: returned buffer of chunkSize {}", type, chunkSize);
-		logger.debug("{} extractor: returned buffer for sampleTime {}", type, sampleTime);
+		logger.trace("{} extractor: returned buffer of chunkSize {}", type, chunkSize);
+		logger.trace("{} extractor: returned buffer for sampleTime {}", type, sampleTime);
 
 		if (mTrimEndTime > 0 && sampleTime > (mTrimEndTime * 1000)) {
 			logger.debug("The current sample is over the trim time. Lets stop.");
@@ -648,9 +639,9 @@ public class VideoTranscoder {
 			return POLLING_ERROR;
 		}
 
-		logger.debug("video decoder: returned output buffer: {}", decoderOutputBufferIndex);
-		logger.debug("video decoder: returned buffer of size {}", videoDecoderOutputBufferInfo.size);
-		logger.debug("video decoder: returned buffer for time {}", videoDecoderOutputBufferInfo.presentationTimeUs);
+		logger.trace("video decoder: returned output buffer: {}", decoderOutputBufferIndex);
+		logger.trace("video decoder: returned buffer of size {}", videoDecoderOutputBufferInfo.size);
+		logger.trace("video decoder: returned buffer for time {}", videoDecoderOutputBufferInfo.presentationTimeUs);
 
 		int percentage = (int) ((videoDecoderOutputBufferInfo.presentationTimeUs - outputStartTimeUs) * 100 / outputDurationUs);
 		if (percentage > progress) {
@@ -672,7 +663,7 @@ public class VideoTranscoder {
 			mOutputSurface.drawImage(false);
 			mInputSurface.setPresentationTime(videoDecoderOutputBufferInfo.presentationTimeUs * 1000);
 			mInputSurface.swapBuffers();
-			logger.debug("video encoder: notified of new frame");
+			logger.trace("video encoder: notified of new frame");
 		}
 
 		if ((videoDecoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
@@ -712,8 +703,8 @@ public class VideoTranscoder {
 			return;
 		}
 
-		logger.debug("audio decoder: returned output buffer: {}", decoderOutputBufferIndex);
-		logger.debug("audio decoder: returned buffer of size {}", audioDecoderOutputBufferInfo.size);
+		logger.trace("audio decoder: returned output buffer: {}", decoderOutputBufferIndex);
+		logger.trace("audio decoder: returned buffer of size {}", audioDecoderOutputBufferInfo.size);
 
 		if ((audioDecoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
 			logger.debug("audio decoder: codec config buffer");
@@ -721,8 +712,8 @@ public class VideoTranscoder {
 			return;
 		}
 
-		logger.debug("audio decoder: returned buffer for time {}", audioDecoderOutputBufferInfo.presentationTimeUs);
-		logger.debug("audio decoder: output buffer is now pending: {}", mPendingAudioDecoderOutputBufferIndex);
+		logger.trace("audio decoder: returned buffer for time {}", audioDecoderOutputBufferInfo.presentationTimeUs);
+		logger.trace("audio decoder: output buffer is now pending: {}", mPendingAudioDecoderOutputBufferIndex);
 
 		mPendingAudioDecoderOutputBufferIndex = decoderOutputBufferIndex;
 		mStats.audioDecodedFrameCount++;
@@ -733,7 +724,7 @@ public class VideoTranscoder {
 	 * @return
 	 */
 	private boolean feedPendingAudioBufferToEncoder(MediaCodec.BufferInfo audioDecoderOutputBufferInfo) {
-		logger.debug("audio decoder: attempting to process pending buffer: {}", mPendingAudioDecoderOutputBufferIndex);
+		logger.trace("audio decoder: attempting to process pending buffer: {}", mPendingAudioDecoderOutputBufferIndex);
 
 		int encoderInputBufferIndex = mAudioEncoder.dequeueInputBuffer(TIMEOUT_USEC);
 
@@ -742,7 +733,7 @@ public class VideoTranscoder {
 			return false;
 		}
 
-		logger.debug("audio encoder: returned input buffer: {}", encoderInputBufferIndex);
+		logger.trace("audio encoder: returned input buffer: {}", encoderInputBufferIndex);
 
 		ByteBuffer encoderInputBuffer =
 			Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ?
@@ -754,9 +745,9 @@ public class VideoTranscoder {
 		int chunkSize = Math.min(audioDecoderOutputBufferInfo.size, encoderInputBuffer.capacity());
 		long presentationTime = audioDecoderOutputBufferInfo.presentationTimeUs;
 
-		logger.debug("audio decoder: processing pending buffer: {}", mPendingAudioDecoderOutputBufferIndex);
-		logger.debug("audio decoder: pending buffer of size {}", chunkSize);
-		logger.debug("audio decoder: pending buffer for time {}", presentationTime);
+		logger.trace("audio decoder: processing pending buffer: {}", mPendingAudioDecoderOutputBufferIndex);
+		logger.trace("audio decoder: pending buffer of size {}", chunkSize);
+		logger.trace("audio decoder: pending buffer for time {}", presentationTime);
 
 		if (chunkSize >= 0) {
 			ByteBuffer decoderOutputBuffer = Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ?
@@ -827,9 +818,9 @@ public class VideoTranscoder {
 //            throw new IllegalStateException("should have added track before processing output");
 //        }
 
-		logger.debug("video encoder: returned output buffer: {}", encoderOutputBufferIndex);
-		logger.debug("video encoder: returned buffer of size {}", videoEncoderOutputBufferInfo.size);
-		logger.debug("video encoder: returned buffer for time {}", videoEncoderOutputBufferInfo.presentationTimeUs);
+		logger.trace("video encoder: returned output buffer: {}", encoderOutputBufferIndex);
+		logger.trace("video encoder: returned buffer of size {}", videoEncoderOutputBufferInfo.size);
+		logger.trace("video encoder: returned buffer for time {}", videoEncoderOutputBufferInfo.presentationTimeUs);
 
 		ByteBuffer encoderOutputBuffer =
 			Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ?
@@ -890,9 +881,9 @@ public class VideoTranscoder {
 //            throw new IllegalStateException("should have added track before processing output");
 //        }
 
-		logger.debug("audio encoder: returned output buffer: {}", encoderOutputBufferIndex);
-		logger.debug("audio encoder: returned buffer of size {}", audioEncoderOutputBufferInfo.size);
-		logger.debug("audio encoder: returned buffer for time {}", audioEncoderOutputBufferInfo.presentationTimeUs);
+		logger.trace("audio encoder: returned output buffer: {}", encoderOutputBufferIndex);
+		logger.trace("audio encoder: returned buffer of size {}", audioEncoderOutputBufferInfo.size);
+		logger.trace("audio encoder: returned buffer for time {}", audioEncoderOutputBufferInfo.presentationTimeUs);
 
 		if (audioEncoderOutputBufferInfo.size != 0) {
 			ByteBuffer encoderOutputBuffer = Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ?

+ 15 - 7
app/src/main/java/ch/threema/app/voip/services/VideoContext.java

@@ -56,7 +56,8 @@ public class VideoContext {
 	// Local state
 	private @Nullable EglBase eglBase;
 	private @Nullable CameraVideoCapturer cameraVideoCapturer;
-	private String[] cameraNames;
+	private volatile String frontCameraName;
+	private volatile String backCameraName;
 	private @CameraOrientation int cameraOrientation;
 	private @Nullable ProxyVideoSink localVideoSink;
 	private @Nullable ProxyVideoSink remoteVideoSink;
@@ -69,7 +70,6 @@ public class VideoContext {
 		this.eglBase = EglBase.create();
 		this.localVideoSink = new ProxyVideoSink("Local");
 		this.remoteVideoSink = new ProxyVideoSink("Remote");
-		this.cameraNames = new String[]{null, null};
 	}
 
 	/**
@@ -107,12 +107,20 @@ public class VideoContext {
 		this.cameraOrientation = cameraOrientation;
 	}
 
-	public @NonNull String[] getCameraNames() {
-		return cameraNames;
+	public @Nullable String getFrontCameraName() {
+		return frontCameraName;
 	}
 
-	public void setCameraNames(@NonNull String[] cameraNames) {
-		this.cameraNames = cameraNames;
+	public @Nullable String getBackCameraName() {
+		return backCameraName;
+	}
+
+	public void setFrontCameraName(@Nullable String frontCameraName) {
+		this.frontCameraName = frontCameraName;
+	}
+
+	public void setBackCameraName(@Nullable String backCameraName) {
+		this.backCameraName = backCameraName;
 	}
 
 	@NonNull
@@ -133,7 +141,7 @@ public class VideoContext {
 	}
 
 	public boolean hasMultipleCameras() {
-		return cameraNames[0] != null && cameraNames[1] != null;
+		return frontCameraName != null && backCameraName != null;
 	}
 
 	/**

+ 46 - 33
app/src/main/java/ch/threema/app/voip/services/VoipCallService.java

@@ -50,7 +50,6 @@ import org.slf4j.LoggerFactory;
 import org.webrtc.CameraVideoCapturer;
 import org.webrtc.IceCandidate;
 import org.webrtc.PeerConnection;
-import org.webrtc.PeerConnectionFactory;
 import org.webrtc.RTCStatsCollectorCallback;
 import org.webrtc.RTCStatsReport;
 import org.webrtc.SessionDescription;
@@ -1661,7 +1660,7 @@ public class VoipCallService extends LifecycleService implements PeerConnectionC
 	@Override
 	@AnyThread
 	public void onLocalDescription(long callId, final SessionDescription sdp) {
-		logger.trace("{}: onLocalDescription", callId);
+		logger.info("{}: onLocalDescription", callId);
 		RuntimeUtil.runInAsyncTask(() -> {
 			synchronized (VoipCallService.this) {
 				final CallStateSnapshot callState = voipStateService.getCallState();
@@ -1686,7 +1685,7 @@ public class VoipCallService extends LifecycleService implements PeerConnectionC
 	@Override
 	@AnyThread
 	public void onRemoteDescriptionSet(long callId) {
-		logger.trace("{}: onRemoteDescriptionSet", callId);
+		logger.info("{}: onRemoteDescriptionSet", callId);
 
 		if (this.peerConnectionClient == null) {
 			logger.error("{}: Cannot create answer: peerConnectionClient is not initialized", callId);
@@ -2148,14 +2147,17 @@ public class VoipCallService extends LifecycleService implements PeerConnectionC
 							this.commonVideoQualityProfile
 						);
 						this.isCapturing = true;
-						VoipUtil.sendVoipBroadcast(getAppContext(), CallActivity.ACTION_OUTGOING_VIDEO_STARTED);
 						if (videoCapturer instanceof CameraVideoCapturer) {
 							// query cameras
-							if (this.voipStateService.getVideoContext() != null) {
-								this.voipStateService.getVideoContext().setCameraNames(VideoCapturerUtil.getFirstCameraNames(getAppContext()));
-								this.voipStateService.getVideoContext().setCameraVideoCapturer((CameraVideoCapturer) videoCapturer);
+							final VideoContext videoContext = this.voipStateService.getVideoContext();
+							if (videoContext != null) {
+								Pair<String,String> primaryCameraNames = VideoCapturerUtil.getPrimaryCameraNames(getAppContext());
+								videoContext.setFrontCameraName(primaryCameraNames.first);
+								videoContext.setBackCameraName(primaryCameraNames.second);
+								videoContext.setCameraVideoCapturer((CameraVideoCapturer) videoCapturer);
 							}
 						}
+						VoipUtil.sendVoipBroadcast(getAppContext(), CallActivity.ACTION_OUTGOING_VIDEO_STARTED);
 					}
 				}
 			}
@@ -2279,37 +2281,48 @@ public class VoipCallService extends LifecycleService implements PeerConnectionC
 			logger.debug("Switching camera");
 			switchCamInProgress.set(true);
 
-			final @VideoContext.CameraOrientation int newCameraOrientation = this.voipStateService.getVideoContext().getCameraOrientation() == CAMERA_FRONT ? CAMERA_BACK : CAMERA_FRONT;
-			final String newCameraName = this.voipStateService.getVideoContext().getCameraNames()[newCameraOrientation];
-			if (newCameraName != null) {
-				capturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() {
-					@Override
-					public void onCameraSwitchDone(boolean isFront) {
-						voipStateService.getVideoContext().setCameraOrientation(newCameraOrientation);
+			final @VideoContext.CameraOrientation int newCameraOrientation;
+			final String newCameraName;
+			if (this.voipStateService.getVideoContext().getCameraOrientation() == CAMERA_FRONT) {
+				newCameraOrientation = CAMERA_BACK;
+				newCameraName = this.voipStateService.getVideoContext().getBackCameraName();
+			} else {
+				newCameraOrientation = CAMERA_FRONT;
+				newCameraName = this.voipStateService.getVideoContext().getFrontCameraName();
+			}
 
-						logger.info("Switched camera to {}", isFront ? "front cam" : "rear cam");
+			if (newCameraName == null) {
+				logger.debug("Ignoring camera switch request, no camera with orientation='{}'", newCameraOrientation);
+				return;
+			}
 
-						VoipUtil.sendVoipBroadcast(getApplicationContext(), CallActivity.ACTION_CAMERA_CHANGED);
+			capturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() {
+				@Override
+				public void onCameraSwitchDone(boolean isFront) {
+					voipStateService.getVideoContext().setCameraOrientation(newCameraOrientation);
 
-						Toast.makeText(
-							getAppContext(),
-							isFront ? R.string.voip_switch_cam_front : R.string.voip_switch_cam_rear,
-							Toast.LENGTH_SHORT
-						).show();
-						this.resetInProgress();
-					}
+					logger.info("Switched camera to {}", isFront ? "front cam" : "rear cam");
 
-					@Override
-					public void onCameraSwitchError(String s) {
-						logger.info("Error while switching camera: {}", s);
-						this.resetInProgress();
-					}
+					VoipUtil.sendVoipBroadcast(getApplicationContext(), CallActivity.ACTION_CAMERA_CHANGED);
 
-					private void resetInProgress() {
-						switchCamInProgress.set(false);
-					}
-				}, newCameraName);
-			}
+					Toast.makeText(
+						getAppContext(),
+						isFront ? R.string.voip_switch_cam_front : R.string.voip_switch_cam_rear,
+						Toast.LENGTH_SHORT
+					).show();
+					this.resetInProgress();
+				}
+
+				@Override
+				public void onCameraSwitchError(String s) {
+					logger.info("Error while switching camera: {}", s);
+					this.resetInProgress();
+				}
+
+				private void resetInProgress() {
+					switchCamInProgress.set(false);
+				}
+			}, newCameraName);
 		}
 	}
 

+ 7 - 3
app/src/main/java/ch/threema/app/voip/util/VideoCapturerUtil.java

@@ -32,6 +32,7 @@ import org.webrtc.CameraVideoCapturer;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.core.util.Pair;
 
 /**
  * Enumerate and initialize device cameras.
@@ -109,11 +110,14 @@ public class VideoCapturerUtil {
 	}
 
 	/**
+	 *
+	 * Returns the primary camera names as Pair of {frontcamera, backcamera}.
+	 * Currently, the first available front/backcamera is used as primary.
 	 *
 	 * @param context
-	 * @return
+	 * @return Pair of nullable camera name strings.
 	 */
-	public static String[] getFirstCameraNames(Context context) {
+	public static Pair<String, String> getPrimaryCameraNames(Context context) {
 		CameraEnumerator enumerator;
 		String frontCamera = null, backCamera = null;
 
@@ -139,6 +143,6 @@ public class VideoCapturerUtil {
 				break;
 			}
 		}
-		return new String[] {frontCamera, backCamera};
+		return new Pair<>(frontCamera, backCamera);
 	}
 }

+ 1 - 1
app/src/main/java/ch/threema/app/webclient/activities/SessionsActivity.java

@@ -59,7 +59,7 @@ import androidx.preference.PreferenceManager;
 import androidx.recyclerview.widget.DefaultItemAnimator;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
-import ch.threema.app.QRScannerUtil;
+import ch.threema.app.utils.QRScannerUtil;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
 import ch.threema.app.activities.DisableBatteryOptimizationsActivity;

+ 1 - 0
app/src/main/java/ch/threema/app/webclient/converter/MessageState.java

@@ -51,6 +51,7 @@ public class MessageState extends Converter {
 					return MessageState.USERACK;
 				case USERDEC:
 					return MessageState.USERDEC;
+				case TRANSCODING:
 				case PENDING:
 					return MessageState.PENDING;
 				case SENDING:

+ 21 - 0
app/src/main/res/drawable/bubble_fade_recv_selector_dark.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+	<item android:state_pressed="true">
+		<shape android:shape="rectangle">
+			<gradient android:endColor="#00000000" android:startColor="@color/bubble_pressed_dark"
+				android:angle="90"/>
+		</shape>
+	</item>
+	<item android:state_activated="true">
+		<shape android:shape="rectangle">
+			<gradient android:endColor="#00000000" android:startColor="@color/bubble_selected_dark"
+				android:angle="90"/>
+		</shape>
+	</item>
+	<item>
+		<shape android:shape="rectangle">
+			<gradient android:endColor="#00000000" android:startColor="@color/dark_bubble_recv"
+				android:angle="90"/>
+		</shape>
+	</item>
+</selector>

+ 21 - 0
app/src/main/res/drawable/bubble_fade_recv_selector_light.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+	<item android:state_pressed="true">
+		<shape android:shape="rectangle">
+			<gradient android:endColor="#00000000" android:startColor="@color/bubble_pressed"
+				android:angle="90"/>
+		</shape>
+	</item>
+	<item android:state_activated="true">
+		<shape android:shape="rectangle">
+			<gradient android:endColor="#00000000" android:startColor="@color/bubble_selected"
+				android:angle="90"/>
+		</shape>
+	</item>
+	<item>
+		<shape android:shape="rectangle">
+			<gradient android:endColor="#00000000" android:startColor="@color/light_bubble_recv"
+				android:angle="90"/>
+		</shape>
+	</item>
+</selector>

+ 21 - 0
app/src/main/res/drawable/bubble_fade_send_selector_dark.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+	<item android:state_pressed="true">
+		<shape android:shape="rectangle">
+			<gradient android:endColor="#00000000" android:startColor="@color/bubble_pressed_dark"
+				android:angle="90"/>
+		</shape>
+	</item>
+	<item android:state_activated="true">
+		<shape android:shape="rectangle">
+			<gradient android:endColor="#00000000" android:startColor="@color/bubble_selected_dark"
+				android:angle="90"/>
+		</shape>
+	</item>
+	<item>
+		<shape android:shape="rectangle">
+			<gradient android:endColor="#00000000" android:startColor="@color/dark_bubble_send"
+				android:angle="90"/>
+		</shape>
+	</item>
+</selector>

+ 21 - 0
app/src/main/res/drawable/bubble_fade_send_selector_light.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+	<item android:state_pressed="true">
+		<shape android:shape="rectangle">
+			<gradient android:endColor="#00000000" android:startColor="@color/bubble_pressed"
+				android:angle="90"/>
+		</shape>
+	</item>
+	<item android:state_activated="true">
+		<shape android:shape="rectangle">
+			<gradient android:endColor="#00000000" android:startColor="@color/bubble_selected"
+				android:angle="90"/>
+		</shape>
+	</item>
+	<item>
+		<shape android:shape="rectangle">
+			<gradient android:endColor="#00000000" android:startColor="@color/light_bubble_send"
+				android:angle="90"/>
+		</shape>
+	</item>
+</selector>

+ 3 - 1
app/src/main/res/layout/conversation_list_item_quote.xml

@@ -110,9 +110,11 @@
 		android:layout_width="match_parent"
 		android:layout_height="wrap_content"
 		android:visibility="gone"
+		android:paddingTop="36dp"
+		android:background="?attr/chat_bubble_fade_send"
 		app:layout_constraintLeft_toLeftOf="parent"
 		app:layout_constraintLeft_toRightOf="parent"
-		app:layout_constraintTop_toBottomOf="@id/text_view">
+		app:layout_constraintBottom_toBottomOf="@id/text_view">
 
 		<com.google.android.material.chip.Chip
 			android:id="@+id/read_on_button"

+ 17 - 13
app/src/main/res/layout/conversation_list_item_send.xml

@@ -44,24 +44,28 @@
 					android:ellipsize="end"
 					android:maxLength="@integer/max_bubble_text_length" />
 
-		</FrameLayout>
+			<FrameLayout
+				android:id="@+id/read_on_container"
+				android:layout_width="match_parent"
+				android:layout_height="wrap_content"
+				android:paddingTop="36dp"
+				android:background="?attr/chat_bubble_fade_send"
+				android:layout_gravity="bottom"
+				android:visibility="gone">
 
-		<FrameLayout
-			android:id="@+id/read_on_container"
-			android:layout_width="match_parent"
-			android:layout_height="wrap_content"
-			android:visibility="gone">
+				<com.google.android.material.chip.Chip
+					android:id="@+id/read_on_button"
+					style="@style/Threema.Chip.VideoTranscoder"
+					android:layout_width="wrap_content"
+					android:layout_height="wrap_content"
+					android:layout_gravity="center_horizontal"
+					android:text="@string/read_on" />
 
-			<com.google.android.material.chip.Chip
-				android:id="@+id/read_on_button"
-				style="@style/Threema.Chip.VideoTranscoder"
-				android:layout_width="wrap_content"
-				android:layout_height="wrap_content"
-				android:layout_gravity="center_horizontal"
-				android:text="@string/read_on" />
+			</FrameLayout>
 
 		</FrameLayout>
 
+
 		<include layout="@layout/conversation_bubble_footer_send"/>
 
 	</LinearLayout>

+ 28 - 0
app/src/main/res/layout/dialog_message_detail.xml

@@ -150,4 +150,32 @@
 		android:visibility="gone"
 		/>
 
+	<TextView
+		android:id="@+id/filesize_text"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:text="@string/file_size"
+		android:layout_marginTop="4dp"
+		app:layout_constraintTop_toBottomOf="@id/filetype_text"
+		app:layout_constraintLeft_toLeftOf="parent"
+		app:layout_constraintRight_toLeftOf="@+id/filesize_data"
+		app:layout_constrainedWidth="true"
+		app:layout_constraintHorizontal_bias="0"
+		android:ellipsize="end"
+		android:singleLine="true"
+		android:textAppearance="@style/Threema.AlertDialog.BodyTextStyle"
+		android:visibility="gone"
+		/>
+
+	<TextView
+		android:id="@+id/filesize_data"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:text=""
+		app:layout_constraintBaseline_toBaselineOf="@id/filesize_text"
+		app:layout_constraintRight_toRightOf="parent"
+		android:textAppearance="@style/Threema.AlertDialog.BodyTextStyle"
+		android:visibility="gone"
+		/>
+
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 2 - 2
app/src/main/res/layout/item_contact_list.xml

@@ -109,10 +109,10 @@
 		android:baselineAlignBottom="true"
 		app:srcCompat="@drawable/ic_block"
 		android:contentDescription="@string/blocked"
-		android:tint="@color/material_red"
 		android:visibility="visible"
 		app:layout_constraintBottom_toBottomOf="@id/name"
-		app:layout_constraintRight_toRightOf="parent" />
+		app:layout_constraintRight_toRightOf="parent"
+		app:tint="@color/material_red" />
 
 	<!-- second line -->
 

+ 48 - 15
app/src/main/res/values-cs/strings.xml

@@ -10,7 +10,7 @@
     <string name="title_adduser">Nový kontakt</string>
     <string name="title_enter_id">Zadejte ID</string>
     <string name="title_invite_friend">Pozvat přátele</string>
-    <string name="invite_via">Pozvat přátele prostřednictvím...</string>
+    <string name="invite_via">Pozvat přátele prostřednictvím</string>
     <string name="invite_email_body">Ahoj,\n\npoužívám aplikaci %1$s – bezpečný komunikátor, který chrání soukromí svých uživatelů.\n\nMoje Threema ID: https://threema.id/%2$s\n\nBuďme v kontaktu přes %1$s!\n\nMěj se\n</string>
     <string name="invite_sms_body">Ahoj! Pojďme používat aplikaci %1$s, ať můžeme komunikovat bezpečným způsobem, který myslí na soukromí! Moje Threema ID: https://threema.id/%2$s</string>
     <string name="invite_email_subject">Threema. Bezpečný komunikátor, který chrání vaše soukromí.</string>
@@ -97,9 +97,9 @@
     <string name="color_white">Bílá</string>
     <string name="next">Další</string>
     <string name="finish">Konec</string>
-    <string name="please_wait">Čekejte prosím...</string>
-    <string name="wizard_first_create_id">Vytvářím Vaše Threema ID...</string>
-    <string name="wizard1_sync_contacts">Synchronizuji kontakty...</string>
+    <string name="please_wait">Čekejte prosím</string>
+    <string name="wizard_first_create_id">Vytvářím Vaše Threema ID</string>
+    <string name="wizard1_sync_contacts">Synchronizuji kontakty</string>
     <string name="wizard2_email_hint">Zadejte Vaši emailovou adresu</string>
     <string name="wizard2_email_linking">Svázání E-mailu s Vašim ID</string>
     <string name="wizard2_phone_hint">Zadejte telefonní číslo</string>
@@ -120,7 +120,7 @@
     <string name="linked_email">E-mail</string>
     <string name="linked_mobile">Číslo mobilu</string>
     <string name="public_nickname">Veřejná přezdívka</string>
-    <string name="share_via">Sdílet prostřednictvím...</string>
+    <string name="share_via">Sdílet prostřednictvím</string>
     <string name="share_subject">Threema konverzace s</string>
     <string name="message_delete_undo">Zpět</string>
     <string name="message_deleted">Zpráva(y) vymazána(y)</string>
@@ -132,7 +132,7 @@
     <string name="really_delete_thread">Odstranit chat</string>
     <string name="really_delete_thread_message" tools:ignore="PluralsCandidate">Opravdu chcete smazat %d chat(y)? Zprávy není možné obnovit.</string>
     <string name="really_delete_contact">Opravdu chcete smazat tento kontakt a všechny související zprávy?</string>
-    <string name="image_placeholder">Obraz</string>
+    <string name="image_placeholder">Obrázek</string>
     <string name="invalid_threema_id">Neplatné Threema ID</string>
     <string name="contact_already_exists">Kontakt již existuje</string>
     <string name="close">Zavřít</string>
@@ -222,8 +222,8 @@ obnově Vašeho Threema ID.</string>
     <string name="file_is_not_a_image">Vybraný soubor není obrázkem</string>
     <string name="connection_error">Chyba spojení, prosím zkuste to později.</string>
     <string name="me_myself_and_i">Vy</string>
-    <string name="really_forward">Přeposlat... «%1$s»?</string>
-    <string name="really_send">Odeslat... «%1$s»?</string>
+    <string name="really_forward">Přeposlat «%1$s»?</string>
+    <string name="really_send">Odeslat «%1$s»?</string>
     <string name="ringtone_none">Tichý</string>
     <string name="no_camera_installed">Fotoaparát není dostupný</string>
     <string name="save_message_action">Uložit</string>
@@ -799,7 +799,7 @@ Threema anonymně?</string>
     <string name="menu_send_profilpic_off">Odebrat z příjemců profilového obrázku</string>
     <string name="menu_send_profilpic_now">Zaslat profilový obrázek</string>
     <string name="profile_picture_sent">Odeslán profilový obrázek</string>
-    <string name="sending_messages">Odesílám...</string>
+    <string name="sending_messages">Odesílám</string>
     <string name="backup_data_media_confirm">Uložení velkých mediálních souborů do lokální zálohy ZIP může překročit kapacitu úložiště vašeho zařízení. Také počítejte, že záloha poběží i desítky minut. Threema během zálohování nebude odesílat ani přijímat zprávy. Přesto pokračovat?</string>
     <string name="backup_data_cancelled">Zálohování přerušeno</string>
     <string name="service_manager_not_available">Program Threema nelze spustit. Prosím, restartujte mobilní telefon.</string>
@@ -893,7 +893,7 @@ Threema anonymně?</string>
     <string name="disable_powermanager_title">Úsporný režim</string>
     <string name="disable_autostart_title">Autostart</string>
     <string name="unchanged">beze změny</string>
-    <string name="safe_learn_more_button">Zjistit více...</string>
+    <string name="safe_learn_more_button">Zjistit více</string>
     <string name="safe_enable_explain">Vše, co se týká chatu, je uloženo pouze ve Vašem zařízení. Nemáte u nás účet a nemůžeme Vám pomoci, pokud ztratíte svůj telefon nebo nechtěně data smažete.\n\n\nThreema Safe provádí automatické zálohování všech důležitých dat, včetně šifrovacích klíčů, seznamu kontaktů a členství ve skupinách. Anonymně a na zabezpečeném serveru podle vašeho výběru.\n</string>
     <string name="safe_disable_confirm">Opravdu chcete pokračovat bez aktivace Threema Safe?</string>
     <string name="safe_configure_choose_password">Zvolte prosím heslo. Toto heslo si dobře zapamatujte. Budete ho v budoucnu potřebovat k obnovení dat z Threema Safe zálohy .</string>
@@ -990,8 +990,8 @@ Threema anonymně?</string>
     <string name="username_hint">Uživ. jméno</string>
     <string name="lock_option_biometric">Biometrický</string>
     <string name="biometric_enter_authentication">Pro odemknutí se autorizujte</string>
-    <string name="biometric_authentication_failed">Chyba autorizace</string>
-    <string name="biometric_authentication_successful">Úspěšně autorizováno</string>
+    <string name="biometric_authentication_failed">Ověření se nezdařilo</string>
+    <string name="biometric_authentication_successful">Úspěšně ověřeno</string>
     <string name="work_safe_forced_explain">Váš správce povolil službu Threema Safe pro vaše zařízení.</string>
     <string name="pin_locked_cannot_send">Nelze odeslat. Aplikace je uzamčena.</string>
     <string name="prefs_summary_hide_screenshots_notice">Upozornění: Při zapnuté ochraně soukromí se nebudou vytvářet náhledy obrázků ani nebude možné ukládání snímků obrazovky</string>
@@ -1006,8 +1006,8 @@ Threema anonymně?</string>
     <string name="voice_action_title">Hlasové pokyny</string>
     <string name="voice_action_body">Hlasový pokyn se zpracovává</string>
     <string name="permission_camera_photo_required">Chcete-li pořídit snímek, povolte přístup k fotoaparátu</string>
-    <string name="global_search">Globální vyhledávání</string>
-    <string name="global_search_empty_view_text">Zadejte alespoň dva znaky pro vyhledávání ve všech zprávách a kontaktech</string>
+    <string name="global_search">Hledat texty</string>
+    <string name="global_search_empty_view_text">Zadejte alespoň dva znaky pro vyhledávání ve všech zprávách</string>
     <string name="my_id">Vaše ID</string>
     <string name="profile_picture_and_nickname">Profilový obrázek a přezdívka</string>
     <string name="lp_select_this_place">Odeslat tuto polohu</string>
@@ -1037,7 +1037,21 @@ Threema anonymně?</string>
     <string name="num_archived_chats">%d archivovaných chatů</string>
     <string name="continue_recording">Pokračuje nahrávání</string>
     <string name="whatsnew_title">Vítejte v %s 4.5</string>
+    <string name="whatsnew_headline">%1$s 4.5 přináší nové funkce pro práci s obrázky.\n\nRozhraní pro práci s obrázky prošlo zásadní úpravou a nyní obsahuje volitelné vyhledávání obrázků založené na strojovém učení.\n\nDále jsme přidali globální textové vyhledávání napříč všemi chaty, vylepšené citování zpráv a další drobná vylepšení.\n</string>
     <string name="whatsnew2_title">Co je nového?</string>
+    <string name="whatsnew2_body">
+    <![CDATA[
+    <p><b>Adresář médií</b>: Klepnutím na ikonu sponky můžete procházet mediální soubory v rolovacím seznamu. Pokud nechcete, aby se seznam s nejnovějšími médii automaticky otevíral, deaktivujte možnost rychlého výběru obrazků v nastavení chatu v<i> Nastavení / Rychlý výběr chatu / média</i>.</p>
+    <p><b>Hledání obrázků</b>: Vyhledejte ve svých obrázcích běžné objekty, aktivity a místa.<br> Rozpoznávání obrázků je založeno na modelu místního strojového učení a neposílá data na server Threema ani žádné jiné externí službě. Protože analýza obrázků je poměrně náročný úkol a může trvat dlouho, je tato možnost ve výchozím nastavení zakázána. Najdete ji v<i> Nastavení / Média a úložiště / Hledání obrázků</i>.</p>
+    <p><b>Odesílání mediálních souborů</b>: Odesílejte obrázky s individuálním rozlišením, aniž byste museli měnit globální nastavení.</p>
+    <p><b>Editor videa</b>: Ořízněte videa před odesláním. Kromě toho byl vylepšen proces překódování videa, který nyní funguje na pozadí.</p>
+    <p><b>Ukládání do galerie</b>: V systému Android 10 a novějších se média nyní ukládají do systémových složek<i> Obrázky, Videa, Hudba,</i>a<i> Dokumenty</i> kvůli novým požadavkům «Pravidla pro úložiště» od společnosti Google.</p>
+    <p><b>Globální vyhledávání</b>: Hledání textu ve všech chatech. Jednoduše klepněte na<i> Nabídka / Hledat chaty</i>, když jste na domovské obrazovce %1$s.</p>
+    <p><b>Citace</b>: %1$s vám nyní umožňuje citovat jakýkoli typ média, včetně obrázků, videí a hlasových zpráv.</p>
+    <p><b>100 nových smajlíků</b>: Podívejte se na dlouho očekávané fondue &#129749;</p>
+    <p><b>Dlouhé texty</b>: Kvůli zachování přehledného rozhraní chatu se zprávy s velkým množstvím textu zobrazují ve zkrácené bublině chatu a lze je poklepem rozbalit.</p>
+    ]]>
+    </string>
     <string name="tooltip_identity_popup">Klepnutím sem zobrazíte Vaše Threema ID nebo aktivujete scannování ID jiného uživatele</string>
     <string name="tap_to_start">Klepnutím sem spustíte %s.</string>
     <string name="two_years">2 roky</string>
@@ -1142,7 +1156,7 @@ Threema anonymně?</string>
     <string name="an_error_occurred_during_send">Při odesílání jedné nebo více zpráv došlo k chybě.</string>
     <string name="state_processing">zpracovává se</string>
     <string name="passphrase_locked">Přístupová fráze je uzamčena</string>
-    <string name="image_labeling_new">Novinka: Rozpoznávání obrázků</string>
+    <string name="image_labeling_new">Novinka: Hledání obrázků</string>
     <string name="tooltip_image_labeling">Chcete-li ve své galerii najít konkrétní obrázek,  zadejte výraz popisující obsah a vyberte jej ze seznamu identifikovaných štítků.</string>
     <string name="selected_media">Váš výběr</string>
     <string name="attach_gif">Gif</string>
@@ -1161,4 +1175,23 @@ Threema anonymně?</string>
     <string name="max_selectable_media_exceeded">Nelze odeslat více než %d objektů najednou.</string>
     <string name="error_unable_loading_media_thumb">Chyba. Nelze načíst náhledovou miniaturu</string>
     <string name="select">Vybrat</string>
+    <string name="filter_list">Seznam filtrů</string>
+    <string name="hint_filter_list">Zadejte \"text\" filtru</string>
+    <string name="add">Přidat</string>
+    <string name="threema_message_from">Zpráva od %s</string>
+    <string name="show_text">Zobrazit text</string>
+    <string name="only_images_or_videos">Lze vybrat pouze obrázky nebo videa</string>
+    <string name="media_gallery_gifs">GIFy</string>
+    <string name="notification_channel_image_labeling">Indexování obrázků</string>
+    <string name="notification_channel_image_labeling_desc">Na pozadí probíhá indexování obrázků veřejné galerie</string>
+    <string name="notification_image_labeling_desc">Indexování mediální galerie</string>
+    <string name="no_media_found_global">Nebyla nalezena žádná média</string>
+    <string name="prefs_sum_image_labeling">Povolte vyhledávání veřejných obrázků ve vaší galerii podle klíčových slov</string>
+    <string name="prefs_image_labeling">Hledání obrázků</string>
+    <string name="enable_formatting">Povolit formátování</string>
+    <string name="original_file_no_longer_avilable">Původní soubor již není přístupný. Znovu odešlete zprávu.</string>
+    <string name="state_transcoding">překódování</string>
+    <string name="importing_files">Import souborů</string>
+    <string name="tooltip_image_resolution_hint">Upravte rozlišení obrázků.</string>
+    <string name="image_labeling_stuck_error">Proces indexování pro vyhledávání obrázků se zasekl a byl zrušen. Zkuste to prosím později.</string>
 </resources>

+ 2 - 0
app/src/main/res/values-de/strings.xml

@@ -1294,4 +1294,6 @@ sicheren Ort gesichert oder ausgedruckt haben.</string>
 	<string name="tooltip_image_resolution_hint">Bildauflösung individuell anpassen.</string>
 	<string name="image_labeling_stuck_error">Der Indexierungsprozess für die Bildersuche ist stehen geblieben und wurde abgebrochen. Versuchen Sie es später nochmals.</string>
 	<string name="max_selectable_media_exceeded">Es können maximal %d Objekte aufs Mal versendet werden.</string>
+	<string name="ballot_created_successfully">Die Umfrage wurde erfolgreich erstellt.</string>
+	<string name="file_size">Dateigrösse</string>
 </resources>

+ 40 - 7
app/src/main/res/values-es/strings.xml

@@ -979,7 +979,7 @@ almacenado.</string>
     <string name="lock_option_biometric">Biométrico</string>
     <string name="biometric_enter_authentication">Para desbloquear debe autenticarse</string>
     <string name="biometric_authentication_failed">Error de autenticación</string>
-    <string name="biometric_authentication_successful">Autenticación realizada</string>
+    <string name="biometric_authentication_successful">Autenticado correctamente</string>
     <string name="work_safe_forced_explain">El administrador ha habilitado Threema Safe en su dispositivo.</string>
     <string name="pin_locked_cannot_send">Envío no permitido: aplicación bloqueada.</string>
     <string name="prefs_summary_hide_screenshots_notice">Aviso: Por razones de privacidad, se bloquean las imágenes en miniatura y las capturas de pantalla cuando la protección de la aplicación está habilitada.</string>
@@ -994,8 +994,8 @@ almacenado.</string>
     <string name="voice_action_title">Acciones vocales</string>
     <string name="voice_action_body">La acción vocal se está procesando.</string>
     <string name="permission_camera_photo_required">Para tomar fotos, conceda acceso a la cámara.</string>
-    <string name="global_search">Búsqueda global</string>
-    <string name="global_search_empty_view_text">Introduzca por lo menos dos caracteres para buscar en todos los mensajes y contactos</string>
+    <string name="global_search">Buscar en chats</string>
+    <string name="global_search_empty_view_text">Introduzca al menos dos caracteres para buscar en los mensajes</string>
     <string name="my_id">Mi ID</string>
     <string name="profile_picture_and_nickname">Foto de perfil y alias</string>
     <string name="lp_select_this_place">Seleccionar este lugar</string>
@@ -1017,15 +1017,29 @@ almacenado.</string>
     <string name="add_contact_enter_id_hint">Introduzca el ID de Threema del contacto que desea añadir</string>
     <string name="notification_channel_new_contact">Contactos nuevos</string>
     <string name="notification_channel_new_contact_desc">Notificación sobre contactos nuevos</string>
-    <string name="notification_contact_has_joined">%1$s se ha unido a %2$s. Toque aquí para enviar un mensaje.</string>
-    <string name="notification_contact_has_joined_multiple">%1$d contactos se han unido a %2$s: %3$s. Toque aquí para enviarles un mensaje.</string>
+    <string name="notification_contact_has_joined">%1$s se unió a %2$s. Toque aquí para enviar un mensaje.</string>
+    <string name="notification_contact_has_joined_multiple">%1$d contactos se unieron a %2$s: %3$s. Toque aquí para enviarles un mensaje.</string>
     <string name="system_default">Estándar del sistema</string>
     <string name="open_in_maps_app">Abrir en Maps</string>
     <string name="delete">Borrar</string>
     <string name="num_archived_chats">%d chats archivados</string>
     <string name="continue_recording">Continuar grabando</string>
     <string name="whatsnew_title">Esto es %s 4.5</string>
+    <string name="whatsnew_headline">%1$s 4.5 ofrece una nueva experiencia multimedia.\n\nLa interfaz de cajón de medios recibió una sobrecarga importante y ahora incluye un aprendizaje automático opcional basado en la búsqueda de imágenes.\n\nAdemás, añadimos la búsqueda de texto global en todos los chats, hemos mejorado la cita de mensajes, etc.</string>
     <string name="whatsnew2_title">Novedades</string>
+    <string name="whatsnew2_body">
+        <![CDATA[
+		<p><b>Cajón de medios</b>: Toque el icono de clip para examinar tus archivos multimedia en un cajón desplazable. Si no quiere que el cajón se abra automáticamente con los archivos multimedia más recientes, deshabilite la opción de selección rápida de imagen en los ajustes del chat, en <i>Ajustes / Chat / Selección rápida de medios</i>.</p>
+		<p><b>Búsqueda de imágenes</b>: Busca objetos comunes, actividades y lugares en las imágenes.<br>El reconocimiento de imágenes se basa en un modelo de aprendizaje automático y no envía datos al servidor de Threema ni a un tercero. Puesto que analizar imágenes es una tarea bastante costosa y puede tardar bastante tiempo, la opción está deshabilitada de forma predeterminada. La encontrarás en <i>Ajustes / Medios y almacenamiento / Búsqueda de imágenes</i>.</p>
+		<p><b>Enviar archivos de medios</b>: Envía imágenes con resoluciones independientes sin tener que cambiar los ajustes globales.</p>
+		<p><b>Editor de vídeo</b>: Recorta vídeos antes de enviarlos. Además, el proceso de transcodificación de vídeos se ha mejorado y ahora funciona en segundo plano.</p>
+		<p><b>Guardar en la galería</b>: En Android 10 y posteriores, los medios se almacenan en las carpetas del sistema <i>Imágenes, Vídeo, Música</i> y <i>Documentos</i> debido a los nuevos requisitos «Almacenamiento específico» de Google.</p>
+		<p><b>Búsqueda global</b>: Busca texto en todos los chats. Solo tienes que tocar en <i>Menú / Buscar chats</i> cuando estés en la pantalla de inicio de %1$s.</p>
+		<p><b>Citas</b>: %1$s ahora permite citar cualquier tipo de medio, incluidas imágenes, vídeos y mensajes de voz.</p>
+		<p><b>100 nuevos emojis</b>: Echa un vistazo a la esperada \"fondue\" &#129749;</p>
+		<p><b>Textos grandes</b>: Para mantener ordenada la interfaz del chat, los mensajes con mucho texto se muestran en una burbuja de chat truncada y se puede ampliar a voluntad.</p>
+		]]>
+    </string>
     <string name="tooltip_identity_popup">Toque aquí para visualizar rápidamente su ID de Threema o para escanear el ID de otras personas.</string>
     <string name="tap_to_start">Toque aquí para iniciar %s ahora.</string>
     <string name="two_years">2 años</string>
@@ -1066,7 +1080,7 @@ almacenado.</string>
     <string name="media_files">Archivos</string>
     <string name="auto_download_limit_explain">Nota: los vídeos y archivos que ocupen más de %s siempre se descargarán bajo demanda.</string>
     <string name="quoted_message_deleted">El mensaje citado ya no está disponible.</string>
-    <string name="searching">Buscando...</string>
+    <string name="searching">Buscando</string>
     <string name="prefs_work_life_balance">No molestar</string>
     <string name="prefs_title_working_days">Días laborables</string>
     <string name="prefs_working_days_sum">Seleccionar días laborables</string>
@@ -1130,7 +1144,7 @@ almacenado.</string>
     <string name="an_error_occurred_during_send">Se ha producido un error al enviar uno o más mensajes.</string>
     <string name="state_processing">procesando</string>
     <string name="passphrase_locked">La frase de contraseña está bloqueada.</string>
-    <string name="image_labeling_new">Nuevo: Reconocimiento de imagen</string>
+    <string name="image_labeling_new">Nuevo: Búsqueda de imagen</string>
     <string name="tooltip_image_labeling">Para buscar un elemento visual concreto en su galería, introduzca un término que describa el contenido y selecciónelo en la lista de etiquetas identificadas.</string>
     <string name="selected_media">Su selección</string>
     <string name="attach_gif">Gif</string>
@@ -1149,4 +1163,23 @@ almacenado.</string>
     <string name="max_selectable_media_exceeded">No se pueden enviar más de %d objetos a la vez.</string>
     <string name="error_unable_loading_media_thumb">Error. No se pudo cargar la imagen del objeto.</string>
     <string name="select">Seleccionar</string>
+    <string name="filter_list">Filtrar lista</string>
+    <string name="hint_filter_list">Introducir texto de filtro</string>
+    <string name="add">Añadir</string>
+    <string name="threema_message_from">Mensaje de %s</string>
+    <string name="show_text">Mostrar texto</string>
+    <string name="only_images_or_videos">Solo se pueden seleccionar imágenes o vídeos</string>
+    <string name="media_gallery_gifs">GIF</string>
+    <string name="notification_channel_image_labeling">Progreso de indexación de imagen</string>
+    <string name="notification_channel_image_labeling_desc">Realizando indexación de imágenes de galería pública en el fondo</string>
+    <string name="notification_image_labeling_desc">Indexación de galería de medios</string>
+    <string name="no_media_found_global">No se encontraron medios en este dispositivo</string>
+    <string name="prefs_sum_image_labeling">Habilitar búsqueda de imágenes públicas en la galería por palabras clave</string>
+    <string name="prefs_image_labeling">Buscar imágenes</string>
+    <string name="enable_formatting">Habilitar formato</string>
+    <string name="original_file_no_longer_avilable">Ya no se puede acceder al archivo original. Vuelva a enviar el mensaje.</string>
+    <string name="state_transcoding">transcodificación</string>
+    <string name="importing_files">Importando archivos</string>
+    <string name="tooltip_image_resolution_hint">Ajusta la resolución de imágenes.</string>
+    <string name="image_labeling_stuck_error">El proceso de indexación de la búsqueda de imágenes se atascó y se canceló. Inténtelo más tarde.</string>
 </resources>

+ 41 - 8
app/src/main/res/values-fr/strings.xml

@@ -968,8 +968,8 @@ Veuillez saisir une question pour votre enquête.</string>
     <string name="username_hint">Nom d\'utilisateur</string>
     <string name="lock_option_biometric">Biométrique</string>
     <string name="biometric_enter_authentication">Pour déverrouiller, veuillez vous authentifier</string>
-    <string name="biometric_authentication_failed">Échec d\'authentification</string>
-    <string name="biometric_authentication_successful">Bien authentifié</string>
+    <string name="biometric_authentication_failed">Authentification échouée</string>
+    <string name="biometric_authentication_successful">Authentification réussie</string>
     <string name="work_safe_forced_explain">Vous administrateur a activé Threema Safe pour votre appareil.</string>
     <string name="pin_locked_cannot_send">L\'application est verrouillée. Envoi impossible.</string>
     <string name="prefs_summary_hide_screenshots_notice">Remarque : pour des raisons de confidentialité, les vignettes et captures d\'écran sont toujours bloqués lorsque la protection de l\'application est activée.</string>
@@ -984,8 +984,8 @@ Veuillez saisir une question pour votre enquête.</string>
     <string name="voice_action_title">Commandes vocales</string>
     <string name="voice_action_body">La commande vocale est en cours de traitement</string>
     <string name="permission_camera_photo_required">Pour prendre une photo, veuillez autoriser l\'accès à l\'appareil photo</string>
-    <string name="global_search">Recherche globale</string>
-    <string name="global_search_empty_view_text">Entrez au moins deux caractères pour rechercher dans tous les messages et les contacts</string>
+    <string name="global_search">Rechercher dans les discussions</string>
+    <string name="global_search_empty_view_text">Entrez au moins deux caractères pour rechercher dans les messages</string>
     <string name="my_id">Mon ID</string>
     <string name="profile_picture_and_nickname">Photo de profil et surnom</string>
     <string name="lp_select_this_place">Sélectionner cet endroit</string>
@@ -1007,15 +1007,29 @@ Veuillez saisir une question pour votre enquête.</string>
     <string name="add_contact_enter_id_hint">Veuillez entrer l\'ID Threema du contact que vous voudriez ajouter</string>
     <string name="notification_channel_new_contact">Nouveaux contacts</string>
     <string name="notification_channel_new_contact_desc">Notification sur les nouveaux contacts</string>
-    <string name="notification_contact_has_joined">%1$s a rejoint %2$s. Touchez ici pour lui envoyer un message.</string>
-    <string name="notification_contact_has_joined_multiple">%1$d contacts on rejoint %2$s : %3$s. Touchez ici pour leur envoyer un message.</string>
+    <string name="notification_contact_has_joined">%1$s a rejoint %2$s. Appuyez ici pour envoyer un message.</string>
+    <string name="notification_contact_has_joined_multiple">%1$d contacts ont rejoint %2$s : %3$s. Appuyez ici pour leur envoyer un message.</string>
     <string name="system_default">Paramètre système par défaut</string>
     <string name="open_in_maps_app">Ouvrir dans l\'app de cartes</string>
     <string name="delete">Supprimer</string>
     <string name="num_archived_chats">%d chats archivés</string>
     <string name="continue_recording">Continuer l\'enregistrement</string>
-    <string name="whatsnew_title">Bienvenue chez %s 4.5</string>
+    <string name="whatsnew_title">Bienvenue dans %s 4.5</string>
+    <string name="whatsnew_headline">%1$s 4.5 apporte une expérience multimédia flambante neuve.\n\nL\'interface de tiroir de média a bénéficié d\'une refonte majeure et propose désormais une recherche d\'images optionnelle basée sur l\'apprentissage automatique.\n\nDe plus, nous avons ajouté une recherche de texte globale à travers tous les chats, une citation améliorée des messages et bien plus.</string>
     <string name="whatsnew2_title">Quoi de neuf ?</string>
+    <string name="whatsnew2_body">
+    <![CDATA[
+		<p><b>Tiroir de média</b> : Touchez l\'icône de trombone pour parcourir vos fichiers média dans un tiroir défilant. Au cas où vous ne voulez pas que le tiroir s\'ouvre automatiquement avec les médias les plus récents, désactivez l\'option Sélection rapide de l\'image depuis les paramètres de chat, dans <i>Paramètres/Chat/Sélection rapide de l\'image</i>.</p>
+		<p><b>Recherche d\'image</b> : Recherchez des objets, des activités et des lieux communs dans vos images.<br>La reconnaissance des images est basée sur un modèle d\'apprentissage automatique local et n\'envoie pas de données aux serveurs de Threema ou à tout autre tiers. Puisque l\'analyse des images est une tâche plutôt coûteuse et qui peut prendre beaucoup de temps, l\'option est désactivée par défaut. Vous pouvez y accéder depuis <i>Paramètres/Médias et stockage/Recherche d’image</i>.</p>
+		<p><b>Envoi de fichiers média</b> : Envoyez des images avec des définitions individuelles sans devoir changer les paramètres globaux.</p>
+		<p><b>Montage vidéo</b> : Découpez les vidéos avant de les envoyer. De plus, le processus de transcodage des vidéos a été amélioré et celui-ci fonctionne désormais en arrière-plan.</p>
+		<p><b>Enregistrer dans la galerie</b> : Sur Android 10 et plus récent, les médias sont maintenant stockés dans les dossiers système <i>Photos, Vidéos, Musique</i> et <i>Documents</i> à cause des nouvelles exigences de « Stockage de portée » de Google.</p>
+		<p><b>Recherche globale</b> : Recherchez du texte à travers tous les chats. Touchez simplement <i>Menu/Rechercher dans les discussions</i> lorsque vous êtes sur l\'écran d\'accueil de %1$s.</p>
+		<p><b>Citations</b> : %1$s vous permet maintenant de citer n\'importe quel type de média, y compris les images, les vidéos et les messages vocaux.</p>
+		<p><b>100 nouvelles émoticônes</b> : Découvrez la tant attendue fondue &#129749;</p>
+		<p><b>Textes longs</b> : Pour conserver une interface de chat allégée, les messages contenant beaucoup de texte s\'affichent dans une bulle de chat tronquée qui peut être développée à volonté.</p>
+    ]]>
+    </string>
     <string name="tooltip_identity_popup">Touchez ici pour afficher rapidement votre ID Threema ou scanner les ID d\'autres personnes</string>
     <string name="tap_to_start">Touchez ici pour lancer %s maintenant.</string>
     <string name="two_years">2 ans</string>
@@ -1120,7 +1134,7 @@ Veuillez saisir une question pour votre enquête.</string>
     <string name="an_error_occurred_during_send">Une erreur s’est produite lors de l’envoi d’un ou plusieurs messages.</string>
     <string name="state_processing">traitement</string>
     <string name="passphrase_locked">La phrase secrète est verrouillée</string>
-    <string name="image_labeling_new">Nouveau : Reconnaissance d’images</string>
+    <string name="image_labeling_new">Nouveau : Recherche d’image</string>
     <string name="tooltip_image_labeling">Pour vous aider à trouver certains médias visuels, Threema offre désormais la possibilité de rechercher et de filtrer votre galerie de médias en fonction d’un modèle local de reconnaissance d’images. Partez à la recherche !</string>
     <string name="selected_media">Votre sélection</string>
     <string name="attach_gif">Gif</string>
@@ -1139,4 +1153,23 @@ Veuillez saisir une question pour votre enquête.</string>
     <string name="max_selectable_media_exceeded">Impossible d’envoyer plus de %d objets à la fois.</string>
     <string name="error_unable_loading_media_thumb">Impossible de charger la miniature</string>
     <string name="select">Sélectionner</string>
+    <string name="filter_list">Liste de filtres</string>
+    <string name="hint_filter_list">Entrez un texte de filtre</string>
+    <string name="add">Ajouter</string>
+    <string name="threema_message_from">Message de %s</string>
+    <string name="show_text">Afficher texte</string>
+    <string name="only_images_or_videos">Seuls des images et des vidéos peuvent être sélectionnés</string>
+    <string name="media_gallery_gifs">GIF</string>
+    <string name="notification_channel_image_labeling">Indexation des images en cours</string>
+    <string name="notification_channel_image_labeling_desc">Indexation des images publiques de la galerie en arrière-plan</string>
+    <string name="notification_image_labeling_desc">Indexation de la galerie des médias</string>
+    <string name="no_media_found_global">Aucun média trouvé sur cet appareil</string>
+    <string name="prefs_sum_image_labeling">Activer la recherche par mots clés d’images publiques de votre galerie</string>
+    <string name="prefs_image_labeling">Recherche d’image</string>
+    <string name="enable_formatting">Activer le formattage</string>
+    <string name="original_file_no_longer_avilable">Le fichier original n’est plus accessible. Veuillez renvoyer le message.</string>
+    <string name="state_transcoding">transcodage</string>
+    <string name="importing_files">Importation des fichiers</string>
+    <string name="tooltip_image_resolution_hint">Ajuster les définitions des images.</string>
+    <string name="image_labeling_stuck_error">Le processus d\'indexation de la recherche d\'images s\'est bloqué et a été annulé. Veuillez réessayer plus tard.</string>
 </resources>

+ 21 - 1
app/src/main/res/values-it/strings.xml

@@ -988,7 +988,7 @@ messaggi ogni 15 minuti.</string>
     <string name="username_hint">Nome utente</string>
     <string name="lock_option_biometric">Biometrica</string>
     <string name="biometric_enter_authentication">Autenticarsi per sbloccare</string>
-    <string name="biometric_authentication_failed">Impossibile autenticare</string>
+    <string name="biometric_authentication_failed">Autenticazione non riuscita</string>
     <string name="biometric_authentication_successful">Autenticazione riuscita</string>
     <string name="work_safe_forced_explain">Il tuo amministratore ha attivato Threema Safe sul tuo dispositivo.</string>
     <string name="pin_locked_cannot_send">L\'app è bloccata. Invio non possibile.</string>
@@ -1035,6 +1035,7 @@ messaggi ogni 15 minuti.</string>
     <string name="num_archived_chats">%d chat archiviate</string>
     <string name="continue_recording">Continua registrazione</string>
     <string name="whatsnew_title">Ecco %s 4.5</string>
+    <string name="whatsnew_headline">Da oggi %1$s è ancora più bello e più facile da usare. Poiché non è più dipendente da applicazioni esterne, la tua privacy è garantita.\n\nIl modernissimo design Material è più intuitivo: per effettuare le diverse azioni sono necessari meno passaggi.\n\nTocca \"Per saperne di più\" e scoprirai molto di più sulle nuove emozionanti caratteristiche di %1$s 4.0.</string>
     <string name="whatsnew2_title">Quali sono le novità?</string>
     <string name="tooltip_identity_popup">Tocca qui per visualizzare rapidamente il tuo Threema ID o per scansionare gli ID di altre persone</string>
     <string name="tap_to_start">Tocca qui per iniziare %s adesso.</string>
@@ -1159,4 +1160,23 @@ messaggi ogni 15 minuti.</string>
     <string name="max_selectable_media_exceeded">Non è possibile inviare più di %d oggetti contemporaneamente.</string>
     <string name="error_unable_loading_media_thumb">Errore. Impossibile caricare la miniatura del contenuto multimediale</string>
     <string name="select">Seleziona</string>
+    <string name="filter_list">Filtra lista</string>
+    <string name="hint_filter_list">Inserisci testo filtrato</string>
+    <string name="add">Aggiungi</string>
+    <string name="threema_message_from">Messaggio di %s</string>
+    <string name="show_text">Mostra testo</string>
+    <string name="only_images_or_videos">È possibile selezionare solo immagini o video</string>
+    <string name="media_gallery_gifs">GIF</string>
+    <string name="notification_channel_image_labeling">Indicizzazione delle immagini in corso</string>
+    <string name="notification_channel_image_labeling_desc">Le immagini della galleria vengono indicizzate in background</string>
+    <string name="notification_image_labeling_desc">Indicizzazione delle immagini</string>
+    <string name="no_media_found_global">Nessun file media trovato su questo dispositivo</string>
+    <string name="prefs_sum_image_labeling">Abilita ricerca parola chiave per le immagini pubbliche nella galleria</string>
+    <string name="prefs_image_labeling">Riconoscimento immagine</string>
+    <string name="enable_formatting">Attiva formattazione</string>
+    <string name="original_file_no_longer_avilable">L\'accesso al file originale è scaduto. Invia nuovamente questo messaggio.</string>
+    <string name="state_transcoding">transcodifica</string>
+    <string name="importing_files">Importazione dati</string>
+    <string name="tooltip_image_resolution_hint">Adattare la risoluzione dell\'immagine.</string>
+    <string name="image_labeling_stuck_error">"Il processo di indicizzazione per il riconoscimento delle immagini si è interrotto. Riprova più tardi. "</string>
 </resources>

+ 42 - 9
app/src/main/res/values-nl-rNL/strings.xml

@@ -10,7 +10,7 @@
     <string name="title_adduser">Contactpersoon toevoegen</string>
     <string name="title_enter_id">ID invoeren</string>
     <string name="title_invite_friend">Een vriend uitnodigen</string>
-    <string name="invite_via">Een vriend uitnodigen via...</string>
+    <string name="invite_via">Een vriend uitnodigen via</string>
     <string name="invite_email_body">Hallo,\n\nIk gebruik %1$s, de veilige instant messenger die de privacy van haar gebruikers beschermt.\n\nMy Threema ID: https://threema.id/%2$s\n\nLaten we voortaan communiceren via %1$s!\n\nGroetjes,\n</string>
     <string name="invite_sms_body">Hallo! Laten we %1$s gebruiken om te communiceren in een beveiligde omgeving die de privacy beschermt! Mijn Threema-ID is: https://threema.id/%2$s</string>
     <string name="invite_email_subject">Threema. De veilige messenger die onze privacy beschermt</string>
@@ -960,7 +960,7 @@ Weet u zeker dat u Threema anoniem wil gebruiken?</string>
     <string name="hide_chat_enter_message_explain">Deze chat is aangemerkt als privéchat. Voer de toegangsbescherming in om de chat te openen.</string>
     <string name="unknown">Onbekend</string>
     <string name="miui_notification_title">Belangrijk bericht over berichten op MIUI 10</string>
-    <string name="miui_notification_body">MIUI 10 schakelt standaard de geluids- en de lichtsignalen uit voor alle nieuwe berichtenkanalen (behalve voor sommige apps die door Xiaomi zijn aangewezen als &lt;&lt;belangrijk&gt;&gt;). U moet ze via uw app-specifieke instellingsschermen voor elk kanaal handmatig weer inschakelen. Neem contact op met de fabrikant van uw telefoon voor meer informatie.</string>
+    <string name="miui_notification_body">MIUI 10 schakelt standaard de geluids- en de lichtsignalen uit voor alle nieuwe berichtenkanalen (behalve voor sommige apps die door Xiaomi zijn aangewezen als \"belangrijk\"). U moet ze via uw app-specifieke instellingsschermen voor elk kanaal handmatig weer inschakelen. Neem contact op met de fabrikant van uw telefoon voor meer informatie.</string>
     <string name="dont_show_again">Niet meer weergeven</string>
     <string name="miui_notification_prefs">Instellingen MIUI</string>
     <string name="threema_safe_upload_successful">Threema Safe back-upbestand geüpload</string>
@@ -969,8 +969,8 @@ Weet u zeker dat u Threema anoniem wil gebruiken?</string>
     <string name="username_hint">Gebruikersnaam</string>
     <string name="lock_option_biometric">Biometrisch</string>
     <string name="biometric_enter_authentication">Verifieer om te ontgrendelen</string>
-    <string name="biometric_authentication_failed">Verificatie is mislukt</string>
-    <string name="biometric_authentication_successful">Verifiëren is gelukt</string>
+    <string name="biometric_authentication_failed">Niet geverifieerd</string>
+    <string name="biometric_authentication_successful">Geverifieerd</string>
     <string name="work_safe_forced_explain">Uw administrator heeft uw apparaat toestemming gegeven Threema Safe te gebruiken.</string>
     <string name="pin_locked_cannot_send">De app is vergrendeld. Kan niet verzenden.</string>
     <string name="prefs_summary_hide_screenshots_notice">Let op: vanwege privacyredenen worden miniaturen en screenshots altijd geblokkeerd wanneer de appbescherming is ingeschakeld.</string>
@@ -985,8 +985,8 @@ Weet u zeker dat u Threema anoniem wil gebruiken?</string>
     <string name="voice_action_title">Stembediening</string>
     <string name="voice_action_body">Stembediening wordt verwerkt</string>
     <string name="permission_camera_photo_required">Verleen toegang tot de camera om een foto te maken</string>
-    <string name="global_search">Wereldwijd zoeken</string>
-    <string name="global_search_empty_view_text">Voer minstens twee tekens in om in alle berichten en contacten te zoeken</string>
+    <string name="global_search">Zoeken in chats</string>
+    <string name="global_search_empty_view_text">Voer minstens twee tekens in om in berichten te zoeken</string>
     <string name="my_id">Mijn ID</string>
     <string name="profile_picture_and_nickname">Profielfoto en bijnaam</string>
     <string name="lp_select_this_place">Selecteer deze plaatsen</string>
@@ -1016,7 +1016,21 @@ Weet u zeker dat u Threema anoniem wil gebruiken?</string>
     <string name="num_archived_chats">%d gearchiveerde chats</string>
     <string name="continue_recording">Doorgaan met opnemen</string>
     <string name="whatsnew_title">Welkom bij %s 4.5</string>
+    <string name="whatsnew_headline">%1$s 4.5 geeft je een heel nieuwe media-ervaring.\n\nDe mediafunctie is helemaal opnieuw ontworpen en heeft nu een optionele machineleerfunctie op basis van het zoeken van afbeeldingen.\n\nDaarnaast hebben we algemene zoekfuncties naar tekst voor alle chats ingevoeerd, het citeren van berichten verbeterd en nog veel meer.</string>
     <string name="whatsnew2_title">Wat is er nieuw?</string>
+    <string name="whatsnew2_body">
+        <![CDATA[
+		<p><b>Mediascherm</b>: Tik op het paperclippictogram om je mediabestanden in een scrollbaar zijscherm te bekijken. Als je niet wilt dat het zijscherm automatisch wordt geopend met de meest recente media, kun je de optie Snelle selectie uitschakelen in de chatinstellingen via <i>Instellingen / Chat / Snelle selectie media</i>.</p>
+		<p><b>Afbeeldingen zoeken</b>: Zoek in je afbeeldingen naar voorwerpen, activiteiten en locaties.<br>De afbeeldingsherkenning is gebaseerd op een lokaal machineleermodel en verzendt geen gegevens naar externe servers. Aangezien het analyseren van afbeeldingen een nogal intensieve taak kan zijn en lang kan duren, is deze optie standaard uitgeschakeld. Je kunt haar inschakelen via <i>Instellingen / Media &amp; Opslag / Afbeeldingen zoeken</i>.</p>
+		<p><b>Mediabestanden verzenden</b>: Afbeeldingen verzenden met individuele resoluties zonder de algemene instellingen te hoeven aanpassen.</p>
+		<p><b>Video bewerken</b>: Snijd video\'s bij voordat je ze verzendt. Daarnaast is het transcoderingsproces verbeterd en werkt dat nu in de achtergrond.</p>
+		<p><b>Opslaan in galerij</b>: Op Android 10 en hoger worden mediabestanden nu opgeslagen in de systeemmappen <i>Pictures, Video, Music,</i> en <i>Documents</i> vanwege de nieuwe «Scoped Storage»-vereisten van Google.</p>
+		<p><b>Algemene zoekopdrachten</b>: Zoek naar tekst in alle chats. Tik gewoon op <i>Menu / Chats zoeken</i> in het startscherm van %1$s.</p>
+		<p><b>Citaten</b>: Met %1$s kun je nu alle mediabestanden citeren, inclusief afbeeldingen, video\'s en gesproken berichten.</p>
+		<p><b>100 nieuwe emoji\'s</b>: Bekijk de langverwachte fondu &#129749;</p>
+		<p><b>Grotere tekstberichten</b>: Om het chatscherm overzichtelijk te houden, worden berichten met veel tekst ingekort in een spraakbubbel die je zelf kunt uitklappen.</p>
+		]]>
+    </string>
     <string name="tooltip_identity_popup">Tik hier om uw Threema-ID snel weer te geven of om de ID\'s van anderen te scannen</string>
     <string name="tap_to_start">Tik hier om %s te starten.</string>
     <string name="two_years">2 jaar</string>
@@ -1025,7 +1039,7 @@ Weet u zeker dat u Threema anoniem wil gebruiken?</string>
     <string name="prefs_sum_show_unread_badge">Een badge weergeven met het aantal ongelezen berichten naast het berichtenpictogram</string>
     <string name="prefs_title_show_unread_badge">Badge ongelezen berichten</string>
     <string name="pinning_not_trusted">Certificate pinning is mislukt. Controleer of het \"Entrust Root Certification Authority - G2\"-certificaat in de gegevensopslag van uw apparaat is geïnstalleerd en geactiveerd.</string>
-    <string name="pinning_failed">Certificate pinning is mislukt. Mogelijk wordt er een \'man-in-the-middle\'-aanval uitgevoerd. Als u een adblocker, contentfilter of firewall-app zoals &lt;&lt;AdGuard&gt;&gt; hebt geïnstalleerd, schakel deze dan uit voor Threema.</string>
+    <string name="pinning_failed">Certificate pinning is mislukt. Mogelijk wordt er een \'man-in-the-middle\'-aanval uitgevoerd. Als u een adblocker, contentfilter of firewall-app zoals \"AdGuard\" hebt geïnstalleerd, schakel deze dan uit voor Threema.</string>
     <string name="open_myid_popup">Pop-upvenster voor snelle toegang openen</string>
     <string name="logo">Logo/Naar boven scrollen</string>
     <string name="quote_subj_end">Einde citaat</string>
@@ -1037,7 +1051,7 @@ Weet u zeker dat u Threema anoniem wil gebruiken?</string>
     <string name="edit_type_content_description">%1$s %2$s bekijken of bewerken</string>
     <string name="group">Groep</string>
     <string name="send_location_privacy_policy_v4_0"><![CDATA[<p>Onze Privacybeleid is bijgewerkt om de volgende verandering weer te geven:</p><p>%1$s is niet langer afhankelijk van Google Play en Google Maps om kaart- en POI-gegevens te verstrekken.</p>Kijk het volledige Privacybeleid <a href="%2$s">hier</a>.]]></string>
-    <string name="play_services_not_installed_unable_to_use_push">Play Services niet geinstalleer. Kan niet overschakelen naar &lt;&lt;push&gt;&gt;.</string>
+    <string name="play_services_not_installed_unable_to_use_push">Play Services niet geinstalleer. Kan niet overschakelen naar \"push\".</string>
     <string name="unable_to_get_current_location">Kan huidige locatie niet bepalen.</string>
     <string name="lp_search_place_min_chars">Voer ten minste drie tekens in om een plaats te zoeken.</string>
     <string name="lp_search_place_no_matches">Geen overeenkomende plaatsen gevonden. Pas uw zoekopdracht aan.</string>
@@ -1121,7 +1135,7 @@ Weet u zeker dat u Threema anoniem wil gebruiken?</string>
     <string name="an_error_occurred_during_send">Er is een fout opgetreden tijdens het verzenden van de berichten.</string>
     <string name="state_processing">verwerken</string>
     <string name="passphrase_locked">De wachtwoordzin is vergrendeld</string>
-    <string name="image_labeling_new">Nieuw: beeldherkenning</string>
+    <string name="image_labeling_new">Nieuw: foto\'s zoeken</string>
     <string name="tooltip_image_labeling">Om specifieke visuele mediabestanden in je galerij te vinden, voer je een zoekwoord in die de inhoud beschrijft en selecteer je het bestand uit de lijst met geïdentificeerde labels.</string>
     <string name="selected_media">Je selectie</string>
     <string name="attach_gif">Gif</string>
@@ -1140,4 +1154,23 @@ Weet u zeker dat u Threema anoniem wil gebruiken?</string>
     <string name="max_selectable_media_exceeded">Je kunt max. %d objecten tegelijkertijd versturen</string>
     <string name="error_unable_loading_media_thumb">Fout: miniatuur van mediabestand niet geladen</string>
     <string name="select">Selecteren</string>
+    <string name="filter_list">Lijst filteren</string>
+    <string name="hint_filter_list">Filtertekst invoeren</string>
+    <string name="add">Toevoegen</string>
+    <string name="threema_message_from">Bericht van %s</string>
+    <string name="show_text">Tekst tonen</string>
+    <string name="only_images_or_videos">Alleen foto\'s of video\'s kunnen worden geselecteerd</string>
+    <string name="media_gallery_gifs">GIFS</string>
+    <string name="notification_channel_image_labeling">Voortgang foto-indexering</string>
+    <string name="notification_channel_image_labeling_desc">Indexering van openbare fotogalerijen in de achtergrond</string>
+    <string name="notification_image_labeling_desc">Mediagalerij indexeren</string>
+    <string name="no_media_found_global">Geen media gevonden op dit apparaat</string>
+    <string name="prefs_sum_image_labeling">Zoeken naar openbare foto\'s in je galerij via zoekwoorden mogelijk maken</string>
+    <string name="prefs_image_labeling">Foto zoeken</string>
+    <string name="enable_formatting">Opmaak inschakelen</string>
+    <string name="original_file_no_longer_avilable">Het oorspronkelijke bestand is niet langer beschikbaar. Verzend het bericht opnieuw.</string>
+    <string name="state_transcoding">transcoderen…</string>
+    <string name="importing_files">Bestanden importeren…</string>
+    <string name="tooltip_image_resolution_hint">Afbeeldingsresoluties aanpassen.</string>
+    <string name="image_labeling_stuck_error">Het indexeringsproces voor het zoekproces is vastgelopen en geannuleerd. Probeer het later opnieuw.</string>
 </resources>

+ 38 - 7
app/src/main/res/values-pl/strings.xml

@@ -985,8 +985,8 @@ anonimowo?</string>
     <string name="username_hint">Nazwa użytkownika</string>
     <string name="lock_option_biometric">Biometryczna</string>
     <string name="biometric_enter_authentication">Aby odblokować, dokonaj uwierzytelnienia</string>
-    <string name="biometric_authentication_failed">Uwierzytelnianie nieudane</string>
-    <string name="biometric_authentication_successful">Uwierzytelnianie udane</string>
+    <string name="biometric_authentication_failed">Nieudane uwierzytelnienie</string>
+    <string name="biometric_authentication_successful">Pomyślne uwierzytelnienie</string>
     <string name="work_safe_forced_explain">Twój administrator włączył Threema Safe dla Twojego urządzenia.</string>
     <string name="pin_locked_cannot_send">Aplikacja jest zablokowana. Nie można wysłać.</string>
     <string name="prefs_summary_hide_screenshots_notice">Uwaga: W trosce o prywatność miniaturki i zrzuty ekranu są dezaktywowane, gdy włączona jest ochrona aplikacji.</string>
@@ -1001,8 +1001,8 @@ anonimowo?</string>
     <string name="voice_action_title">Polecenia głosowe</string>
     <string name="voice_action_body">Polecenie głosowe jest przetwarzane</string>
     <string name="permission_camera_photo_required">Aby móc zrobić zdjęcie, zezwól na dostęp do aparatu</string>
-    <string name="global_search">Wyszukiwanie globalne</string>
-    <string name="global_search_empty_view_text">Wprowadź co najmniej dwa znaki, aby wyszukać we wszystkich wiadomościach i kontaktach</string>
+    <string name="global_search">Przeszukaj czaty</string>
+    <string name="global_search_empty_view_text">Wprowadź co najmniej dwa znaki, aby przeszukać wiadomości</string>
     <string name="my_id">Mój identyfikator</string>
     <string name="profile_picture_and_nickname">Zdjęcie profilowe i nick</string>
     <string name="lp_select_this_place">Wybierz to miejsce</string>
@@ -1024,15 +1024,27 @@ anonimowo?</string>
     <string name="add_contact_enter_id_hint">Wprowadź identyfikator Threema kontaktu, który chcesz dodać</string>
     <string name="notification_channel_new_contact">Nowe kontakty</string>
     <string name="notification_channel_new_contact_desc">Powiadomienie o nowych kontaktach</string>
-    <string name="notification_contact_has_joined">%1$s korzysta z %2$s. Dotknij tutaj, aby wysłać wiadomość.</string>
-    <string name="notification_contact_has_joined_multiple">%1$d kontakty, które dołączyły do %2$s: %3$s. Dotknij tutaj, aby wysłać im wiadomość.</string>
+    <string name="notification_contact_has_joined">Dołączono %1$s do %2$s. Dotknij tutaj, aby wysłać wiadomość.</string>
+    <string name="notification_contact_has_joined_multiple">%1$d kontakty dołączyły do %2$s: %3$s. Dotknij tutaj, aby wysłać im wiadomość.</string>
     <string name="system_default">Domyślne ustawienia systemowe</string>
     <string name="open_in_maps_app">Otwórz w aplikacji Mapy</string>
     <string name="delete">Usuń</string>
     <string name="num_archived_chats">Zarchiwizowane czaty: %d</string>
     <string name="continue_recording">Kontynuuj nagrywanie</string>
     <string name="whatsnew_title">Witaj w %s 4.5</string>
+    <string name="whatsnew_headline">%1$s 4.5 przynosi zupełnie nowe sposoby korzystania z multimediów.\n\nInterfejs szuflady na multimedia został poddany gruntownemu przebudowie i zawiera teraz opcję wyszukiwania obrazów wykorzystującą technologię uczenia maszynowego.\n\nPonadto dodaliśmy globalne wyszukiwanie tekstu we wszystkich czatach, ulepszone cytowanie wiadomości i inne funkcje.</string>
     <string name="whatsnew2_title">Co nowego?</string>
+    <string name="whatsnew2_body"><![CDATA[
+		<p><b>Szuflada na multimedia</b>: Dotknij ikony spinacza, gdy chcesz przeglądać swoje pliki multimedialne w przewijalanej szufladzie . Jeśli nie chcesz, aby szuflada otwierała się automatycznie z najnowszymi multimediami, wyłącz opcję szybkiego wyboru obrazu w ustawieniach czatu w opcji  <i>Ustawienia / Czat / Szybki wybór multimediów</i>.</p>
+		<p><b>Wyszukiwanie obrazów</b>: Przeszukuj swoje obrazy w poszukiwaniu zwykłych przedmiotów, aktywności i miejsc.<br>Rozpoznawanie obrazów opiera się na lokalnym modelu uczenia maszynowego i nie wysyła danych na serwer Threema ani do firm zewnętrznych. Ponieważ analiza obrazów jest funkcjonalnością stosunkowo kosztowną i czynność ta może zajmować dużo czasu, opcja ta jest domyślnie wyłączona. Znajdziesz ją w <i>Ustawienia / Multimedia i pamięć / Wyszukiwanie obrazów</i>.</p>
+		<p><b>Wysyłanie plików multimedialnych</b>: Wysyłaj obrazy, określając ich własną rozdzielczość, bez konieczności zmieniania ustawień globalnych.</p>
+		<p><b>Edytor filmów</b>: Przycinaj filmy przed wysłaniem. Ponadto usprawniliśmy transkodowanie filmów i proces ten przebiega teraz działa w tle.</p>
+		<p><b>Zapisz w galerii</b>: W systemie Android 10 i wyższych, multimedia są teraz przechowywane w folderach systemowych <i>Zdjęcia, Filmy, Muzyka,</i> i <i>Dokumenty</i> ze względu na nowe wymagania Google\'a dotyczące funkcji «Scoped Storage».</p>
+		<p><b>Wyszukiwanie globalne</b>: Wyszukiwanie tekstu we wszystkich czatach. Wystarczy dotknąć <i>Menu / Przeszukaj czaty</i>, gdy jesteś na ekranie głównym %1$s.</p>
+		<p><b>Cytaty</b>:%1$s teraz ta opcja pozwala Ci cytować dowolny rodzaj multimediów, w tym obrazy, filmy i wiadomości głosowe .</p>
+		<p><b>100 nowych emotek</b>: Sprawdź długo oczekiwane fondue &#129749;</p>
+		<p><b>Długie teksty</b>: aby zachować uproszczony interfejs czatu, wiadomości z dużą ilością tekstu są wyświetlane w obciętym dymku i mogą zostać rozwinięte na żądanie.</p>
+]]></string>
     <string name="tooltip_identity_popup">Dotknij tutaj, aby szybko wyświetlić swój identyfikator Threema albo zeskanować identyfikatory innych osób</string>
     <string name="tap_to_start">Dotknij tutaj, aby włączyć %s teraz.</string>
     <string name="two_years">2 lata</string>
@@ -1137,7 +1149,7 @@ anonimowo?</string>
     <string name="an_error_occurred_during_send">Wystąpił błąd podczas wysyłania jednej lub kilku wiadomości.</string>
     <string name="state_processing">przetwarzanie</string>
     <string name="passphrase_locked">Hasło jest zablokowane</string>
-    <string name="image_labeling_new">Nowość: Rozpoznawanie obrazów</string>
+    <string name="image_labeling_new">Nowość: wyszukiwanie obrazów</string>
     <string name="tooltip_image_labeling">Aby znaleźć konkretny materiał wizualny w swojej galerii, wystarczy wpisać termin opisujący jego zawartość i wybrać go z listy zidentyfikowanych etykiet.</string>
     <string name="selected_media">Twój wybór</string>
     <string name="attach_gif">Gif</string>
@@ -1156,4 +1168,23 @@ anonimowo?</string>
     <string name="max_selectable_media_exceeded">Możesz wysłać maksymalnie %d obieky(-ów) naraz</string>
     <string name="error_unable_loading_media_thumb">Błąd. Nie udało się wczytać miniaturki pliku multimedialnego</string>
     <string name="select">Zaznacz</string>
+    <string name="filter_list">Lista filtrów</string>
+    <string name="hint_filter_list">Wprowadź tekst filtru</string>
+    <string name="add">Dodaj</string>
+    <string name="threema_message_from">Wiadomość od %s</string>
+    <string name="show_text">Pokaż tekst</string>
+    <string name="only_images_or_videos">Można wybrać tylko obrazy lub filmy</string>
+    <string name="media_gallery_gifs">GIF-y</string>
+    <string name="notification_channel_image_labeling">Postęp indeksowania obrazów</string>
+    <string name="notification_channel_image_labeling_desc">Trwa indeksowanie w tle obrazów z publicznej galerii</string>
+    <string name="notification_image_labeling_desc">Indeksowanie galerii multimediów</string>
+    <string name="no_media_found_global">Nie znaleziono multimediów na tym urządzeniu</string>
+    <string name="prefs_sum_image_labeling">Włącz wyszukiwanie publicznych obrazów w Twojej galerii wg słów kluczowych</string>
+    <string name="prefs_image_labeling">Wyszukiwanie obrazów</string>
+    <string name="enable_formatting">Włącz formatowanie</string>
+    <string name="original_file_no_longer_avilable">Oryginalny plik nie jest już dostępny. Wyślij  wiadomość ponownie.</string>
+    <string name="state_transcoding">Transkodowanie</string>
+    <string name="importing_files">Importowanie plików</string>
+    <string name="tooltip_image_resolution_hint">Skoryguj rozdzielczości.</string>
+    <string name="image_labeling_stuck_error">Proces indeksacji do wyszukiwania obrazów został zatrzymany i anulowany. Spróbuj później.</string>
 </resources>

+ 38 - 5
app/src/main/res/values-pt-rBR/strings.xml

@@ -973,7 +973,7 @@ ficará órfã. Os outros membros ainda poderão conversar, mas não serão mais
     <string name="username_hint">Usuário</string>
     <string name="lock_option_biometric">Biométrico</string>
     <string name="biometric_enter_authentication">Para desbloquear, faça a autenticação</string>
-    <string name="biometric_authentication_failed">Falha na autenticação</string>
+    <string name="biometric_authentication_failed">A autenticação falhou</string>
     <string name="biometric_authentication_successful">Autenticação bem-sucedida</string>
     <string name="work_safe_forced_explain">Seu administrador permitiu o Threema Safe no seu dispositivo.</string>
     <string name="pin_locked_cannot_send">Aplicativo bloqueado. Não é possível enviar.</string>
@@ -989,8 +989,8 @@ ficará órfã. Os outros membros ainda poderão conversar, mas não serão mais
     <string name="voice_action_title">Ações de voz</string>
     <string name="voice_action_body">A ação de voz está sendo processada</string>
     <string name="permission_camera_photo_required">Para tirar uma foto, conceda o acesso à camera</string>
-    <string name="global_search">Pesquisa global</string>
-    <string name="global_search_empty_view_text">Insira pelo menos dois caracteres para pesquisar em todas as mensagens e contatos</string>
+    <string name="global_search">Pesquisar conversas</string>
+    <string name="global_search_empty_view_text">Insira pelo menos dois caracteres para pesquisar nas mensagens</string>
     <string name="my_id">Minha ID</string>
     <string name="profile_picture_and_nickname">Foto de perfil e apelido</string>
     <string name="lp_select_this_place">Selecione este local</string>
@@ -1020,7 +1020,21 @@ ficará órfã. Os outros membros ainda poderão conversar, mas não serão mais
     <string name="num_archived_chats">%d conversas arquivadas</string>
     <string name="continue_recording">Continuar gravação</string>
     <string name="whatsnew_title">Boas-vindas ao %s 4.5</string>
+    <string name="whatsnew_headline">%1$s 4.5 traz uma experiência de mídia totalmente nova.\n\nA interface da gaveta de mídia foi redesenhada e agora traz uma pesquisa de imagem opcional com machine learning (aprendizagem de máquina).\n\nAlém disso, adicionamos a pesquisa de texto global em todas as conversas, citação de mensagens melhorada e mais.</string>
     <string name="whatsnew2_title">Quais novidades?</string>
+    <string name="whatsnew2_body">
+    <![CDATA[
+<p><b>Gaveta de mídia</b>: Toque no ícone de clipe para navegar pelos seus arquivos de mídia em uma gaveta com barra de rolagem. Caso você não deseje que a gaveta abra automaticamente com os itens de mídia mais recentes, desative a opção de seleção rápida de imagens nas configurações de chat, em <i>Configurações / Chat / Seleção rápida de mídia </i>.</p>
+<p><b>Pesquisa de imagens</b>: Busque imagens de objetos comuns, atividades e lugares.<br>O reconhecimento de imagem é baseado em um modelo de machine learning local e não envia dados para o servidor do Threema ou de terceiros. Como a análise de imagens é uma tarefa pesada e pode levar muito tempo, a opção está desativada por padrão. Você a encontrará em <i>Configurações / Mídia e armazenamento / Pesquisa de imagens</i>.</p>
+<p><b>Envio de arquivos de mídia</b>: Envie imagens com resoluções individuais sem precisar alterar as configurações globais.</p>
+<p><b>Editor de vídeo</b>: Corte vídeos antes de enviar. Além disso, o processo de transcodificação de vídeo foi melhorado e funciona em segundo plano.</p>
+<p><b>Salvar na galeria</b>: No Android 10 e mais recentes, a mídia é armazenada nas pastas <i>Fotos, Vídeo, Música</i> e <i>Documentos</i> do sistema devido às novas exigências de «armazenamento com escopo» do Google.</p>
+<p><b>Pesquisa global</b>: Busque textos em todas as conversas. Basta tocar em <i>Menu / Pesquisar conversas</i> quando estiver na tela inicial do %1$s.</p>
+<p><b>Citações</b>: Agora, o %1$s permite que você cite qualquer tipo de mídia, incluindo imagens, vídeos e mensagens de voz.</p>
+<p><b>100 novos emojis</b>: Confira o tão esperado fondue &#129749;</p>
+<p><b>Textos maiores</b>: Para manter a interface das conversas limpa, mensagens com muito texto serão exibidas em uma bolha de chat que pode ser expandida conforme a necessidade.</p>
+	]]>
+    </string>
     <string name="tooltip_identity_popup">Toque aqui para exibir a sua ID do Threema ou escanear IDs de outras pessoas</string>
     <string name="tap_to_start">Toque aqui para iniciar o %s agora.</string>
     <string name="two_years">2 anos</string>
@@ -1120,12 +1134,12 @@ ficará órfã. Os outros membros ainda poderão conversar, mas não serão mais
     <string name="prefs_sum_disable_smart_replies">Inibir Respostas Inteligentes nas notificações do Android</string>
     <string name="prefs_title_disable_smart_replies">Desativar Respostas Inteligentes</string>
     <string name="url_warning_body_alt">O nome do host do link que você está prestes a abrir é suspeito.\n\nIsso pode ser uma tentativa para fazer com que você abra um site que finge ser outro.\n\nGostaria de continuar mesmo assim?</string>
-    <string name="read_on">Ler mais</string>
+    <string name="read_on">Ler mais...</string>
     <string name="forward_text">Encaminhar mensagem</string>
     <string name="an_error_occurred_during_send">Ocorreu um erro ao enviar uma ou mais mensagens.</string>
     <string name="state_processing">processando</string>
     <string name="passphrase_locked">A frase secreta está bloqueada</string>
-    <string name="image_labeling_new">Novo: reconhecimento de imagem</string>
+    <string name="image_labeling_new">Novo: pesquisa de imagem</string>
     <string name="tooltip_image_labeling">Para encontrar uma mídia visual específica na sua galeria, digite um termo que descreve o conteúdo e o selecione em uma lista de rótulos identificados.</string>
     <string name="selected_media">Sua seleção</string>
     <string name="attach_gif">Gif</string>
@@ -1144,4 +1158,23 @@ ficará órfã. Os outros membros ainda poderão conversar, mas não serão mais
     <string name="max_selectable_media_exceeded">Até %d objetos podem ser enviados de uma só vez.</string>
     <string name="error_unable_loading_media_thumb">Erro. Miniatura de mídia não pôde ser carregada</string>
     <string name="select">Selecionar</string>
+    <string name="filter_list">Filtrar lista</string>
+    <string name="hint_filter_list">Insira o texto a filtrar</string>
+    <string name="add">Adicionar</string>
+    <string name="threema_message_from">Mensagem de %s</string>
+    <string name="show_text">Mostrar texto</string>
+    <string name="only_images_or_videos">Somente imagens ou vídeos podem ser selecionados</string>
+    <string name="media_gallery_gifs">GIFs</string>
+    <string name="notification_channel_image_labeling">Progresso de indexação de imagem</string>
+    <string name="notification_channel_image_labeling_desc">Executando a indexação de imagens da galeria pública em segundo plano</string>
+    <string name="notification_image_labeling_desc">Indexação da galeria de mídia</string>
+    <string name="no_media_found_global">Nenhuma mídia encontrada neste dispositivo</string>
+    <string name="prefs_sum_image_labeling">Ative a pesquisa de imagens públicas na sua galeria com palavras-chave</string>
+    <string name="prefs_image_labeling">Pesquisa de imagem</string>
+    <string name="enable_formatting">Ativar formatação</string>
+    <string name="original_file_no_longer_avilable">O arquivo original não está mais acessível. Reenvie a mensagem.</string>
+    <string name="state_transcoding">transcodificação</string>
+    <string name="importing_files">Importando arquivos</string>
+    <string name="tooltip_image_resolution_hint">Ajuste a resolução das imagens.</string>
+    <string name="image_labeling_stuck_error">O processo de indexação para a pesquisa de imagens travou e foi cancelado. Tente novamente mais tarde.</string>
 </resources>

+ 68 - 10
app/src/main/res/values-rm/strings.xml

@@ -11,7 +11,10 @@
     <string name="title_enter_id">Endatar l\'ID</string>
     <string name="title_invite_friend">Envidar amis</string>
     <string name="invite_via">Envidar via ...</string>
-	<string name="invite_email_subject">Threema. Il messenger segir che protegia tia e mia sfera privata.</string>
+    <string name="invite_email_subject">Threema. Il messenger segir che protegia tia e mia sfera privata.</string>
+    <string name="invite_email_body">Hallo\n\nTelechargia Threema, il messenger segir che protegia tia e mia sfera privata.\n\nTrametta fotos, videos, messadis discurrids e lieus; fa gruppas ed enquistas; trametta documents e datotecas en tut ils formats (PDF, GIF, MP3 eav.).\n\nLain en futur communitgar ensemen via Threema.\n\nTelechargia Threema qua: https://threema.ch/download\n\nChars salids</string>
+    <string name="invite_sms_body">Lain en futur communitgar ensemen via Threema. Telechargia qua Threema:
+https://threema.ch/download</string>
     <string name="enter_id_hint">Endatar l\'ID da Threema</string>
     <string name="account_links">Colliaziuns</string>
     <string name="menu_settings">Configuraziuns</string>
@@ -34,7 +37,7 @@
     <string name="prefs_header_reset">Maletg dal fund davos</string>
     <string name="prefs_header_keyboard">Tastatura</string>
     <string name="prefs_sum_sync_contacts_on">Colliar ils contacts da Threema cun il cudesch d\'adressas da l\'apparat</string>
-	<string name="prefs_sum_sync_contacts_off">Allontanar la colliaziun tranter Threema ed il cudesch d\'adressas</string>
+    <string name="prefs_sum_sync_contacts_off">Allontanar la colliaziun tranter Threema ed il cudesch d\'adressas</string>
     <string name="prefs_title_sync_contacts">Sincronisar ils contacts</string>
     <string name="prefs_sum_block_unknown_off">Mintgin po trametter messadis a tai. Novs contacts vegnan agiuntads automaticamain suenter l\'emprim messadi.</string>
     <string name="prefs_sum_block_unknown_on">Mo persunas sin tia glista da contacts pon tramettar messadis a tai.</string>
@@ -163,9 +166,6 @@
     <string name="backup_share_content">Las suandantas datas da backup pon vegnir utilisadas ensemen cun il pled-clav tschernì per restaurar tia ID.</string>
     <string name="backup_share_subject">Backup da l\'ID da Threema per</string>
     <string name="add_attachment">Encollar</string>
-    <string name="attach_picture">Maletg</string>
-    <string name="attach_video">Video</string>
-    <string name="attach_location">Lieu</string>
     <string name="invalid_passphrase">Frasa secreta nunvalaivla</string>
     <string name="master_key_locked">La clav principala è bloccada</string>
     <string name="master_key_locked_notify_description">Endatar la frasa secreta</string>
@@ -391,7 +391,7 @@ Endatescha in pled-clav per tes backup da datas.</string>
     <string name="no_matching_distribution_lists">Anc definì naginas glistas da distribuziun.</string>
     <string name="is_typing">scriva gist ...</string>
     <string name="push_not_available_title">Il servetsch da push n\'è betg a disposiziun</string>
-    <string name="push_not_available_text">"Chattà nagin servetsch da push sin tes apparat. Sche ti cuntinueschas, vegnan messadis da push deactivads e Threema vegn a controllar mintga 15 minutas, sch'i dat novs messadis."</string>
+    <string name="push_not_available_text">Chattà nagin servetsch da push sin tes apparat. Sche ti cuntinueschas, vegnan messadis da push deactivads e Threema vegn a controllar mintga 15 minutas, sch\'i dat novs messadis.</string>
     <string name="backup_in_progress">Las datas vegnan segiradas</string>
     <string name="backup_or_restore_success_body">Las datas èn vegnidas segiradas cun success</string>
     <string name="backup_or_restore_error">Segirada da datas da Threema</string>
@@ -474,7 +474,7 @@ Endatescha in pled-clav per tes backup da datas.</string>
     <string name="contact_state_invalid">Nunvalaivel</string>
     <string name="back">Enavos</string>
     <string name="wearable_reply">Respunder</string>
-    <string name="wearable_reply_label">Respunder a %1$s</string>
+    <string name="wearable_reply_label">Respunder a %s</string>
     <string name="message_acknowledged">Confermar il messadi</string>
     <string name="push_disable_text">Sche ti cuntinueschas, vegnan messadis da push deactivads e Threema vegn a controllar mintga 15 minutas, sch\'i dat novs messadis.</string>
     <string name="ballot_intermediate_results_show">Mussar resultats intermediars</string>
@@ -827,9 +827,7 @@ Endatescha in pled-clav per tes backup da datas.</string>
     <string name="prefs_auto_download_title">Telechargiar automaticamain las medias</string>
     <string name="prefs_auto_download_wifi">En il WLAN</string>
     <string name="prefs_auto_download_mobile">En raits mobilas</string>
-    <string name="rate_intro">Nus avain midà ad in nov design pli modern, perquai ch\'i na dat per ils emojis vegls nagins updates pli.
-
-Co plaschan ils novs emojis a tai?</string>
+    <string name="rate_intro">Nus avain midà ad in nov design pli modern, perquai ch\'i na dat per ils emojis vegls nagins updates pli. Co plaschan ils novs emojis a tai?</string>
     <string name="rate_feedback_intro">Nus deplorain d\'udir quai. Schai p.pl. a nus tge che nus pudain far meglier.</string>
     <string name="rate_positive">Trametter recensiun</string>
     <string name="rate_title">Valitar ils novs emojis</string>
@@ -1088,5 +1086,65 @@ Co plaschan ils novs emojis a tai?</string>
     <string name="permission_camera_videocall_required">Activescha l\'autorisaziun da la camera per far videotelefonats</string>
     <string name="feedback">Feedback</string>
     <string name="tooltip_voip_enable_speakerphone">Cliccar qua per activar l\'opziun da telefonar a maun liber</string>
+    <string name="ballot_open">Enquistas avertas</string>
+    <string name="translators">Translatur</string>
+    <string name="credits">Engraziaments</string>
+    <string name="translators_thanks">Grazia fitg a noss translaturs en uffizi d\'onur:\n%s</string>
+    <string name="ballot_window_hide">Zuppentar las enquistas avertas</string>
+    <string name="ballot_window_show">Mussar las enquistas avertas</string>
+    <string name="tooltip_video_call">Ultra da telefonats segirs porscha Threema uss era videotelefonats cun codaziun end-to-end.</string>
+    <string name="tooltip_voip_other_party_video_on">Tes partenari da discurs ha cumenzà in videotelefonat. Smatga qua per medemamain activar la camera.</string>
+    <string name="tooltip_voip_other_party_video_disabled">Tes partenari da discurs utilisescha ina versiun da l\'app betg actuala u na permetta nagins videotelefonats.</string>
+    <string name="video_calls_new">Nov: videotelefonats</string>
+    <string name="biometrics_not_enrolled">Deponì naginas datas biometricas en il sistem.</string>
+    <string name="biometrics_not_avilable">L\'autentificaziun biometrica n\'è betg disponibla.</string>
+    <string name="biometrics_no_permission">Nagin dretg d\'acceder a las datas biometricas.</string>
+    <string name="verification_settings_desc">Ils puncts inditgeschan il stgalim da confidenza per in contact.</string>
     <string name="verification_levels_title">Stgalim da segirezza</string>
+    <string name="work_verification_levels_title">Contacts en vossa organisaziun</string>
+    <string name="external_verification_levels_title">Auters contacts</string>
+    <string name="switch_flash">Commutar il modus da chametg</string>
+    <string name="message_not_found">Betg chattà il messadi</string>
+    <string name="insert_datetime">Agiuntar la data ed il temp</string>
+    <string name="prefs_sum_disable_smart_replies">Supprimer propostas da resposta en avis d\'Android</string>
+    <string name="prefs_title_disable_smart_replies">Deactivar propostas da resposta</string>
+    <string name="url_warning_body_alt">La pagina-web che ti vuls avrir è dubiusa.\n\nI pudess esser in\'emprova da sviar tai sin ina pagina-web falsifitgada.\n\nVuls ti tuttina cuntinuar?</string>
+    <string name="read_on">Leger vinavant ...</string>
+    <string name="forward_text">Renviar il text</string>
+    <string name="an_error_occurred_during_send">Igl ha dà in errur cun trametter in u plirs messadis.</string>
+    <string name="state_processing">vegn elavurà</string>
+    <string name="passphrase_locked">Die Passphrase ist gesperrt</string>
+    <string name="image_labeling_new">Nov: tschertga da maletgs</string>
+    <string name="tooltip_image_labeling">Endatescha ina noziun che descriva il cuntegn u al selecziunescha da la glista da las descripziuns chattadas per tschertgar in tschert medium visual en tia gallaria.</string>
+    <string name="selected_media">Tia selecziun</string>
+    <string name="attach_gif">GIF</string>
+    <string name="attach_gallery">Gallaria</string>
+    <string name="attach_picture">Maletg</string>
+    <string name="attach_video">Video</string>
+    <string name="attach_location">Lieu</string>
+    <string name="image_labeling_toolbar_title">Filtrar tenor</string>
+    <string name="no_labels_info">Chattà nagins resultats</string>
+    <string name="image_label_query_hint">Object, lieu, activitad?</string>
+    <string name="filter_by_album">Filtrar tenor ordinatur</string>
+    <string name="media_date_taken">Data: %s</string>
+    <string name="media_date_added">Agiuntà la data: %s</string>
+    <string name="media_date_modified">Midà la data: %s</string>
+    <string name="media_date_unknown">Data betg enconuschenta</string>
+    <string name="max_selectable_media_exceeded">Ti pos trametter max. %d objects enina.</string>
+    <string name="error_unable_loading_media_thumb">Betg pussaivel da chargiar ina prevista</string>
+    <string name="select">Selecziunar</string>
+    <string name="filter_list">Filtrar la glista</string>
+    <string name="hint_filter_list">Endatar il text dal filter</string>
+    <string name="add">Agiuntar</string>
+    <string name="threema_message_from">Messadi da %s</string>
+    <string name="show_text">Mussar il text</string>
+    <string name="only_images_or_videos">Ti pos selecziunar mo maletgs e videos</string>
+    <string name="media_gallery_gifs">GIFs</string>
+    <string name="notification_channel_image_labeling">Progress da l\'indexaziun dals maletgs</string>
+    <string name="notification_channel_image_labeling_desc">Ils maletgs en la gallaria vegnan indexads davosvart</string>
+    <string name="notification_image_labeling_desc">Indexaziun dals maletgs</string>
+    <string name="no_media_found_global">Chattà naginas medias sin quest apparat</string>
+    <string name="prefs_image_labeling">Tschertga da maletgs</string>
+    <string name="enable_formatting">Activar la formataziun</string>
+    <string name="importing_files">Las datotecas vegnan importadas</string>
 </resources>

+ 38 - 5
app/src/main/res/values-ru/strings.xml

@@ -983,8 +983,8 @@
     <string name="voice_action_title">Голосовые действия</string>
     <string name="voice_action_body">Выполняется обработка голосового действия</string>
     <string name="permission_camera_photo_required">Чтобы сделать фото, предоставьте доступ к камере</string>
-    <string name="global_search">Глобальный поиск</string>
-    <string name="global_search_empty_view_text">Введите не менее двух символов для поиска во всех сообщениях и контактах</string>
+    <string name="global_search">Поиск в чатах</string>
+    <string name="global_search_empty_view_text">Введите не менее двух символов для поиска в сообщениях</string>
     <string name="my_id">Мой ID</string>
     <string name="profile_picture_and_nickname">Аватар и ник</string>
     <string name="lp_select_this_place">Выбрать это место</string>
@@ -1006,15 +1006,29 @@
     <string name="add_contact_enter_id_hint">Введите Threema ID контакта, который вы хотите добавить</string>
     <string name="notification_channel_new_contact">Новые контакты</string>
     <string name="notification_channel_new_contact_desc">Уведомления о новых контактах</string>
-    <string name="notification_contact_has_joined">%1$s присоедин. к %2$s. Коснитесь, чтобы отправить сообщение.</string>
-    <string name="notification_contact_has_joined_multiple">%1$d контакта(ов) присоединились к %2$s: %3$s. Коснитесь, чтобы отправить им сообщение.</string>
+    <string name="notification_contact_has_joined">%1$s присоед. к %2$s. Коснитесь, чтобы отправить сообщение.</string>
+    <string name="notification_contact_has_joined_multiple">%1$d контакта(ов) присоед. к %2$s: %3$s. Коснитесь, чтобы отправить им сообщение.</string>
     <string name="system_default">Системный по умолчан.</string>
     <string name="open_in_maps_app">Открыть в прил. «Карты»</string>
     <string name="delete">Удалить</string>
     <string name="num_archived_chats">Архивированных чатов: %d</string>
     <string name="continue_recording">Продолжить запись</string>
     <string name="whatsnew_title">Добро пожаловать в %s 4.5</string>
+    <string name="whatsnew_headline">Версия %1$s 4.5 кардинально расширяет возможности работы с мультимедиа.\n\nИнтерфейс панели мультимедиа был полностью переработан и теперь включает дополнительную функцию поиска по изображениям с алгоритмом машинного обучения.\n\nКроме того, мы добавили функцию глобального текстового поиска по всем чатам, улучшили цитирование сообщений и др.</string>
     <string name="whatsnew2_title">Что нового?</string>
+    <string name="whatsnew2_body">
+        <![CDATA[
+		<p><b>Панель мультимедиа</b>: Коснитесь значка скрепки для просмотра своих мультимедийных файлов в панели с возможностью прокрутки. Если вы не хотите, чтобы панель раскрывалась автоматически, показывая последние просмотренные мультимедийные файлы, отключите параметр быстрого выбора изображений в настройках чата — меню <i>Настройки / Чат / Быстрый выбор изображения</i>.</p>
+		<p><b>Поиск изображений</b>: Выполняйте в своих изображениях поиск стандартных объектов, действий и мест.<br>Распознавание изображений основано на локальной модели машинного обучения и не передает данные на сервер Threema или третьим лицам. Поскольку анализ изображений — это довольно ресурсоемкая задача, выполнение которой может занять долгое время, по умолчанию этот параметр отключен. Для доступа к нему откройте меню <i>Настройки / Мультимедиа / Поиск изображений</i>.</p>
+		<p><b>Отправка мультимедийных файлов</b>: Отправляйте изображения с нужным разрешением без необходимости менять общие настройки.</p>
+		<p><b>Видеоредактор</b>: Выполняйте обрезку видео перед их отправкой. Кроме того, мы доработали процесс перекодировки видео, и теперь он работает в фоновом режиме.</p>
+		<p><b>Сохранение в галерее</b>: Из-за новых требований компании Google (концепция «Scoped Storage») в системе Android 10 и выше мультимедийные файлы теперь хранятся в системных папках <i>Изображения, Видео, Музыка</i> и <i>Документы</i>.</p>
+		<p><b>Глобальный поиск</b>: Поиск текста по всем чатам. На главном экране %1$s коснитесь <i>Меню / Поиск в чатах</i>.</p>
+		<p><b>Цитаты</b>: %1$s теперь позволяет цитировать любые мультимедийные сообщения, в том числе изображения, видео и голосовые сообщения.</p>
+		<p><b>100 новых эмодзи</b>: Ознакомьтесь с долгожданным набором &#129749;</p>
+		<p><b>Крупные тексты</b>: Чтобы не загромождать интерфейс чата, мы теперь показываем сообщения с большим количеством текста в усеченном пузырьке чата, который при необходимости можно развернуть.</p>
+		]]>
+    </string>
     <string name="tooltip_identity_popup">Коснитесь здесь, чтобы быстро показать свой Threema ID или сканировать ID других пользователей</string>
     <string name="tap_to_start">Коснитесь здесь, чтобы запустить %s.</string>
     <string name="two_years">2 года</string>
@@ -1119,7 +1133,7 @@
     <string name="an_error_occurred_during_send">При отправке одного или нескольких сообщений произошла ошибка.</string>
     <string name="state_processing">идет обработка</string>
     <string name="passphrase_locked">Кодовая фраза заблокирована</string>
-    <string name="image_labeling_new">Новинка: распознавание изображений</string>
+    <string name="image_labeling_new">Новинка: поиск изображений</string>
     <string name="tooltip_image_labeling">Чтобы найти в галерее нужное визуальное мультимедиа, введите слово, описывающее его содержимое, и выберите его из списка идентифицированных меток.</string>
     <string name="selected_media">Ваш выбор</string>
     <string name="attach_gif">GIF</string>
@@ -1138,4 +1152,23 @@
     <string name="max_selectable_media_exceeded">Одновременно можно отправить не более %d объектов.</string>
     <string name="error_unable_loading_media_thumb">Произошла ошибка. Не удалось загрузить эскиз мультимедиа</string>
     <string name="select">Выбрать</string>
+    <string name="filter_list">Список фильтров</string>
+    <string name="hint_filter_list">Введите текст фильтра</string>
+    <string name="add">Добавить</string>
+    <string name="threema_message_from">Сообщение от %s</string>
+    <string name="show_text">Показать текст</string>
+    <string name="only_images_or_videos">Можно выбирать только изображения или видео</string>
+    <string name="media_gallery_gifs">GIF-файлы</string>
+    <string name="notification_channel_image_labeling">Ход индексирования изображений</string>
+    <string name="notification_channel_image_labeling_desc">В фоновом режиме выполняется индексирование изображений в общедоступной галерее</string>
+    <string name="notification_image_labeling_desc">Индексируем галерею мультимедиа</string>
+    <string name="no_media_found_global">На устройстве не найдены файлы мультимедиа</string>
+    <string name="prefs_sum_image_labeling">Включите поиск общедоступных изображений в вашей галерее по ключевым словам</string>
+    <string name="prefs_image_labeling">Поиск изображений</string>
+    <string name="enable_formatting">Включить форматирование</string>
+    <string name="original_file_no_longer_avilable">Исходный файл больше не доступен. Отправьте сообщение повторно.</string>
+    <string name="state_transcoding">перекодируем</string>
+    <string name="importing_files">импортируем файлы</string>
+    <string name="tooltip_image_resolution_hint">Коррекция разрешения изображений.</string>
+    <string name="image_labeling_stuck_error">Процесс индексирования для поиска по изображениям завис и был отменен. Повторите попытку позже.</string>
 </resources>

+ 28 - 12
app/src/main/res/values-tr/strings.xml

@@ -11,7 +11,7 @@
     <string name="title_enter_id">ID Yaz</string>
     <string name="title_invite_friend">Arkadaşını Davet Et</string>
     <string name="invite_via">Arkadaşınızı … ile davet et</string>
-    <string name="invite_email_body">Merhaba,\n\nKullanıcıların gizliliğini koruyan güvenli anlık mesajlaşma uygulaması%1$s kullanıyorum.\n\nThreema Kimliğim: https://threema.id/%2$s\n\n%1$ üzerinden iletişim kuralım s!\n\nHoşçakal,\n</string>
+    <string name="invite_email_body">Merhaba,\n\nKullanıcıların gizliliğini koruyan güvenli anlık mesajlaşma uygulaması %1$s kullanıyorum.\n\nThreema Kimliğim: https://threema.id/%2$s\n\n%1$s üzerinden iletişim kuralım s!\n\nHoşçakal,\n</string>
     <string name="invite_sms_body">Selam! Güvenli ve gizlilikle uyumlu bir şekilde iletişim kurmak için % 1 $ s kullanalım! Threema kimliğim: https://threema.id/% 2 $ s</string>
     <string name="invite_email_subject">Threema. Özelinizi koruyan güvenli mesajlaşma programı.</string>
     <string name="enter_id_hint">"Threema ID yaz. "</string>
@@ -104,11 +104,7 @@
     <string name="wizard2_email_linking">"E-postayı kimliğinize Bağlayın "</string>
     <string name="wizard2_phone_hint">Cep telefonu numaranızı yazınız.</string>
     <string name="wizard2_phone_number_confirm_title">Numarayı Onay</string>
-    <string name="wizard2_phone_number_confirm">Şu Adrese bir SMS göndermek üzereyiz:
-
- {%1$s}
-
- Bu numara doğru mu?</string>
+    <string name="wizard2_phone_number_confirm">Şu Adrese bir SMS göndermek üzereyiz: {%1$s} Bu numara doğru mu?</string>
     <string name="wizard2_phone_linking">Telefon numarasını kimliğinize bağlayın</string>
     <string name="wizard3_nickname_hint">Takma adınızı yazınız</string>
     <string name="set_nickname_title">Adını ayarla</string>
@@ -1016,8 +1012,8 @@ yöneticisiz kalır . Diğer üyeler yine de sohbet edebilecek, ancak artık de
     <string name="voice_action_title">Sesli İşlemler</string>
     <string name="voice_action_body">Sesli işlem işleniyor</string>
     <string name="permission_camera_photo_required">Fotoğraf çekmek için lütfen kameraya erişime izin verin</string>
-    <string name="global_search">Global arama</string>
-    <string name="global_search_empty_view_text">Tüm mesajlarda ve kişilerde aramak için en az iki karakter girin</string>
+    <string name="global_search">Sohbetlerde Ara</string>
+    <string name="global_search_empty_view_text">en az iki karakter girin en az iki karakter girin</string>
     <string name="my_id">Benim kimliğim</string>
     <string name="profile_picture_and_nickname">Profil resmi ve takma ad</string>
     <string name="lp_select_this_place">Bu yeri seçin</string>
@@ -1039,14 +1035,15 @@ yöneticisiz kalır . Diğer üyeler yine de sohbet edebilecek, ancak artık de
     <string name="add_contact_enter_id_hint">Lütfen eklemek istediğiniz kişinin Threema ID\'sini girin</string>
     <string name="notification_channel_new_contact">Yeni kişiler</string>
     <string name="notification_channel_new_contact_desc">Yeni kişiler hakkında bildirim</string>
-    <string name="notification_contact_has_joined">%1$s katıldı %2$s . Mesaj göndermek için buraya dokunun.</string>
-    <string name="notification_contact_has_joined_multiple">%1$d kişi %2$s : %3$s grubuna katıldı. Onlara mesaj göndermek için buraya hafifçe vurun.</string>
+    <string name="notification_contact_has_joined">%1$s,%2$s \'ye katıldı. Mesaj göndermek için buraya dokunun.</string>
+    <string name="notification_contact_has_joined_multiple">%1$d kişi %2$s:%3$s \'e katıldı. Onlara bir mesaj göndermek için buraya dokunun.</string>
     <string name="system_default">Sistem varsayılanı</string>
     <string name="open_in_maps_app">Haritalar uygulamasında aç</string>
     <string name="delete">Sil</string>
     <string name="num_archived_chats">%d arşivlenmiş sohbet</string>
     <string name="continue_recording">Kayda devam et</string>
     <string name="whatsnew_title">%s 4.5 sürümüne hoş geldiniz</string>
+    <string name="whatsnew_headline">%1$s 4.5 yepyeni bir medya deneyimi getiriyor.\n\nMedya çekmecesi arayüzü büyük bir revizyondan geçti ve şimdi isteğe bağlı bir makine öğrenimi tabanlı görsel arama özelliği sunuyor.\n\nAyrıca, tüm sohbetlere genel metin arama, iyileştirilmiş mesaj alıntıları ve daha fazlasını ekledik.</string>
     <string name="whatsnew2_title">Ne var ne yok?</string>
     <string name="tooltip_identity_popup">Threema ID\'nizi hızlı bir şekilde görüntülemek veya diğer kişilerin kimliklerini taramak için buraya dokunun</string>
     <string name="tap_to_start">%s\'i şimdi başlatmak için buraya hafifçe vurun.</string>
@@ -1065,7 +1062,7 @@ yöneticisiz kalır . Diğer üyeler yine de sohbet edebilecek, ancak artık de
     <string name="seconds">Saniye</string>
     <string name="minutes">Dakika</string>
     <string name="and">ve</string>
-    <string name="edit_type_content_description">%1$s%2$s öğesini görüntüleme veya düzenleme</string>
+    <string name="edit_type_content_description">görüntüleyin veya düzenleyin %1$s %2$s öğesini görüntüleyin veya düzenleyin</string>
     <string name="group">Grup</string>
     <string name="send_location_privacy_policy_v4_0"><![CDATA[<p> Gizlilik Politikamız aşağıdaki değişikliği yansıtacak şekilde güncellendi: </p> <p> %1$s artık harita ve İÇN verileri sağlamak için Google Play ve Google Haritalar\'a bağlı değil. </p> Lütfen gizlilik politikasının tamamını <a href="%2$s"> burada </a> inceleyin .]]></string>
     <string name="play_services_not_installed_unable_to_use_push">Play Hizmetleri yüklü değil. «Push» konumuna geçilemiyor.</string>
@@ -1152,7 +1149,7 @@ yöneticisiz kalır . Diğer üyeler yine de sohbet edebilecek, ancak artık de
     <string name="an_error_occurred_during_send">Bir veya daha fazla mesaj gönderilirken hata oluştu.</string>
     <string name="state_processing">İşleme</string>
     <string name="passphrase_locked">Parola Kilitlendi</string>
-    <string name="image_labeling_new">Yeni: Görüntü Tanıma</string>
+    <string name="image_labeling_new">Yeni: Görsel Arama</string>
     <string name="tooltip_image_labeling">Galerinizde belirli bir görsel medyayı bulmak için, içeriği tanımlayan bir terim girin ve tanımlanmış etiketler listesinden seçim yapınız.</string>
     <string name="selected_media">Seçiminiz</string>
     <string name="attach_gif">GIF</string>
@@ -1171,4 +1168,23 @@ yöneticisiz kalır . Diğer üyeler yine de sohbet edebilecek, ancak artık de
     <string name="max_selectable_media_exceeded">Bir seferde %d nesneden fazlası gönderilemez.</string>
     <string name="error_unable_loading_media_thumb">Hata. Medya Küçük Resmi yüklenemedi</string>
     <string name="select">Seçiniz</string>
+    <string name="filter_list">Listeyi filtrele</string>
+    <string name="hint_filter_list">Filtre metni girin</string>
+    <string name="add">Ekle</string>
+    <string name="threema_message_from">%s tarafından gönderilen mesaj</string>
+    <string name="show_text">Metni göster</string>
+    <string name="only_images_or_videos">Yalnızca resimler veya videolar seçilebilir</string>
+    <string name="media_gallery_gifs">GIF\'ler</string>
+    <string name="notification_channel_image_labeling">Görüntü indeksleme ilerlemesi</string>
+    <string name="notification_channel_image_labeling_desc">Arka planda genel galeri resimlerinin indekslenmesi</string>
+    <string name="notification_image_labeling_desc">Medya galerisi indeksleme</string>
+    <string name="no_media_found_global">Bu cihazda hiçbir medya bulunamadı</string>
+    <string name="prefs_sum_image_labeling">Galerinizdeki genel resimleri anahtar kelimelere göre aramayı etkinleştirin</string>
+    <string name="prefs_image_labeling">Görsel arama</string>
+    <string name="enable_formatting">Biçimlendirmeyi etkinleştir</string>
+    <string name="original_file_no_longer_avilable">Orijinal dosyaya artık erişilemez. Lütfen mesajı tekrar gönderin.</string>
+    <string name="state_transcoding">Kod dönüştürme</string>
+    <string name="importing_files">Dosyaları içe aktarma</string>
+    <string name="tooltip_image_resolution_hint">Görüntü çözünürlüklerini ayarlayın.</string>
+    <string name="image_labeling_stuck_error">Görsel arama için indeksleme işlemi takıldı ve iptal edildi. Lütfen daha sonra tekrar deneyin.</string>
 </resources>

+ 116 - 99
app/src/main/res/values-zh-rCN/strings.xml

@@ -49,7 +49,7 @@
     <string name="prefs_image_size">图片尺寸</string>
     <string name="prefs_notification_sound">通知声音</string>
     <string name="prefs_sum_notification_sound">系统默认</string>
-    <string name="prefs_vibrate">动</string>
+    <string name="prefs_vibrate">动</string>
     <string name="prefs_sum_vibrate">振动收到的消息</string>
     <string name="prefs_light">通知灯</string>
     <string name="prefs_sum_light">白色</string>
@@ -70,7 +70,7 @@
     <string name="prefs_troubleshooting">故障排除</string>
     <string name="prefs_sum_troubleshooting">分析并修复问题</string>
     <string name="prefs_workarounds">解决方法</string>
-    <string name="prefs_title_polling_switch">轮询</string>
+    <string name="prefs_title_polling_switch">投票中</string>
     <string name="prefs_sum_polling_on">定期检查是否有新消息(使用更多电量!)</string>
     <string name="prefs_sum_polling_off">仅在推送时检查新消息</string>
     <string name="prefs_logging">记录中</string>
@@ -96,9 +96,9 @@
     <string name="color_magenta">品红色</string>
     <string name="color_yellow">黄色</string>
     <string name="color_white">白色</string>
-    <string name="next">下一</string>
-    <string name="finish">完</string>
-    <string name="please_wait">请耐心等待…</string>
+    <string name="next">下一</string>
+    <string name="finish">完</string>
+    <string name="please_wait">请等待...</string>
     <string name="wizard_first_create_id">创建您的Threema ID…</string>
     <string name="wizard1_sync_contacts">正在同步通讯录…</string>
     <string name="wizard2_email_hint">输入你的电子邮箱地址</string>
@@ -114,21 +114,20 @@
     <string name="copy_message_action">复制</string>
     <string name="delete_contact_action">删除联络人</string>
     <string name="scan_id">扫描ID</string>
-    <string name="id_mismatch">您扫描的公钥与服务器
-为此ID存储的密钥不匹配。这意味着有人操纵了扫描的代码,并且密钥不应该被信任。</string>
+    <string name="id_mismatch">您扫描的公钥与服务器为该ID存储的密钥不匹配。这意味着有人操纵了扫描后的代码,并且该密钥不应受到信任。</string>
     <string name="scan_successful">该ID已成功扫描,现在已验证联系人。</string>
     <string name="scan_duplicate">此ID已被扫描和验证。</string>
     <string name="linked_email">电子邮件</string>
     <string name="linked_mobile">手机号码</string>
     <string name="public_nickname">昵称</string>
-    <string name="share_via">通过…分享</string>
+    <string name="share_via">分享到...</string>
     <string name="share_subject">Threema对话</string>
     <string name="message_delete_undo">撤消</string>
     <string name="message_deleted">讯息已删除</string>
     <string name="mobile_already_linked">您的Threema ID已链接到该手机号码</string>
     <string name="email_already_linked">您的Threema ID已链接到该电子邮件地址</string>
     <string name="whoaaa">Threema 公告</string>
-    <string name="really_delete_message_title">删除留言</string>
+    <string name="really_delete_message_title">删除信息</string>
     <string name="really_delete_thread">删除聊天</string>
     <string name="really_delete_thread_message" tools:ignore="PluralsCandidate">您是否确实要删除%d聊天?您将无法恢复
 消息。</string>
@@ -151,7 +150,7 @@
 正确无误,并且已连接到移动网络,然后重试。</string>
     <string name="verify_failed_not_linked">验证您的手机号码失败。验证过程已中止。</string>
     <string name="check_incoming_sms">检查传入的短信</string>
-    <string name="backup_title">出口编号</string>
+    <string name="backup_title">导出ID</string>
     <string name="backup_sum">导出您的Threema ID</string>
     <string name="backup_and_delete">备份和删除</string>
     <string name="delete_id_title">删除ID</string>
@@ -164,23 +163,23 @@
     <string name="backup_password_again_summary">再次输入密码</string>
     <string name="password_hint">密码</string>
     <string name="generating_backup_data">生成备份数据</string>
-    <string name="backup_id_title">您的身份证出口</string>
+    <string name="backup_id_title">您的ID导出</string>
     <string name="backup_id_summary">上面显示的文本字符串或QR码以及您
 选择的密码可用于在另一台设备上还原您的ID。您应将其复制到合适的位置,通过
 电子邮件共享或使用其他设备扫描QR码。</string>
-    <string name="support">救援</string>
+    <string name="support">帮助</string>
     <string name="support_url">https://threema.ch/android/support/</string>
     <string name="backup_share_content">以下文本字符串可以与所选密码一起使用,以
 还原Threema ID。</string>
     <string name="backup_share_subject">Threema ID导出为</string>
     <string name="add_attachment">新附件</string>
     <string name="invalid_passphrase">密码无效</string>
-    <string name="master_key_locked">主钥被锁定</string>
+    <string name="master_key_locked">主钥被锁定</string>
     <string name="master_key_locked_notify_description">点按此处输入密码</string>
     <string name="prefs_masterkey_passphrase">未设置密码</string>
-    <string name="prefs_title_masterkey_passphrase">密码短语</string>
+    <string name="prefs_title_masterkey_passphrase">密码口令</string>
     <string name="setting_masterkey_passphrase">设置主密钥密码</string>
-    <string name="masterkey_passphrase_title">万能钥匙密码</string>
+    <string name="masterkey_passphrase_title">主密钥密码口令</string>
     <string name="masterkey_passphrase_summary">设置密码来保护您的主密钥。请注意,
 每次Threema重新启动时,您都必须输入。</string>
     <string name="masterkey_passphrase_again_summary">再次输入密码</string>
@@ -200,11 +199,11 @@
     <string name="masterkey_title">输入密码</string>
     <string name="masterkey_body">您的Threema主密钥受密码保护。输入密码以解锁
 密钥。</string>
-    <string name="masterkey_unlocking">解锁主钥</string>
+    <string name="masterkey_unlocking">解锁主钥</string>
     <string name="verify_phonecall_text">请求通话</string>
-    <string name="prepare_call_message">如果您继续进行,我们将尝试立即致电给您。您的验证码
-将被读取两次。请注意,我们只会进行一次尝试,因此请确保您已准备就绪,然后
-再继续。</string>
+    <string name="prepare_call_message">如果您选择继续,我们将尝试立即致电给您。您的验证码
+被朗读两次。请注意,您只有一次致电的机会,因此在继续之前,
+请确定您已准备就绪。</string>
     <string name="enter_code_hint">输入验证码</string>
     <string name="enter_code_sum">请输入验证短信中的验证码或电话中提及的验证码。</string>
     <string name="no_matching_contacts">找不到联系人。</string>
@@ -262,10 +261,10 @@ https://shop.threema.ch/retrieve_keys</string>
     <string name="emoji_flags">标志</string>
     <string name="title_lock">锁</string>
     <string name="new_messages_locked">有新讯息</string>
-    <string name="new_messages_locked_description">触摸以查看新息。</string>
+    <string name="new_messages_locked_description">触摸以查看新息。</string>
     <string name="new_unprocessed_messages">有新讯息</string>
-    <string name="new_unprocessed_messages_description">触摸以获取并查看新的传入息。</string>
-    <string name="prefs_title_masterkey_notification_newmsg">新息通知</string>
+    <string name="new_unprocessed_messages_description">触摸以获取并查看新的传入息。</string>
+    <string name="prefs_title_masterkey_notification_newmsg">新息通知</string>
     <string name="prefs_masterkey_notification_newmsg_off">主
 密钥锁定时,新消息不会触发通知</string>
     <string name="prefs_masterkey_notification_newmsg_on">当主
@@ -292,53 +291,53 @@ https://shop.threema.ch/retrieve_keys</string>
     <string name="set_pin_again_summary">再次输入PIN码</string>
     <string name="set_pin_hint">销</string>
     <string name="title_addgroup">新组</string>
-    <string name="search_group">搜索组</string>
+    <string name="search_group">搜索组</string>
     <string name="updating_system">更新系统</string>
-    <string name="title_select_contacts">选择员</string>
+    <string name="title_select_contacts">选择员</string>
     <string name="pin_invalid_not_set">PIN码无效。没有设置。</string>
     <string name="prefs_group_notifications">群聊</string>
-    <string name="add_group_members_list">组成员</string>
+    <string name="add_group_members_list">组成员</string>
     <string name="group_select_at_least_two">需要选择至少一名成员才能继续</string>
-    <string name="group_select_max" tools:ignore="PluralsCandidate">您选择的成员不得超过 %1$d 个组成员</string>
+    <string name="group_select_max" tools:ignore="PluralsCandidate">您选择的成员不得超过 %1$d 个组成员</string>
     <string name="search">搜索</string>
     <string name="hint_search_keyword">搜索关键词</string>
-    <string name="add_group_owner">组创作者</string>
+    <string name="add_group_owner">组创作者</string>
     <string name="title_tab_users">联络人</string>
-    <string name="title_tab_groups">团体</string>
+    <string name="title_tab_groups">群组</string>
     <string name="no_matching_groups">找不到群组</string>
-    <string name="action_leave_group">离开组</string>
-    <string name="group_edit_title">修改组</string>
-    <string name="really_leave_group_message">您真的要离开这个组吗?</string>
+    <string name="action_leave_group">离开组</string>
+    <string name="group_edit_title">修改组</string>
+    <string name="really_leave_group_message">您真的要离开这个组吗?</string>
     <string name="search_no_matches">找不到匹配项</string>
-    <string name="search_no_more_matches">没有更多的比赛</string>
+    <string name="search_no_more_matches">没有更多的匹配项</string>
     <string name="backup_share">共享备份</string>
-    <string name="my_backups_title">备</string>
+    <string name="my_backups_title">备</string>
     <string name="backup_delete_confirm">备份文件已删除</string>
     <string name="message_log_title">讯息详情</string>
-    <string name="state_read">读</string>
+    <string name="state_read">读</string>
     <string name="state_ack">同意</string>
     <string name="state_dec">不同意</string>
-    <string name="state_delivered">已交付</string>
-    <string name="state_sending">发送</string>
+    <string name="state_delivered">已传送</string>
+    <string name="state_sending">发送</string>
     <string name="state_pending">待定</string>
     <string name="state_failed">失败了</string>
     <string name="state_sent">已发送</string>
     <string name="state_dialog_created">已建立</string>
     <string name="state_dialog_posted">已发送</string>
-    <string name="state_dialog_modified">更新</string>
+    <string name="state_dialog_modified">更新</string>
     <string name="state_dialog_status">状态</string>
     <string name="title_tab_recent">最近</string>
     <string name="no_recent_conversations">找不到聊天记录</string>
     <string name="save_changes">保存</string>
-    <string name="group_created_confirm">组创建成功</string>
+    <string name="group_created_confirm">组创建成功</string>
     <string name="creating_group">建立群组</string>
-    <string name="updating_group">更新组</string>
-    <string name="status_create_group">组已创建。</string>
+    <string name="updating_group">更新组</string>
+    <string name="status_create_group">组已创建。</string>
     <string name="status_rename_group">群组已重命名为«%1$s»</string>
-    <string name="status_group_new_photo">组已更新。</string>
-    <string name="status_group_new_member">«%1$s»已添加到组中。</string>
-    <string name="status_group_member_left">«%1$s»离开组。</string>
-    <string name="status_group_member_kicked">«%1$s» 已从组中删除。</string>
+    <string name="status_group_new_photo">组已更新。</string>
+    <string name="status_group_new_member">«%1$s»已添加到组中。</string>
+    <string name="status_group_member_left">«%1$s»离开组。</string>
+    <string name="status_group_member_kicked">«%1$s» 已从组中删除。</string>
     <string name="can_not_send_no_group_members">您无法将消息发送到空群组</string>
     <string name="you_are_not_a_member_of_this_group">您不是该群组的成员</string>
     <string name="can_not_delete_not_valid">无法删除无效的对象</string>
@@ -356,8 +355,8 @@ https://shop.threema.ch/retrieve_keys</string>
     <string name="prefs_sum_black_list">来自此处列出的ID的消息将被忽略。</string>
     <string name="verified">已验证</string>
     <string name="want_to_add_to_exclude_list">此联系人已链接到手机通讯录中的记录。如果您
-在Threema中将其删除,则联系人同步后它会重新出现。\ n是否要从同步中排除它?</string>
-    <string name="no">没有</string>
+在Threema中将其删除,则联系人同步后它会重新出现。 n是否要从同步中排除它?</string>
+    <string name="no"></string>
     <string name="yes">是</string>
     <string name="deleting_contact">删除联络人</string>
     <string name="prefs_contact_soring">排序</string>
@@ -378,7 +377,7 @@ https://shop.threema.ch/retrieve_keys</string>
     <string name="contact_format_last_name_first_name">姓氏名字</string>
     <string name="block_contact">屏蔽联系人</string>
     <string name="unblock_contact">取消阻止联系人</string>
-    <string name="unread_messages">未读邮件</string>
+    <string name="unread_messages">未读讯息</string>
     <string name="really_unlink_contact_title">取消联系</string>
     <string name="really_unlink_contact">您是否真的要取消此联系人的链接?</string>
     <string name="do_unlink_contact">取消联系</string>
@@ -406,10 +405,10 @@ https://shop.threema.ch/retrieve_keys</string>
     <string name="really_delete_distribution_list_message">您是否真的要删除此列表并删除其
 消息?</string>
     <string name="enter_distribution_list_name">为该列表选择一个名称</string>
-    <string name="distribution_list">发行清单</string>
+    <string name="distribution_list">通讯组</string>
     <string name="title_tab_distribution_list">通讯组列表</string>
     <string name="no_matching_distribution_lists">没有匹配的通讯组列表</string>
-    <string name="is_typing">打字…</string>
+    <string name="is_typing">打字…</string>
     <string name="push_not_available_title">找不到推送服务</string>
     <string name="push_not_available_text">“我们未在您的设备上找到推送服务,因为Google Play服务尚未安装或过时。Threema将每15分钟检查一次新邮件。</string>
     <string name="backup_in_progress">正在进行备份</string>
@@ -417,14 +416,14 @@ https://shop.threema.ch/retrieve_keys</string>
     <string name="backup_or_restore_error">Threema备份</string>
     <string name="backup_or_restore_error_body">备份未成功完成</string>
     <string name="could_not_download_message">无法下载消息</string>
-    <string name="info">信息</string>
+    <string name="info">资讯</string>
     <string name="resync_group">重新同步群组</string>
     <string name="edit_name">编辑名称和图片</string>
     <string name="edit_name_only">编辑名称</string>
     <string name="group_was_synchronized">群组已同步。</string>
     <string name="verification_level2_work_explain">内部联系人,由您的组织预先填充。</string>
-    <string name="verification_level3_work_explain">内部联系人</string>
-    <string name="verification_level3_explain">通过扫描他们的QR码已亲自验证其身份和公钥的联系人。</string>
+    <string name="verification_level3_work_explain">通过扫描二维码,您能亲自验证内部联系人的身份和公钥。</string>
+    <string name="verification_level3_explain">通过扫描二维码,您能亲自验证联系人的身份和公钥。</string>
     <string name="verification_level2_explain">联系人的电话号码和/或电子邮件地址包含在您的通讯录中。</string>
     <string name="verification_level1_explain">未知联系人;该联系人没有将电话号码或电子邮件地址链接到其ID,或者您的通讯录中没有这些联系人详细信息。</string>
     <string name="state_dialog_received">已收到</string>
@@ -437,7 +436,7 @@ https://shop.threema.ch/retrieve_keys</string>
     <string name="media_gallery_all">一切</string>
     <string name="media_gallery_pictures">图片</string>
     <string name="media_gallery_videos">影片</string>
-    <string name="group_membership_title">这些组的成员</string>
+    <string name="group_membership_title">这些组的成员</string>
     <string name="num_items_sected">已选择 %s</string>
     <string name="really_delete_media" tools:ignore="PluralsCandidate">您真的要删除%d条媒体消息吗?</string>
     <string name="check_updates">检查更新</string>
@@ -446,9 +445,9 @@ https://shop.threema.ch/retrieve_keys</string>
     <string name="identity_already_exists">此ID已在您的联系人列表中</string>
     <string name="share_contact">分享联络人</string>
     <string name="add_shortcut">添加快捷方式</string>
-    <string name="group_not_found">找不到这样的组。</string>
-    <string name="contact_not_found">没有这样的联系</string>
-    <string name="contact_now_blocked">联人已封锁</string>
+    <string name="group_not_found">找不到这样的组。</string>
+    <string name="contact_not_found">没有这样的联系</string>
+    <string name="contact_now_blocked">联人已封锁</string>
     <string name="contact_now_unblocked">联络畅通</string>
     <string name="not_enough_disk_space_title">存储空间不足</string>
     <string name="not_enough_disk_space_text">至少释放%1$s来接收消息</string>
@@ -457,31 +456,31 @@ https://shop.threema.ch/retrieve_keys</string>
 http://www.7-zip.org或https://itunes.apple.com/us/app/the-unarchiver/id425424353</string>
     <string name="enter_zip_password_body">聊天将作为加密的zip文件发送。请选择一个密码:</string>
     <string name="new_messages_in_chats" tools:ignore="PluralsCandidate">%2$d 个聊天中有%1$d条新消息</string>
-    <string name="ballot_create">建立意见调查</string>
-    <string name="ballot_choice_add">添加选</string>
-    <string name="ballot_state_closed">关闭</string>
+    <string name="ballot_create">建立投票</string>
+    <string name="ballot_choice_add">添加选</string>
+    <string name="ballot_state_closed">关闭</string>
     <string name="ballot_vote">投票</string>
-    <string name="ballot_placeholder">轮询</string>
-    <string name="ballot_close">关闭此民意调查</string>
-    <string name="ballot_really_close">真的要关闭这项意见调查吗?</string>
+    <string name="ballot_placeholder">投票</string>
+    <string name="ballot_close">关闭此投票</string>
+    <string name="ballot_really_close">真的要关闭这此投票吗?</string>
     <string name="ballot_multiple_choice">多项选择</string>
     <string name="ballot_result_intermediate">中间结果</string>
-    <string name="ballot_error_more_than_x_choices">需要定义至少两个选择</string>
+    <string name="ballot_error_more_than_x_choices">需要设置至少两个选择</string>
     <string name="ballot_message_closed">可用结果</string>
     <string name="ballot_vote_posted_successfully">投票成功发布</string>
     <string name="ballot_vote_posted_failed">投票发布失败</string>
-    <string name="attach_ballot">轮询</string>
-    <string name="ballot_overview">所有民意调查</string>
-    <string name="ballot_copy">复制民意调查</string>
-    <string name="ballot_remove">删除民意调查</string>
-    <string name="ballot_really_delete">删除民意调查</string>
+    <string name="attach_ballot">投票</string>
+    <string name="ballot_overview">所有投票</string>
+    <string name="ballot_copy">复制投票</string>
+    <string name="ballot_remove">删除投票</string>
+    <string name="ballot_really_delete">删除投票</string>
     <string name="ballot_really_delete_text" tools:ignore="PluralsCandidate">您确实要删除%1$d个投票吗?您将无法收回
 投票。</string>
     <string name="ballot_not_exist">投票不存在</string>
     <string name="ballot_tap_to_vote">点按即可投票</string>
     <string name="ballot_tap_to_view_results">投票关闭。点按即可查看结果</string>
-    <string name="ballot_subject_hint">输入此民意调查的标题</string>
-    <string name="ballot_no_ballots_yet">尚无民意调查。</string>
+    <string name="ballot_subject_hint">输入此投票的标题</string>
+    <string name="ballot_no_ballots_yet">尚无投票。</string>
     <string name="print">打印</string>
     <string name="ballot_wizard0_explain">直接与Threema快速创建在线民意调查。安排活动,
 进行调查或向朋友询问任何您想要的东西。</string>
@@ -490,11 +489,11 @@ http://www.7-zip.org或https://itunes.apple.com/us/app/the-unarchiver/id42542435
     <string name="really_block_contact">来自此联系人的未来消息将被丢弃。继续?</string>
     <string name="ballot_result_final">最后结果</string>
     <string name="invalid_cannot_send">您无法向无效的联系人发送消息</string>
-    <string name="title_activity_new_ballot_wizard">NewBallotWizard活动</string>
+    <string name="title_activity_new_ballot_wizard">NewBallotWizardActivity</string>
     <string name="ballot_answer_count_error">请输入至少两个答案进行投票。</string>
     <string name="ballot_one_contact_not_supported">警告:%1$s将无法参与您的投票。</string>
     <string name="ballot_x_contact_not_supported" tools:ignore="PluralsCandidate">警告:%1$d个联系人将无法参与您的投票。</string>
-    <string name="enable_polling">使用轮询</string>
+    <string name="enable_polling">使用投票</string>
     <string name="contact_state_inactive">不活跃</string>
     <string name="contact_state_invalid">无效</string>
     <string name="back">背部</string>
@@ -505,17 +504,17 @@ http://www.7-zip.org或https://itunes.apple.com/us/app/the-unarchiver/id42542435
 每15分钟检查一次新消息。</string>
     <string name="ballot_intermediate_results_show">显示中间结果</string>
     <string name="converting_video">处理影片</string>
-    <string name="video_size_small">高(数据少)</string>
-    <string name="video_size_large">低(更多数据)</string>
-    <string name="video_size_original">无(大量数据)</string>
+    <string name="video_size_small">高(使用数据少)</string>
+    <string name="video_size_large">低(使用更多数据)</string>
+    <string name="video_size_original">无(使用大量数据)</string>
     <string name="prefs_video_size">视频压缩</string>
     <string name="show_contact">显示联络人</string>
     <string name="chat_with">与 %1$s 聊天</string>
     <string name="kick_user_from_group">从组中踢出%1$ s</string>
     <string name="show_as_qrcode">显示为QR码</string>
-    <string name="qr_code">二维码</string>
+    <string name="qr_code">QR码</string>
     <string name="really_leave_id_export">如果尚未这样做,请将ID导出文本字符串或相应的QR码保存到安全的地方或打印出来。没有备份,无法还原Threema ID。</string>
-    <string name="revocation_key_title">身份吊销</string>
+    <string name="revocation_key_title">ID吊销</string>
     <string name="revocation_key_not_set">未设置撤销密码</string>
     <string name="revocation_key_set_at">密码设置为%1$s</string>
     <string name="prefs_sum_remove_wallpapers">删除所有个人壁纸</string>
@@ -541,14 +540,14 @@ https://myid.threema.ch/revoke撤消您的ID,以防丢失或被盗</string>
     <string name="open_from">从打开</string>
     <string name="file_one_contact_not_supported">%1$s无法接收文件。</string>
     <string name="file_x_contact_not_supported" tools:ignore="PluralsCandidate">警告:%1$d个联系人无法收到您的文件。</string>
-    <string name="mime_android_apk">Android套</string>
+    <string name="mime_android_apk">Android套</string>
     <string name="mime_audio">音频文件</string>
     <string name="mime_certificate">数字证书</string>
     <string name="mime_codes">源代码</string>
     <string name="mime_compressed">封存</string>
     <string name="mime_contact">联系</string>
     <string name="mime_event">日历活动</string>
-    <string name="mime_font">字</string>
+    <string name="mime_font">字</string>
     <string name="mime_image">图像文件</string>
     <string name="mime_pdf">PDF文件</string>
     <string name="mime_presentation">介绍</string>
@@ -564,11 +563,11 @@ https://myid.threema.ch/revoke撤消您的ID,以防丢失或被盗</string>
     <string name="prefs_theme">设计主题</string>
     <string name="list_theme_light">浅色(默认)</string>
     <string name="list_theme_dark">黑暗</string>
-    <string name="prefs_header_appearance">出现</string>
+    <string name="prefs_header_appearance">外观</string>
     <string name="prefs_sum_passphrase">需要密码才能解锁本地加密</string>
     <string name="prefs_title_masterkey_change_passphrase">更改密码</string>
     <string name="storage_total">内部存储空间</string>
-    <string name="storage_threema">内部存储空间</string>
+    <string name="storage_threema">Threema佔用的空间</string>
     <string name="storage_total_free">总可用空间</string>
     <string name="storage_total_in_use">正在使用</string>
     <string name="one_year">1年</string>
@@ -588,27 +587,27 @@ https://myid.threema.ch/revoke撤消您的ID,以防丢失或被盗</string>
     <string name="media">媒体</string>
     <string name="prefs_storage_mgmt_title">清理媒体,文件和消息</string>
     <string name="num_messages">讯息数</string>
-    <string name="delete_messages_explain">删除以下息:</string>
-    <string name="delete_message">立即删除邮件</string>
-    <string name="really_delete_messages">如果继续,这些息将被永久删除。您将无法
+    <string name="delete_messages_explain">删除以下息:</string>
+    <string name="delete_message">立即删除讯息</string>
+    <string name="really_delete_messages">如果继续,这些息将被永久删除。您将无法
 恢复它们。</string>
     <string name="messages_delete_explain">您也可以完全删除较旧的消息。</string>
     <string name="backup_started">数据备份开始</string>
     <string name="invalid_data">无效数据。无法发送。</string>
     <string name="prefs_emoji_style">表情符号样式</string>
     <string name="prefs_android_emojis">系统表情符号</string>
-    <string name="prefs_default_emojis">Threema Emojis(默认)</string>
+    <string name="prefs_default_emojis">Threema 表情(默认)</string>
     <string name="android_emojis_warning">注意:系统的字符集可能无法显示
 Threema支持的所有表情符号。</string>
-    <string name="crop">作物</string>
+    <string name="crop">裁剪</string>
     <string name="scan_id_mismatch_title">ID不匹配</string>
     <string name="scan_id_mismatch_message">您扫描的ID与您
 存储的联系人不匹配。</string>
     <string name="title_remove_picture">删除图片</string>
-    <string name="blocked">受阻</string>
+    <string name="blocked">被封锁</string>
     <string name="name">名称</string>
     <string name="privacy_policy">隐私政策</string>
-    <string name="save_group_changes">您想将更改保存到组吗?</string>
+    <string name="save_group_changes">您想将更改保存到组吗?</string>
     <string name="prefs_title_fontsize">字体大小</string>
     <string name="fontsize_normal">定期</string>
     <string name="fontsize_large">大</string>
@@ -617,7 +616,7 @@ Threema支持的所有表情符号。</string>
     <string name="polling_interval_5">5分钟</string>
     <string name="polling_interval_15">15分钟(默认)</string>
     <string name="polling_interval_30">30分钟</string>
-    <string name="prefs_polling_interval">轮询间隔</string>
+    <string name="prefs_polling_interval">投票间隔</string>
     <string name="chat_deleted" tools:ignore="PluralsCandidate">已删除 %d 个聊天</string>
     <string name="prefs_sendlog">发送日志</string>
     <string name="prefs_sendlog_summary">将日志文件发送给Threema,以便在出现问题时进行进一步分析</string>
@@ -635,7 +634,7 @@ Threema支持的所有表情符号。</string>
     <string name="confirm_your_pin">确认密码</string>
     <string name="too_many_incorrect_attempts">过多的错误尝试。请在%s秒后重试。</string>
     <string name="no_lockscreen_set">未设置系统屏幕锁定。</string>
-    <string name="on"></string>
+    <string name="on"></string>
     <string name="off">关</string>
     <string name="new_wizard_select_country">选择你的国家</string>
     <string name="new_wizard_lets_get_started">让我们开始吧!</string>
@@ -644,7 +643,7 @@ Threema支持的所有表情符号。</string>
     <string name="new_wizard_welcome">欢迎来到Threema!</string>
     <string name="new_wizard_move_finger">在屏幕上移动手指</string>
     <string name="new_wizard_this_is_your_id">这是您的Threema ID:</string>
-    <string name="new_wizard_works_like_phone_number">您的Threema ID就像电话号码一样工作。\n您的朋友可以通过此ID与您联系。</string>
+    <string name="new_wizard_works_like_phone_number">您的Threema ID就像电话号码一样。\n您的朋友可以通过此ID与您联系。</string>
     <string name="new_wizard_choose_nickname">选择一个昵称</string>
     <string name="new_wizard_nickname_explain">您的朋友会在通知中看到您的昵称</string>
     <string name="new_wizard_hint_enter_nickname">输入昵称</string>
@@ -704,12 +703,12 @@ Threema ID。您不会出现在朋友的联系人列表中。您真的要
     <string name="wait_one_minute">请至少等待10分钟以使SMS到达,然后再请求呼叫。</string>
     <string name="backup_id">导出的ID</string>
     <string name="backup_data">数据备份</string>
-    <string name="really_leave_group_admin_message">您是该群组的管理员。如果现在离开它,它
-成为孤立的。其他成员仍然可以聊天,但是将无法再进行更改。</string>
+    <string name="really_leave_group_admin_message">您是该群组的管理员。如果现在离开,群组
+群龙无首。其他成员仍然可以聊天,但是将无法再进行更改。</string>
     <string name="action_delete_group">删除群组</string>
     <string name="delete_my_group_message">您确实要完全删除该组吗?所有邮件将被删除,其余成员将无法再使用该网上论坛。</string>
     <string name="error_out_of_memory">没有足够的内存来完成此操作</string>
-    <string name="configure">v</string>
+    <string name="configure">配置</string>
     <string name="file_is_not_audio">不是音频文件</string>
     <!-- restrictions -->
     <string name="disabled_by_policy">某些设置已被设备策略禁用</string>
@@ -850,7 +849,7 @@ Threema ID。您不会出现在朋友的联系人列表中。您真的要
     <string name="permission_phone_required">使用Threema Call时允许管理电话的权限</string>
     <string name="strikethrough">Strikethru</string>
     <string name="italic">斜体</string>
-    <string name="bold">体</string>
+    <string name="bold">体</string>
     <string name="shortcut_choice_title">为…创建快捷方式</string>
     <string name="prefs_title_device_info">设备信息</string>
     <string name="notifications_disabled_title">通知已禁用</string>
@@ -921,7 +920,7 @@ Threema ID。您不会出现在朋友的联系人列表中。您真的要
     <string name="disable_autostart_title">自动开启</string>
     <string name="unchanged">不变的</string>
     <string name="safe_learn_more_button">学到更多</string>
-    <string name="safe_enable_explain">您需要聊天的所有内容仅存储在设备上。您没有我们的帐户,如果您丢失了手机或意外删除了数据,我们将无法为您提供帮助。\n\nThreema Safe在您选择的安全服务器上匿名创建所有重要数据的自动备份,包括您的密钥,联系人列表和组成员身份。</string>
+    <string name="safe_enable_explain">您需要聊天的所有内容仅存储在设备上。因为我们没有保存您的帐户,所以如果您丢失了手机或意外删除了数据,我们将无法为您提供帮助。\n\nThreema Safe在您选择的安全服务器上,以匿名方式创建所有重要数据的自动备份,包括您的密钥,联系人列表和组成员身份。</string>
     <string name="safe_disable_confirm">您真的要在不启用Threema Safe的情况下继续吗?</string>
     <string name="safe_configure_choose_password">请选择一个强密码。您将需要此密码来还原Threema Safe备份。</string>
     <string name="safe_configure_choose_server">选择Threema Safe服务器</string>
@@ -970,7 +969,7 @@ Threema ID。您不会出现在朋友的联系人列表中。您真的要
     <string name="safe_restore">恢复Threema安全</string>
     <string name="backup_restore_in_progress">正在进行备份或还原。检查通知以获取状态信息。</string>
     <string name="restore_error_body">还原未成功完成</string>
-    <string name="forgot_your_id">忘记身份证了吗?</string>
+    <string name="forgot_your_id">忘记ID了吗?</string>
     <string name="restore_success_body">恢复成功完成</string>
     <string name="work_data_sync">资料同步</string>
     <string name="private_contact">私人联络人</string>
@@ -1035,7 +1034,7 @@ Threema ID。您不会出现在朋友的联系人列表中。您真的要
     <string name="permission_camera_photo_required">要拍照,请允许使用相机</string>
     <string name="global_search">搜索聊天</string>
     <string name="global_search_empty_view_text">输入至少两个字符以搜索所有消息</string>
-    <string name="my_id">我的身份</string>
+    <string name="my_id">我的ID</string>
     <string name="profile_picture_and_nickname">个人资料图片和昵称</string>
     <string name="lp_select_this_place">选择这个地方</string>
     <string name="lp_or_select_nearby">或选择附近的地方</string>
@@ -1163,7 +1162,6 @@ Threema ID。您不会出现在朋友的联系人列表中。您真的要
     <string name="insert_datetime">插入日期和时间</string>
     <string name="prefs_sum_disable_smart_replies">禁止Android通知中的智能回复</string>
     <string name="prefs_title_disable_smart_replies">禁用智能回复</string>
-    <!-- TODO: Remove or change this placeholder text -->
     <string name="url_warning_body_alt">您即将打开的链接的主机名可疑。\n\n这可能是试图欺骗您打开一个假装其他网站的尝试。\n\n您仍然要继续吗?</string>
     <string name="read_on">继续阅读…</string>
     <string name="forward_text">转发文字</string>
@@ -1189,4 +1187,23 @@ Threema ID。您不会出现在朋友的联系人列表中。您真的要
     <string name="max_selectable_media_exceeded">一次最多只能发送%d个对象。</string>
     <string name="error_unable_loading_media_thumb">错误。无法加载媒体缩略图</string>
     <string name="select">选择</string>
+    <string name="filter_list">过滤列表</string>
+    <string name="hint_filter_list">输入过滤文字</string>
+    <string name="add">添加</string>
+    <string name="threema_message_from">来自%s的信息</string>
+    <string name="show_text">显示文字</string>
+    <string name="only_images_or_videos">只能选择图片或视频</string>
+    <string name="media_gallery_gifs">GIF</string>
+    <string name="notification_channel_image_labeling">图像索引进度</string>
+    <string name="notification_channel_image_labeling_desc">公共图库图片在后台进行索引中</string>
+    <string name="notification_image_labeling_desc">媒体库索引中</string>
+    <string name="no_media_found_global">此设备上找不到媒体</string>
+    <string name="prefs_sum_image_labeling">在您的图库中启用关键字搜索公共图片</string>
+    <string name="prefs_image_labeling">图片搜索</string>
+    <string name="enable_formatting">启用格式化</string>
+    <string name="original_file_no_longer_avilable">原文件已无法访问,请重新发送信息。</string>
+    <string name="state_transcoding">转码中</string>
+    <string name="importing_files">导入文件</string>
+    <string name="tooltip_image_resolution_hint">调整图像分辨率。</string>
+    <string name="image_labeling_stuck_error">图像搜索的索引过程遇到问題,已取消。请稍后重试。</string>
 </resources>

+ 2 - 0
app/src/main/res/values/attrs.xml

@@ -53,6 +53,8 @@
 	<attr name="textColorTertiary" format="reference"/>
 	<attr name="chat_bubble_recv" format="reference"/>
 	<attr name="chat_bubble_send" format="reference"/>
+	<attr name="chat_bubble_fade_recv" format="reference"/>
+	<attr name="chat_bubble_fade_send" format="reference"/>
 	<attr name="chat_bubble_status" format="reference"/>
 	<attr name="detail_highlight_bg_color" format="reference"/>
 	<attr name="divider_color" format="reference"/>

+ 2 - 0
app/src/main/res/values/strings.xml

@@ -1222,4 +1222,6 @@
 	<string name="importing_files">Importing files</string>
 	<string name="tooltip_image_resolution_hint">Adjust image resolutions.</string>
 	<string name="image_labeling_stuck_error">The indexing process for image search got stuck and was cancelled. Please retry later.</string>
+	<string name="ballot_created_successfully">The poll was successfully created.</string>
+	<string name="file_size">File size</string>
 </resources>

+ 4 - 0
app/src/main/res/values/themes.xml

@@ -159,6 +159,8 @@
 		<!-- bubble theme -->
 		<item name="chat_bubble_recv">@drawable/bubble_recv_selector_light</item>
 		<item name="chat_bubble_send">@drawable/bubble_send_selector_light</item>
+		<item name="chat_bubble_fade_recv">@drawable/bubble_fade_recv_selector_light</item>
+		<item name="chat_bubble_fade_send">@drawable/bubble_fade_send_selector_light</item>
 		<item name="chat_bubble_status">@drawable/bubble_status_selector_light</item>
 		<item name="send_button_background">@drawable/ic_circle_send_light</item>
 		<item name="compose_container">@color/activity_background_secondary</item>
@@ -372,6 +374,8 @@
 		<!-- bubble theme -->
 		<item name="chat_bubble_recv">@drawable/bubble_recv_selector_dark</item>
 		<item name="chat_bubble_send">@drawable/bubble_send_selector_dark</item>
+		<item name="chat_bubble_fade_recv">@drawable/bubble_fade_recv_selector_dark</item>
+		<item name="chat_bubble_fade_send">@drawable/bubble_fade_send_selector_dark</item>
 		<item name="chat_bubble_status" >@drawable/bubble_status_selector_dark</item>
 		<item name="send_button_background">@drawable/ic_circle_send_dark</item>
 		<item name="compose_container">@color/dark_material_primary</item>

+ 2 - 2
app/src/store_threema/java/ch/threema/app/activities/DownloadApkActivity.java

@@ -40,7 +40,7 @@ import android.widget.Toast;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.content.FileProvider;
+import ch.threema.app.NamedFileProvider;
 import ch.threema.app.BuildConfig;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
@@ -78,7 +78,7 @@ public class DownloadApkActivity extends AppCompatActivity implements GenericAle
 					Intent installIntent;
 
 					if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-						uri = FileProvider.getUriForFile(DownloadApkActivity.this, BuildConfig.APPLICATION_ID + ".fileprovider", downloadState.getDestinationFile());
+						uri = NamedFileProvider.getUriForFile(DownloadApkActivity.this, BuildConfig.APPLICATION_ID + ".fileprovider", downloadState.getDestinationFile(), null);
 						installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
 						installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 						installIntent.setData(uri);