Merge branch 'dev'

This commit is contained in:
hjubb
2023-02-06 16:03:19 +11:00
30 changed files with 683 additions and 693 deletions

View File

@@ -64,7 +64,6 @@ import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
import org.thoughtcrime.securesms.dependencies.DatabaseModule;
import org.thoughtcrime.securesms.emoji.EmojiSource;
import org.thoughtcrime.securesms.groups.OpenGroupManager;
import org.thoughtcrime.securesms.groups.OpenGroupMigrator;
import org.thoughtcrime.securesms.home.HomeActivity;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer;
@@ -206,9 +205,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
storage,
messageDataProvider,
()-> KeyPairUtilities.INSTANCE.getUserED25519KeyPair(this));
// migrate session open group data
OpenGroupMigrator.migrate(getDatabaseComponent());
// end migration
callMessageProcessor = new CallMessageProcessor(this, textSecurePreferences, ProcessLifecycleOwner.get().getLifecycle(), storage);
Log.i(TAG, "onCreate()");
startKovenant();

View File

@@ -210,8 +210,7 @@ public class PassphrasePromptActivity extends BaseActionBarActivity {
try {
signature = biometricSecretProvider.getOrCreateBiometricSignature(this);
hasSignatureObject = true;
throw new InvalidKeyException("e");
} catch (InvalidKeyException e) {
} catch (Exception e) {
signature = null;
hasSignatureObject = false;
Log.e(TAG, "Error getting / creating signature", e);

View File

@@ -963,6 +963,18 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
}
override fun copyOpenGroupUrl(thread: Recipient) {
if (!thread.isOpenGroupRecipient) { return }
val threadId = threadDb.getThreadIdIfExistsFor(thread) ?: return
val openGroup = lokiThreadDb.getOpenGroupChat(threadId) ?: return
val clip = ClipData.newPlainText("Community URL", openGroup.joinURL)
val manager = getSystemService(PassphraseRequiredActionBarActivity.CLIPBOARD_SERVICE) as ClipboardManager
manager.setPrimaryClip(clip)
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
}
override fun showExpiringMessagesDialog(thread: Recipient) {
if (thread.isClosedGroupRecipient) {
val group = groupDb.getGroup(thread.address.toGroupString()).orNull()

View File

@@ -78,6 +78,10 @@ object ConversationMenuHelper {
inflater.inflate(R.menu.menu_conversation_expiration_off, menu)
}
}
// One-on-one chat menu allows copying the session id
if (thread.isContactRecipient) {
inflater.inflate(R.menu.menu_conversation_copy_session_id, menu)
}
// One-on-one chat menu (options that should only be present for one-on-one chats)
if (thread.isContactRecipient) {
if (thread.isBlocked) {
@@ -154,6 +158,7 @@ object ConversationMenuHelper {
R.id.menu_block -> { block(context, thread, deleteThread = false) }
R.id.menu_block_delete -> { blockAndDelete(context, thread) }
R.id.menu_copy_session_id -> { copySessionID(context, thread) }
R.id.menu_copy_open_group_url -> { copyOpenGroupUrl(context, thread) }
R.id.menu_edit_group -> { editClosedGroup(context, thread) }
R.id.menu_leave_group -> { leaveClosedGroup(context, thread) }
R.id.menu_invite_to_open_group -> { inviteContacts(context, thread) }
@@ -270,6 +275,12 @@ object ConversationMenuHelper {
listener.copySessionID(thread.address.toString())
}
private fun copyOpenGroupUrl(context: Context, thread: Recipient) {
if (!thread.isOpenGroupRecipient) { return }
val listener = context as? ConversationMenuListener ?: return
listener.copyOpenGroupUrl(thread)
}
private fun editClosedGroup(context: Context, thread: Recipient) {
if (!thread.isClosedGroupRecipient) { return }
val intent = Intent(context, EditClosedGroupActivity::class.java)
@@ -344,6 +355,7 @@ object ConversationMenuHelper {
fun block(deleteThread: Boolean = false)
fun unblock()
fun copySessionID(sessionId: String)
fun copyOpenGroupUrl(thread: Recipient)
fun showExpiringMessagesDialog(thread: Recipient)
}

View File

@@ -57,7 +57,6 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
import org.thoughtcrime.securesms.groups.OpenGroupMigrator;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
@@ -800,77 +799,6 @@ public class ThreadDatabase extends Database {
return query;
}
@NotNull
public List<ThreadRecord> getHttpOxenOpenGroups() {
String where = TABLE_NAME+"."+ADDRESS+" LIKE ?";
String selection = OpenGroupMigrator.HTTP_PREFIX+OpenGroupMigrator.OPEN_GET_SESSION_TRAILING_DOT_ENCODED +"%";
SQLiteDatabase db = databaseHelper.getReadableDatabase();
String query = createQuery(where, 0);
Cursor cursor = db.rawQuery(query, new String[]{selection});
if (cursor == null) {
return Collections.emptyList();
}
List<ThreadRecord> threads = new ArrayList<>();
try {
Reader reader = readerFor(cursor);
ThreadRecord record;
while ((record = reader.getNext()) != null) {
threads.add(record);
}
} finally {
cursor.close();
}
return threads;
}
@NotNull
public List<ThreadRecord> getLegacyOxenOpenGroups() {
String where = TABLE_NAME+"."+ADDRESS+" LIKE ?";
String selection = OpenGroupMigrator.LEGACY_GROUP_ENCODED_ID+"%";
SQLiteDatabase db = databaseHelper.getReadableDatabase();
String query = createQuery(where, 0);
Cursor cursor = db.rawQuery(query, new String[]{selection});
if (cursor == null) {
return Collections.emptyList();
}
List<ThreadRecord> threads = new ArrayList<>();
try {
Reader reader = readerFor(cursor);
ThreadRecord record;
while ((record = reader.getNext()) != null) {
threads.add(record);
}
} finally {
cursor.close();
}
return threads;
}
@NotNull
public List<ThreadRecord> getHttpsOxenOpenGroups() {
String where = TABLE_NAME+"."+ADDRESS+" LIKE ?";
String selection = OpenGroupMigrator.NEW_GROUP_ENCODED_ID+"%";
SQLiteDatabase db = databaseHelper.getReadableDatabase();
String query = createQuery(where, 0);
Cursor cursor = db.rawQuery(query, new String[]{selection});
if (cursor == null) {
return Collections.emptyList();
}
List<ThreadRecord> threads = new ArrayList<>();
try {
Reader reader = readerFor(cursor);
ThreadRecord record;
while ((record = reader.getNext()) != null) {
threads.add(record);
}
} finally {
cursor.close();
}
return threads;
}
public void migrateEncodedGroup(long threadId, @NotNull String newEncodedGroupId) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(ADDRESS, newEncodedGroupId);

View File

@@ -97,25 +97,40 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private final DatabaseSecret databaseSecret;
public SQLCipherOpenHelper(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) {
super(context, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, MIN_DATABASE_VERSION, null, new SQLiteDatabaseHook() {
@Override
public void preKey(SQLiteConnection connection) {
SQLCipherOpenHelper.applySQLCipherPragmas(connection, true);
}
@Override
public void postKey(SQLiteConnection connection) {
SQLCipherOpenHelper.applySQLCipherPragmas(connection, true);
// if not vacuumed in a while, perform that operation
long currentTime = System.currentTimeMillis();
// 7 days
if (currentTime - TextSecurePreferences.getLastVacuumTime(context) > 604_800_000) {
connection.execute("VACUUM;", null, null);
TextSecurePreferences.setLastVacuumNow(context);
super(
context,
DATABASE_NAME,
databaseSecret.asString(),
null,
DATABASE_VERSION,
MIN_DATABASE_VERSION,
null,
new SQLiteDatabaseHook() {
@Override
public void preKey(SQLiteConnection connection) {
SQLCipherOpenHelper.applySQLCipherPragmas(connection, true);
}
}
}, true);
@Override
public void postKey(SQLiteConnection connection) {
SQLCipherOpenHelper.applySQLCipherPragmas(connection, true);
// if not vacuumed in a while, perform that operation
long currentTime = System.currentTimeMillis();
// 7 days
if (currentTime - TextSecurePreferences.getLastVacuumTime(context) > 604_800_000) {
connection.execute("VACUUM;", null, null);
TextSecurePreferences.setLastVacuumNow(context);
}
}
},
// Note: Now that we support concurrent database reads the migrations are actually non-blocking
// because of this we need to initially open the database with writeAheadLogging (WAL mode) disabled
// and enable it once the database officially opens it's connection (which will cause it to re-connect
// in WAL mode) - this is a little inefficient but will prevent SQL-related errors/crashes due to
// incomplete migrations
false
);
this.context = context.getApplicationContext();
this.databaseSecret = databaseSecret;
@@ -150,11 +165,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
// If the old SQLCipher3 database file doesn't exist then no need to do anything
if (!oldDbFile.exists()) { return; }
try {
// Define the location for the new database
String newDbPath = context.getDatabasePath(DATABASE_NAME).getPath();
File newDbFile = new File(newDbPath);
// Define the location for the new database
String newDbPath = context.getDatabasePath(DATABASE_NAME).getPath();
File newDbFile = new File(newDbPath);
try {
// If the new database file already exists then check if it's valid first, if it's in an
// invalid state we should delete it and try to migrate again
if (newDbFile.exists()) {
@@ -162,10 +177,24 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
// assume the user hasn't downgraded for some reason and made changes to the old database and
// can remove the old database file (it won't be used anymore)
if (oldDbFile.lastModified() <= newDbFile.lastModified()) {
// TODO: Delete 'CIPHER3_DATABASE_NAME' once enough time has past
// //noinspection ResultOfMethodCallIgnored
// oldDbFile.delete();
return;
try {
SQLiteDatabase newDb = SQLCipherOpenHelper.open(newDbPath, databaseSecret, true);
int version = newDb.getVersion();
newDb.close();
// Make sure the new database has it's version set correctly (if not then the migration didn't
// fully succeed and the database will try to create all it's tables and immediately fail so
// we will need to remove and remigrate)
if (version > 0) {
// TODO: Delete 'CIPHER3_DATABASE_NAME' once enough time has past
// //noinspection ResultOfMethodCallIgnored
// oldDbFile.delete();
return;
}
}
catch (Exception e) {
Log.i(TAG, "Failed to retrieve version from new database, assuming invalid and remigrating");
}
}
// If the old database does have newer changes then the new database could have stale/invalid
@@ -207,6 +236,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
catch (Exception e) {
Log.e(TAG, "Migration from SQLCipher3 to SQLCipher4 failed", e);
// If an exception was thrown then we should remove the new database file (it's probably invalid)
if (!newDbFile.delete()) {
Log.e(TAG, "Unable to delete invalid new database file");
}
// Notify the user of the issue so they know they can downgrade until the issue is fixed
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
String channelId = context.getString(R.string.NotificationChannel_failures);
@@ -559,6 +593,15 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
}
}
@Override
public void onOpen(SQLiteDatabase db) {
super.onOpen(db);
// Now that the database is officially open (ie. the migrations are completed) we want to enable
// write ahead logging (WAL mode) to officially support concurrent read connections
db.enableWriteAheadLogging();
}
public void markCurrent(SQLiteDatabase db) {
db.setVersion(DATABASE_VERSION);
}

View File

@@ -1,139 +0,0 @@
package org.thoughtcrime.securesms.groups
import androidx.annotation.VisibleForTesting
import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.Hex
import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
object OpenGroupMigrator {
const val HTTP_PREFIX = "__loki_public_chat_group__!687474703a2f2f"
private const val HTTPS_PREFIX = "__loki_public_chat_group__!68747470733a2f2f"
const val OPEN_GET_SESSION_TRAILING_DOT_ENCODED = "6f70656e2e67657473657373696f6e2e6f72672e"
const val LEGACY_GROUP_ENCODED_ID = "__loki_public_chat_group__!687474703a2f2f3131362e3230332e37302e33332e" // old IP based toByteArray()
const val NEW_GROUP_ENCODED_ID = "__loki_public_chat_group__!68747470733a2f2f6f70656e2e67657473657373696f6e2e6f72672e" // new URL based toByteArray()
data class OpenGroupMapping(val stub: String, val legacyThreadId: Long, val newThreadId: Long?)
@VisibleForTesting
fun Recipient.roomStub(): String? {
if (!isOpenGroupRecipient) return null
val serialized = address.serialize()
if (serialized.startsWith(LEGACY_GROUP_ENCODED_ID)) {
return serialized.replace(LEGACY_GROUP_ENCODED_ID,"")
} else if (serialized.startsWith(NEW_GROUP_ENCODED_ID)) {
return serialized.replace(NEW_GROUP_ENCODED_ID,"")
} else if (serialized.startsWith(HTTP_PREFIX + OPEN_GET_SESSION_TRAILING_DOT_ENCODED)) {
return serialized.replace(HTTP_PREFIX + OPEN_GET_SESSION_TRAILING_DOT_ENCODED, "")
}
return null
}
@VisibleForTesting
fun getExistingMappings(legacy: List<ThreadRecord>, new: List<ThreadRecord>): List<OpenGroupMapping> {
val legacyStubsMapping = legacy.mapNotNull { thread ->
val stub = thread.recipient.roomStub()
stub?.let { it to thread.threadId }
}
val newStubsMapping = new.mapNotNull { thread ->
val stub = thread.recipient.roomStub()
stub?.let { it to thread.threadId }
}
return legacyStubsMapping.map { (legacyEncodedStub, legacyId) ->
// get 'new' open group thread ID if stubs match
OpenGroupMapping(
legacyEncodedStub,
legacyId,
newStubsMapping.firstOrNull { (newEncodedStub, _) -> newEncodedStub == legacyEncodedStub }?.second
)
}
}
@JvmStatic
fun migrate(databaseComponent: DatabaseComponent) {
// migrate thread db
val threadDb = databaseComponent.threadDatabase()
val legacyOpenGroups = threadDb.legacyOxenOpenGroups
val httpBasedNewGroups = threadDb.httpOxenOpenGroups
if (legacyOpenGroups.isEmpty() && httpBasedNewGroups.isEmpty()) return // no need to migrate
val newOpenGroups = threadDb.httpsOxenOpenGroups
val firstStepMigration = getExistingMappings(legacyOpenGroups, newOpenGroups)
val secondStepMigration = getExistingMappings(httpBasedNewGroups, newOpenGroups)
val groupDb = databaseComponent.groupDatabase()
val lokiApiDb = databaseComponent.lokiAPIDatabase()
val smsDb = databaseComponent.smsDatabase()
val mmsDb = databaseComponent.mmsDatabase()
val lokiMessageDatabase = databaseComponent.lokiMessageDatabase()
val lokiThreadDatabase = databaseComponent.lokiThreadDatabase()
firstStepMigration.forEach { (stub, old, new) ->
val legacyEncodedGroupId = LEGACY_GROUP_ENCODED_ID+stub
if (new == null) {
val newEncodedGroupId = NEW_GROUP_ENCODED_ID+stub
// migrate thread and group encoded values
threadDb.migrateEncodedGroup(old, newEncodedGroupId)
groupDb.migrateEncodedGroup(legacyEncodedGroupId, newEncodedGroupId)
// migrate Loki API DB values
// decode the hex to bytes, decode byte array to string i.e. "oxen" or "session"
val decodedStub = Hex.fromStringCondensed(stub).decodeToString()
val legacyLokiServerId = "${OpenGroupApi.legacyDefaultServer}.$decodedStub"
val newLokiServerId = "${OpenGroupApi.defaultServer}.$decodedStub"
lokiApiDb.migrateLegacyOpenGroup(legacyLokiServerId, newLokiServerId)
// migrate loki thread db server info
val oldServerInfo = lokiThreadDatabase.getOpenGroupChat(old)
val newServerInfo = oldServerInfo!!.copy(server = OpenGroupApi.defaultServer, id = newLokiServerId)
lokiThreadDatabase.setOpenGroupChat(newServerInfo, old)
} else {
// has a legacy and a new one
// migrate SMS and MMS tables
smsDb.migrateThreadId(old, new)
mmsDb.migrateThreadId(old, new)
lokiMessageDatabase.migrateThreadId(old, new)
// delete group for legacy ID
groupDb.delete(legacyEncodedGroupId)
// delete thread for legacy ID
threadDb.deleteConversation(old)
lokiThreadDatabase.removeOpenGroupChat(old)
}
// maybe migrate jobs here
}
secondStepMigration.forEach { (stub, old, new) ->
val legacyEncodedGroupId = HTTP_PREFIX + OPEN_GET_SESSION_TRAILING_DOT_ENCODED + stub
if (new == null) {
val newEncodedGroupId = NEW_GROUP_ENCODED_ID+stub
// migrate thread and group encoded values
threadDb.migrateEncodedGroup(old, newEncodedGroupId)
groupDb.migrateEncodedGroup(legacyEncodedGroupId, newEncodedGroupId)
// migrate Loki API DB values
// decode the hex to bytes, decode byte array to string i.e. "oxen" or "session"
val decodedStub = Hex.fromStringCondensed(stub).decodeToString()
val legacyLokiServerId = "${OpenGroupApi.httpDefaultServer}.$decodedStub"
val newLokiServerId = "${OpenGroupApi.defaultServer}.$decodedStub"
lokiApiDb.migrateLegacyOpenGroup(legacyLokiServerId, newLokiServerId)
// migrate loki thread db server info
val oldServerInfo = lokiThreadDatabase.getOpenGroupChat(old)
val newServerInfo = oldServerInfo!!.copy(server = OpenGroupApi.defaultServer, id = newLokiServerId)
lokiThreadDatabase.setOpenGroupChat(newServerInfo, old)
} else {
// has a legacy and a new one
// migrate SMS and MMS tables
smsDb.migrateThreadId(old, new)
mmsDb.migrateThreadId(old, new)
lokiMessageDatabase.migrateThreadId(old, new)
// delete group for legacy ID
groupDb.delete(legacyEncodedGroupId)
// delete thread for legacy ID
threadDb.deleteConversation(old)
lokiThreadDatabase.removeOpenGroupChat(old)
}
// maybe migrate jobs here
}
}
}

View File

@@ -21,26 +21,26 @@ public class PushMediaConstraints extends MediaConstraints {
@Override
public int getImageMaxSize(Context context) {
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
return FileServerApi.maxFileSize;
}
@Override
public int getGifMaxSize(Context context) {
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
return FileServerApi.maxFileSize;
}
@Override
public int getVideoMaxSize(Context context) {
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
return FileServerApi.maxFileSize;
}
@Override
public int getAudioMaxSize(Context context) {
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
return FileServerApi.maxFileSize;
}
@Override
public int getDocumentMaxSize(Context context) {
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
return FileServerApi.maxFileSize;
}
}

View File

@@ -60,7 +60,7 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
// FIXME: Using a job here seems like a bad idea...
MessageReceiveParameters(envelope.toByteArray(), serverHash, null)
}
BatchMessageReceiveJob(params).executeAsync()
BatchMessageReceiveJob(params).executeAsync("background")
}
promises.add(dmsPromise)

View File

@@ -2,6 +2,10 @@
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:title="@string/ConversationActivity_copy_open_group_url"
android:id="@+id/menu_copy_open_group_url" />
<item
android:title="@string/ConversationActivity_invite_to_open_group"
android:id="@+id/menu_invite_to_open_group" />

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Session</string>
<string name="yes">Oui</string>
<string name="no">Non</string>
<string name="delete">Supprimer</string>
<string name="ban">Bannir</string>
<string name="please_wait">Veuillez patienter…</string>
<string name="save">Enregistrer</string>
<string name="note_to_self">Note à mon intention</string>
<string name="version_s">Version %s</string>
@@ -19,7 +19,7 @@
</plurals>
<string name="ApplicationPreferencesActivity_delete_all_old_messages_now">Supprimer tous les anciens messages maintenant?</string>
<plurals name="ApplicationPreferencesActivity_this_will_immediately_trim_all_conversations_to_the_d_most_recent_messages">
<item quantity="one">Cela va immédiatement réduire toutes les conversations pour quil ne reste que le message le plus récent.</item>
<item quantity="one">Cela réduira immédiatement toutes les conversations au message le plus récent.</item>
<item quantity="other">Cela réduira immédiatement toutes les conversations aux %d messages les plus récents.</item>
</plurals>
<string name="ApplicationPreferencesActivity_delete">Supprimer</string>
@@ -63,6 +63,7 @@
<string name="ConversationActivity_muted_until_date">Son désactivé jusqu\'à %1$s</string>
<string name="ConversationActivity_muted_forever">En sourdine</string>
<string name="ConversationActivity_member_count">%1$d membres</string>
<string name="ConversationActivity_active_member_count">%1$d membres actifs</string>
<string name="ConversationActivity_open_group_guidelines">Règles de la communauté</string>
<string name="ConversationActivity_invalid_recipient">Le destinataire est invalide!</string>
<string name="ConversationActivity_added_to_home_screen">Ajouté à lécran daccueil</string>
@@ -82,7 +83,9 @@
<string name="ConversationActivity_to_send_photos_and_video_allow_signal_access_to_storage">Session a besoin d\'un accès au stockage pour envoyer des photos et des vidéos.</string>
<string name="ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video">Session a besoin de lautorisation Appareil photo afin de prendre des photos ou des vidéos, mais elle a été refusée définitivement. Veuillez accéder au menu des paramètres des applis, sélectionner Autorisations et activer Appareil photo.</string>
<string name="ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video">Session a besoin de lautorisation Appareil photo pour prendre des photos ou des vidéos</string>
<string name="ConversationActivity_search_position">%1$d de %2$d</string>
<string name="ConversationActivity_search_position">%1$d sur %2$d</string>
<string name="ConversationActivity_call_title">Autorisations d\'appel requises</string>
<string name="ConversationActivity_call_prompt">Vous pouvez activer la permission \"Appels vocaux et vidéo\" dans les paramètres de confidentialité.</string>
<!-- ConversationFragment -->
<plurals name="ConversationFragment_delete_selected_messages">
<item quantity="one">Supprimer le message sélectionné?</item>
@@ -99,13 +102,17 @@
<item quantity="other">Lenregistrement des %1$d médias dans la mémoire permettra à nimporte quelles autres applis de votre appareil dy accéder.\n\nContinuer?</item>
</plurals>
<plurals name="ConversationFragment_error_while_saving_attachments_to_sd_card">
<item quantity="one">Erreur denregistrement de la pièce jointe dans la mémoire!</item>
<item quantity="one">Erreur lors de lenregistrement de la pièce jointe dans la mémoire!</item>
<item quantity="other">Erreur denregistrement des pièces jointes dans la mémoire!</item>
</plurals>
<plurals name="ConversationFragment_saving_n_attachments">
<item quantity="one">Enregistrement de la pièce jointe</item>
<item quantity="other">Enregistrement de %1$d pièces jointes</item>
</plurals>
<plurals name="ConversationFragment_saving_n_attachments_to_sd_card">
<item quantity="one">Enregistrement de la pièce jointe dans la mémoire…</item>
<item quantity="other">Enregistrement de %1$d pièces jointes dans la mémoire…</item>
</plurals>
<!-- CreateProfileActivity -->
<string name="CreateProfileActivity_profile_photo">Photo de profil</string>
<!-- CustomDefaultPreference -->
@@ -156,7 +163,7 @@
<!-- MediaPickerActivity -->
<string name="MediaPickerActivity_send_to">Envoyer à %s</string>
<!-- MediaSendActivity -->
<string name="MediaSendActivity_add_a_caption">Ajouter un légende</string>
<string name="MediaSendActivity_add_a_caption">Ajouter une légende...</string>
<string name="MediaSendActivity_an_item_was_removed_because_it_exceeded_the_size_limit">Un élément a été supprimé, car il dépassait la taille limite</string>
<string name="MediaSendActivity_camera_unavailable">Lappareil photo nest pas disponible</string>
<string name="MediaSendActivity_message_to_s">Message à %s</string>
@@ -191,6 +198,7 @@
<string name="Slide_video">Vidéo</string>
<!-- SmsMessageRecord -->
<string name="SmsMessageRecord_received_corrupted_key_exchange_message">Vous avez reçu un message déchange de clés corrompu!</string>
<string name="SmsMessageRecord_received_key_exchange_message_for_invalid_protocol_version"> Le message d\'échange de clé reçu est pour une version du protocole invalide. </string>
<string name="SmsMessageRecord_received_message_with_new_safety_number_tap_to_process">Vous avez reçu un message avec un nouveau numéro de sécurité. Touchez pour le traiter et lafficher.</string>
<string name="SmsMessageRecord_secure_session_reset">Vous avez réinitialisé la session sécurisée.</string>
<string name="SmsMessageRecord_secure_session_reset_s">%s a réinitialisé la session sécurisée.</string>
@@ -201,7 +209,7 @@
<string name="ThreadRecord_secure_session_reset">La session sécurisée a été réinitialisée.</string>
<string name="ThreadRecord_draft">Brouillon :</string>
<string name="ThreadRecord_called">Vous avez appelé</string>
<string name="ThreadRecord_called_you">Vous a appelé</string>
<string name="ThreadRecord_called_you">Vous a appelé·e</string>
<string name="ThreadRecord_missed_call">Appel manqué</string>
<string name="ThreadRecord_media_message">Message multimédia</string>
<string name="ThreadRecord_s_is_on_signal">%s est sur Session!</string>
@@ -304,7 +312,7 @@
<string name="conversation_activity__send">Envoyer</string>
<string name="conversation_activity__compose_description">Rédaction dun message</string>
<string name="conversation_activity__emoji_toggle_description">Afficher, masquer le clavier des émojis</string>
<string name="conversation_activity__attachment_thumbnail">Imagette de pièces jointes</string>
<string name="conversation_activity__attachment_thumbnail">Vignette de pièce jointe</string>
<string name="conversation_activity__quick_attachment_drawer_toggle_camera_description">Afficher, masquer le tiroir permettant de lancer lappareil photo à basse résolution</string>
<string name="conversation_activity__quick_attachment_drawer_record_and_send_audio_description">Enregistrer et envoyer une pièce jointe audio</string>
<string name="conversation_activity__quick_attachment_drawer_lock_record_description">Verrouiller lenregistrement de pièces jointes audio</string>
@@ -378,9 +386,9 @@
<string name="arrays__settings_default">Valeur par défaut</string>
<string name="arrays__enabled">Activé</string>
<string name="arrays__disabled">Désactivé</string>
<string name="arrays__name_and_message">Nom et message</string>
<string name="arrays__name_only">Nom seulement</string>
<string name="arrays__no_name_or_message">Aucun nom ni message</string>
<string name="arrays__name_and_message">Nom et Contenu</string>
<string name="arrays__name_only">Nom uniquement</string>
<string name="arrays__no_name_or_message">Aucun nom ni contenu</string>
<string name="arrays__images">Images</string>
<string name="arrays__audio">Son</string>
<string name="arrays__video">Vidéo</string>
@@ -398,9 +406,14 @@
<item quantity="other">%d heures</item>
</plurals>
<!-- preferences.xml -->
<string name="preferences__pref_enter_sends_title">La touche Entrée envoie</string>
<string name="preferences__send_link_previews">Envoyer des aperçus de liens</string>
<string name="preferences__previews_are_supported_for">Les aperçus sont pris en charge pour les liens Imgur, Instagram, Pinterest, Reddit et YouTube</string>
<string name="preferences__pref_enter_sends_title">Envoyer avec bouton Entrée</string>
<string name="preferences__pref_enter_sends_summary">Appuyer sur la touche Entrée enverra un message au lieu de commencer une nouvelle ligne.</string>
<string name="preferences__send_link_previews">Envoyer les aperçus des liens</string>
<string name="preferences__link_previews">Aperçus des liens</string>
<string name="preferences__link_previews_summary">Générer les aperçus des liens pour les URLs supportés.</string>
<string name="preferences__pref_autoplay_audio_category">Messages audio</string>
<string name="preferences__pref_autoplay_audio_title">Lire automatiquement les messages audios</string>
<string name="preferences__pref_autoplay_audio_summary">Lire automatiquement les messages audio consécutifs.</string>
<string name="preferences__screen_security">Sécurité de lécran</string>
<string name="preferences__disable_screen_security_to_allow_screen_shots">Bloquer les captures décran dans la liste des récents et dans lappli</string>
<string name="preferences__notifications">Notifications</string>
@@ -408,6 +421,7 @@
<string name="preferences__led_color_unknown">Inconnue</string>
<string name="preferences__pref_led_blink_title">Rythme de clignotement de la DEL</string>
<string name="preferences__sound">Son</string>
<string name="preferences__in_app_sounds">Son à l\'ouverture de l\'application</string>
<string name="preferences__silent">Silencieux</string>
<string name="preferences__repeat_alerts">Répéter les alertes</string>
<string name="preferences__never">Jamais</string>
@@ -436,18 +450,27 @@
<string name="preferences__default">Valeur par défaut</string>
<string name="preferences__incognito_keyboard">Clavier incognito</string>
<string name="preferences__read_receipts">Accusés de lecture</string>
<string name="preferences__read_receipts_summary">Envoyer des accusés de lecture dans les conversations individuelles.</string>
<string name="preferences__typing_indicators">Indicateurs de saisie</string>
<string name="preferences__if_typing_indicators_are_disabled_you_wont_be_able_to_see_typing_indicators">Si les indicateurs de saisie sont désactivés, vous ne serez pas en mesure de voir les indicateurs de saisie des autres.</string>
<string name="preferences__typing_indicators_summary">Voir et envoyer les indicateurs de saisie dans les conversations un à un.</string>
<string name="preferences__request_keyboard_to_disable_personalized_learning">Demander au clavier de désactiver lapprentissage personnalisé</string>
<string name="preferences__light_theme">Clair</string>
<string name="preferences__dark_theme">Sombre</string>
<string name="preferences_chats__message_trimming">Élagage des messages</string>
<string name="preferences_chats__message_trimming_title">Raccourcir les communautés</string>
<string name="preferences_chats__message_trimming_summary">Supprimer des messages de plus de 6 mois dans des communautés qui ont plus de 2 000 messages.</string>
<string name="preferences_advanced__use_system_emoji">Utiliser les émojis du système</string>
<string name="preferences_advanced__disable_signal_built_in_emoji_support">Désactiver la prise en charge des émojis intégrés à Session</string>
<string name="preferences_app_protection__screen_security">Sécurité d\'écran</string>
<string name="preferences_chats__chats">Conversations</string>
<string name="preferences_notifications__messages">Messages</string>
<string name="preferences_notifications__in_chat_sounds">Sons des conversations</string>
<string name="preferences_notifications__content">Contenu de la notification</string>
<string name="preferences_notifications__content_message">Afficher :</string>
<string name="preferences_notifications__summary">Informations affichées dans les notifications.</string>
<string name="preferences_notifications__priority">Priorité</string>
<string name="preferences_app_protection__screenshot_notifications">Notifications de capture d\'écran</string>
<string name="preferences_app_protected__screenshot_notifications_summary">Recevoir une notification lorsqu\'un contact prend une capture d\'écran d\'une conversation individuelle.</string>
<!-- **************************************** -->
<!-- menus -->
<!-- **************************************** -->
@@ -460,7 +483,10 @@
<string name="conversation_context__menu_ban_user">Bannir l\'utilisateur</string>
<string name="conversation_context__menu_ban_and_delete_all">Bannir et supprimer tout</string>
<string name="conversation_context__menu_resend_message">Renvoyer le message</string>
<string name="conversation_context__menu_reply">Répondre</string>
<string name="conversation_context__menu_reply_to_message">Répondre au message</string>
<string name="conversation_context__menu_call">Appeler</string>
<string name="conversation_context__menu_select">Sélectionner</string>
<!-- conversation_context_image -->
<string name="conversation_context_image__save_attachment">Enregistrer la pièce jointe</string>
<!-- conversation_expiring_off -->
@@ -513,8 +539,8 @@
<string name="LocalBackupJob_creating_backup">Création de la sauvegarde…</string>
<string name="ProgressPreference_d_messages_so_far">%d messages pour linstant</string>
<string name="BackupUtil_never">Jamais</string>
<string name="preferences_app_protection__screen_lock">Verrouillage de lécran</string>
<string name="preferences_app_protection__lock_signal_access_with_android_screen_lock_or_fingerprint">Verrouiller laccès à Session avec le verrouillage de lécran dAndroid ou une empreinte</string>
<string name="preferences_app_protection__screen_lock">Verrouiller Session</string>
<string name="preferences_app_protection__lock_signal_access_with_android_screen_lock_or_fingerprint">Nécessite une empreinte digitale, un code PIN, un schéma ou un mot de passe pour déverrouiller Session.</string>
<string name="preferences_app_protection__screen_lock_inactivity_timeout">Délai dinactivité avant verrouillage de lécran</string>
<string name="AppProtectionPreferenceFragment_none">Aucune</string>
<!-- Conversation activity -->
@@ -522,6 +548,7 @@
<!-- Session -->
<string name="continue_2">Continuer</string>
<string name="copy">Copier</string>
<string name="close">Fermer</string>
<string name="invalid_url">URL non valide</string>
<string name="copied_to_clipboard">Copié dans le presse-papier</string>
<string name="next">Suivant</string>
@@ -573,12 +600,12 @@
<string name="activity_path_resolving_progress">Contact en cours…</string>
<string name="activity_create_private_chat_title">Nouvelle Session</string>
<string name="activity_create_private_chat_enter_session_id_tab_title">Saisir un Session ID</string>
<string name="activity_create_private_chat_scan_qr_code_tab_title">Scanner un Code QR</string>
<string name="activity_create_private_chat_scan_qr_code_explanation">Scannez le code QR d\'un utilisateur pour démarrer une session. Les codes QR peuvent se trouver en touchant l\'icône du code QR dans les paramètres du compte.</string>
<string name="activity_create_private_chat_scan_qr_code_tab_title">Scanner un QR Code</string>
<string name="activity_create_private_chat_scan_qr_code_explanation">Scannez le QR code d\'un utilisateur pour démarrer une session. Les QR codes peuvent se trouver en touchant l\'icône du QR code dans les paramètres du compte.</string>
<string name="fragment_enter_public_key_edit_text_hint">Entrer un Session ID ou un nom ONS</string>
<string name="fragment_enter_public_key_explanation">Les utilisateurs peuvent partager leur Session ID depuis les paramètres du compte ou en utilisant le code QR.</string>
<string name="fragment_enter_public_key_error_message">Veuillez vérifier le Session ID ou le nom ONS et réessayer.</string>
<string name="fragment_scan_qr_code_camera_access_explanation">Session a besoin d\'accéder à l\'appareil photo pour scanner les codes QR</string>
<string name="fragment_scan_qr_code_camera_access_explanation">Session a besoin d\'accéder à l\'appareil photo pour scanner les QR codes</string>
<string name="fragment_scan_qr_code_grant_camera_access_button_title">Autoriser l\'accès</string>
<string name="activity_create_closed_group_title">Nouveau groupe privé</string>
<string name="activity_create_closed_group_edit_text_hint">Saisissez un nom de groupe</string>
@@ -591,7 +618,7 @@
<string name="activity_join_public_chat_title">Joindre un groupe public</string>
<string name="activity_join_public_chat_error">Impossible de rejoindre le groupe</string>
<string name="activity_join_public_chat_enter_group_url_tab_title">URL du groupe public</string>
<string name="activity_join_public_chat_scan_qr_code_tab_title">Scannez le code QR</string>
<string name="activity_join_public_chat_scan_qr_code_tab_title">Scanner le QR Code</string>
<string name="activity_join_public_chat_scan_qr_code_explanation">Scannez le code QR du groupe public que vous souhaitez rejoindre</string>
<string name="fragment_enter_chat_url_edit_text_hint">Saisissez une URL de groupe public</string>
<string name="activity_settings_title">Paramètres</string>
@@ -600,6 +627,7 @@
<string name="activity_settings_display_name_too_long_error">Veuillez choisir un nom d\'utilisateur plus court</string>
<string name="activity_settings_privacy_button_title">Confidentialité</string>
<string name="activity_settings_notifications_button_title">Notifications</string>
<string name="activity_settings_message_requests_button_title">Demandes de message</string>
<string name="activity_settings_chats_button_title">Conversations</string>
<string name="activity_settings_devices_button_title">Appareils reliés</string>
<string name="activity_settings_invite_button_title">Inviter un ami</string>
@@ -612,25 +640,39 @@
<string name="activity_notification_settings_style_section_title">Style de notification</string>
<string name="activity_notification_settings_content_section_title">Contenu de notification</string>
<string name="activity_privacy_settings_title">Confidentialité</string>
<string name="activity_conversations_settings_title">Conversations</string>
<string name="activity_help_settings_title">Aide</string>
<string name="activity_help_settings__report_bug_title">Signaler un bug</string>
<string name="activity_help_settings__report_bug_summary">Exportez vos logs, puis télécharger le fichier au service d\'aide de Session.</string>
<string name="activity_help_settings__translate_session">Traduire Session</string>
<string name="activity_help_settings__feedback">Nous aimerions avoir votre avis</string>
<string name="activity_help_settings__faq">FAQ</string>
<string name="activity_help_settings__support">Assistance</string>
<string name="activity_help_settings__export_logs">Exporter les journaux</string>
<string name="preferences_notifications_strategy_category_title">Stratégie de notification</string>
<string name="preferences_notifications_strategy_category_fast_mode_title">Utiliser le Mode Rapide</string>
<string name="preferences_notifications_strategy_category_fast_mode_summary">Vous serez averti de nouveaux messages de manière fiable et immédiate en utilisant les serveurs de notification de Google.</string>
<string name="fragment_device_list_bottom_sheet_change_name_button_title">Modifier le nom</string>
<string name="fragment_device_list_bottom_sheet_unlink_device_button_title">Déconnecter l\'appareil</string>
<string name="dialog_seed_title">Votre phrase de récupération</string>
<string name="dialog_seed_explanation">Ceci est votre phrase de récupération. Elle vous permet de restaurer ou migrer votre Session ID vers un nouvel appareil.</string>
<string name="dialog_seed_explanation">Vous pouvez utiliser votre phrase de récupération pour restaurer votre compte ou relier un appareil.</string>
<string name="dialog_clear_all_data_title">Effacer toutes les données</string>
<string name="dialog_clear_all_data_explanation">Cela supprimera définitivement vos messages, vos sessions et vos contacts.</string>
<string name="dialog_clear_all_data_network_explanation">Souhaitez-vous effacer seulement cet appareil ou supprimer l\'ensemble de votre compte ?</string>
<string name="dialog_clear_all_data_message">Cela supprimera définitivement vos messages, sessions et contacts. Voulez-vous uniquement effacer cet appareil ou supprimer l\'intégralité de votre compte ?</string>
<string name="dialog_clear_all_data_clear_device_only">Effacer l\'appareil uniquement</string>
<string name="dialog_clear_all_data_clear_device_and_network">Effacer l\'appareil et le réseau</string>
<string name="dialog_clear_all_data_clear_device_and_network_confirmation">Êtes-vous sûr de vouloir supprimer vos données du réseau ? Si vous continuez, vous ne pourrez pas restaurer vos messages ou vos contacts.</string>
<string name="dialog_clear_all_data_clear">Effacer</string>
<string name="dialog_clear_all_data_local_only">Effacer seulement</string>
<string name="dialog_clear_all_data_clear_network">Compte complet</string>
<string name="activity_qr_code_title">Code QR</string>
<string name="activity_qr_code_view_my_qr_code_tab_title">Afficher mon code QR</string>
<string name="activity_qr_code_view_scan_qr_code_tab_title">Scanner le code QR</string>
<string name="activity_qr_code_view_scan_qr_code_explanation">Scannez le code QR d\'un autre utilisateur pour démarrer une session</string>
<string name="activity_qr_code_title">QR Code</string>
<string name="activity_qr_code_view_my_qr_code_tab_title">Afficher mon QR code</string>
<string name="activity_qr_code_view_scan_qr_code_tab_title">Scanner le QR Code</string>
<string name="activity_qr_code_view_scan_qr_code_explanation">Scannez le QR code d\'un autre utilisateur pour démarrer une session</string>
<string name="fragment_view_my_qr_code_title">Scannez-moi</string>
<string name="fragment_view_my_qr_code_explanation">Ceci est votre code QR. Les autres utilisateurs peuvent le scanner pour démarrer une session avec vous.</string>
<string name="fragment_view_my_qr_code_share_title">Partager le code QR</string>
<string name="fragment_view_my_qr_code_explanation">Ceci est votre QR code. Les autres utilisateurs peuvent le scanner pour démarrer une session avec vous.</string>
<string name="fragment_view_my_qr_code_share_title">Partager le QR code</string>
<string name="fragment_contact_selection_contacts_title">Contacts</string>
<string name="fragment_contact_selection_closed_groups_title">Groupes privés</string>
<string name="fragment_contact_selection_open_groups_title">Groupes publics</string>
@@ -664,8 +706,8 @@
<string name="activity_link_device_skip_prompt">Cela prend un certain temps, voulez-vous passer ?</string>
<string name="activity_link_device_link_device">Relier un appareil</string>
<string name="activity_link_device_recovery_phrase">Phrase de récupération</string>
<string name="activity_link_device_scan_qr_code">Scannez le code QR</string>
<string name="activity_link_device_qr_message">Allez dans Paramètres → Phrase de récupération sur votre autre appareil pour afficher votre code QR.</string>
<string name="activity_link_device_scan_qr_code">Scanner le QR Code</string>
<string name="activity_link_device_qr_message">Allez dans Paramètres → Phrase de récupération sur votre autre appareil pour afficher votre QR code.</string>
<string name="activity_join_public_chat_join_rooms">Ou rejoignez l\'un(e) de ceux-ci…</string>
<string name="activity_pn_mode_message_notifications">Notifications de message</string>
<string name="activity_pn_mode_explanation">Session peut vous avertir de la présence de nouveaux messages de deux façons.</string>
@@ -694,6 +736,7 @@
<string name="dialog_download_explanation">Êtes-vous sûr de vouloir télécharger le média envoyé par %s ?</string>
<string name="dialog_download_button_title">Télécharger</string>
<string name="activity_conversation_blocked_banner_text">%s est bloqué. Débloquer ?</string>
<string name="activity_conversation_block_user">Bloquer l\'utilisateur</string>
<string name="activity_conversation_attachment_prep_failed">La préparation de la pièce jointe pour l\'envoi a échoué.</string>
<string name="media">Médias</string>
<string name="UntrustedAttachmentView_download_attachment">Touchez pour télécharger %s</string>
@@ -711,6 +754,116 @@
<string name="activity_settings_support">Journal de débogage</string>
<string name="dialog_share_logs_title">Partager les logs</string>
<string name="dialog_share_logs_explanation">Voulez-vous exporter les logs de votre application pour pouvoir partager pour le dépannage ?</string>
<string name="conversation_pin">Code pin</string>
<string name="conversation_pin">Épingler</string>
<string name="conversation_unpin">Désépingler</string>
<string name="mark_all_as_read">Tout marquer comme lu</string>
<string name="global_search_contacts_groups">Contacts et Groupes</string>
<string name="global_search_messages">Messages</string>
<string name="activity_message_requests_title">Demandes de message</string>
<string name="message_requests_send_notice">Envoyer un message à cet utilisateur acceptera automatiquement sa demande de message et révélera votre ID de session.</string>
<string name="accept">Accepter</string>
<string name="decline">Refuser</string>
<string name="message_requests_clear_all">Effacer tout</string>
<string name="message_requests_decline_message">Êtes-vous sûr de vouloir refuser cette demande de message ?</string>
<string name="message_requests_block_message">Êtes-vous sûr de vouloir supprimer cette demande de message ?</string>
<string name="message_requests_deleted">Demande de message supprimée</string>
<string name="message_requests_clear_all_message">Êtes-vous sûr de vouloir supprimer toutes les demandes de message ?</string>
<string name="message_requests_cleared">Demandes de message supprimées</string>
<string name="message_requests_accepted">Votre demande de message a été acceptée.</string>
<string name="message_requests_pending">Votre demande de message est en attente.</string>
<string name="message_request_empty_state_message">Aucune demande de message en attente</string>
<string name="NewConversationButton_SessionTooltip">Message privé</string>
<string name="NewConversationButton_ClosedGroupTooltip">Groupes privés</string>
<string name="NewConversationButton_OpenGroupTooltip">Groupe public</string>
<string name="message_requests_notification">Vous avez une nouvelle demande de message</string>
<string name="CallNotificationBuilder_connecting">Connexion…</string>
<string name="NotificationBarManager__incoming_signal_call">Appel entrant</string>
<string name="NotificationBarManager__deny_call">Refuser lappel</string>
<string name="NotificationBarManager__answer_call">Répondre à lappel</string>
<string name="NotificationBarManager_call_in_progress">Appel en cours</string>
<string name="NotificationBarManager__cancel_call">Annuler lappel</string>
<string name="NotificationBarManager__establishing_signal_call">Établissement de l\'appel</string>
<string name="NotificationBarManager__end_call">Raccrocher</string>
<string name="accept_call">Accepter l\'appel</string>
<string name="decline_call">Refuser l\'appel</string>
<string name="preferences__voice_video_calls">Appels vocaux et vidéos</string>
<string name="preferences__calls_beta">Appels (Bêta)</string>
<string name="preferences__allow_access_voice_video">Active les appels vocaux et vidéo vers et depuis d\'autres utilisateurs.</string>
<string name="dialog_voice_video_title">Appels vocaux / vidéo</string>
<string name="dialog_voice_video_message">La version actuelle des appels vocaux/vidéo exposera votre adresse IP aux serveurs de la Fondation Oxen et aux utilisateurs appelés</string>
<string name="CallNotificationBuilder_first_call_title">Appel Manqué</string>
<string name="CallNotificationBuilder_first_call_message">Vous avez manqué un appel car vous devez activer la permission « Appels vocaux et vidéo » dans les paramètres de confidentialité.</string>
<string name="WebRtcCallActivity_Session_Call">Appel Session</string>
<string name="WebRtcCallActivity_Reconnecting">Reconnexion…</string>
<string name="CallNotificationBuilder_system_notification_title">Notifications</string>
<string name="CallNotificationBuilder_system_notification_message">Les notifications désactivées vous empêcheront de recevoir des appels, aller dans les paramètres de notification de session?</string>
<string name="dismiss">Rejeter</string>
<string name="activity_settings_conversations_button_title">Conversations</string>
<string name="activity_settings_message_appearance_button_title">Apparence</string>
<string name="activity_settings_help_button">Aide</string>
<string name="activity_appearance_themes_category">Thèmes</string>
<string name="ocean_dark_theme_name">Océan sombre</string>
<string name="classic_dark_theme_name">Sombre classique</string>
<string name="ocean_light_theme_name">Océan lumineux</string>
<string name="classic_light_theme_name">Clair classique</string>
<string name="activity_appearance_primary_color_category">Couleur principale</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__this_message">Ce message</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__recently_used">Fréquemment Utilisés</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__smileys_and_people">Émoticônes et personnes</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__nature" comment="Heading for an emoji list's category">Nature</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__food" comment="Heading for an emoji list's category">Nourriture</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__activities">Activités</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__places" comment="Heading for an emoji list's category">Voyage</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__objects">Objets</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__symbols">Symboles</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__flags">Drapeaux</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__emoticons">Emoticônes</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__no_results_found">Aucun résultat trouvé</string>
<!-- ReactionsBottomSheetDialogFragment -->
<string name="ReactionsBottomSheetDialogFragment_all">Tous &#183; %1$d</string>
<!-- ReactionsConversationView -->
<string name="ReactionsConversationView_plus">+%1$d</string>
<!-- ReactionsRecipientAdapter -->
<string name="ReactionsRecipientAdapter_you">Vous</string>
<string name="reaction_notification">%1$s a réagi à un message %2$s</string>
<string name="ReactionsConversationView_show_less">Masquer les détails</string>
<string name="KeyboardPagerFragment_search_emoji">Rechercher un émoticône</string>
<string name="KeyboardPagerfragment_back_to_emoji">Retour à l\'émoticône</string>
<string name="KeyboardPagerfragment_clear_search_entry">Effacer la recherche</string>
<string name="activity_appearance_follow_system_category">Thème sombre automatique</string>
<string name="activity_appearance_follow_system_explanation">Faire correspondre aux paramètres systèmes</string>
<string name="go_to_device_notification_settings">Accédez aux paramètres de notifications de l\'appareil</string>
<string name="blocked_contacts_title">Contacts bloqués</string>
<string name="blocked_contacts_empty_state">Vous n\'avez aucun contact bloqué</string>
<string name="Unblock_dialog__title_single">Débloquer %s</string>
<string name="Unblock_dialog__title_multiple">Débloquer les utilisateurs</string>
<string name="Unblock_dialog__message">Êtes-vous sûr·e de vouloir débloquer %s ?</string>
<plurals name="Unblock_dialog__message_multiple_overflow">
<item quantity="one">et %d autre</item>
<item quantity="other">et %d autres</item>
</plurals>
<plurals name="ReactionsRecipientAdapter_other_reactors">
<item quantity="one">Et %1$d autre a réagi %2$s à ce message</item>
<item quantity="other">Et %1$d autres ont réagi %2$s à ce message</item>
</plurals>
<string name="dialog_new_conversation_title">Nouvelle conversation</string>
<string name="dialog_new_message_title">Nouveau message</string>
<string name="activity_create_group_title">Créer un groupe</string>
<string name="dialog_join_community_title">Rejoindre la communauté</string>
<string name="new_conversation_contacts_title">Contacts</string>
<string name="new_conversation_unknown_contacts_section_title">Inconnu·e</string>
<string name="fragment_enter_public_key_prompt">Commencez une nouvelle conversation en entrant l\'ID Session de quelqu\'un ou en lui partageant votre ID Session.</string>
<string name="activity_create_group_create_button_title">Créer</string>
<string name="search_contacts_hint">Rechercher parmi les contacts</string>
<string name="activity_join_public_chat_enter_community_url_tab_title">URL de la communauté</string>
<string name="fragment_enter_community_url_edit_text_hint">Entrez l\'URL de la communauté</string>
<string name="fragment_enter_community_url_join_button_title">Rejoindre</string>
<string name="new_conversation_dialog_back_button_content_description">Revenir en arrière</string>
<string name="new_conversation_dialog_close_button_content_description">Fermer la fenêtre</string>
<string name="ErrorNotifier_migration">Échec de la mise à jour de la base de données</string>
<string name="ErrorNotifier_migration_downgrade">Veuillez contacter le support pour signaler l\'erreur.</string>
<string name="delivery_status_sending">Envoi</string>
<string name="delivery_status_read">Lu</string>
<string name="delivery_status_sent">Envoyé</string>
<string name="delivery_status_failed">Échec denvoi</string>
</resources>

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Session</string>
<string name="yes">Oui</string>
<string name="no">Non</string>
<string name="delete">Supprimer</string>
<string name="ban">Bannir</string>
<string name="please_wait">Veuillez patienter…</string>
<string name="save">Enregistrer</string>
<string name="note_to_self">Note à mon intention</string>
<string name="version_s">Version %s</string>
@@ -19,7 +19,7 @@
</plurals>
<string name="ApplicationPreferencesActivity_delete_all_old_messages_now">Supprimer tous les anciens messages maintenant?</string>
<plurals name="ApplicationPreferencesActivity_this_will_immediately_trim_all_conversations_to_the_d_most_recent_messages">
<item quantity="one">Cela va immédiatement réduire toutes les conversations pour quil ne reste que le message le plus récent.</item>
<item quantity="one">Cela réduira immédiatement toutes les conversations au message le plus récent.</item>
<item quantity="other">Cela réduira immédiatement toutes les conversations aux %d messages les plus récents.</item>
</plurals>
<string name="ApplicationPreferencesActivity_delete">Supprimer</string>
@@ -63,6 +63,7 @@
<string name="ConversationActivity_muted_until_date">Son désactivé jusqu\'à %1$s</string>
<string name="ConversationActivity_muted_forever">En sourdine</string>
<string name="ConversationActivity_member_count">%1$d membres</string>
<string name="ConversationActivity_active_member_count">%1$d membres actifs</string>
<string name="ConversationActivity_open_group_guidelines">Règles de la communauté</string>
<string name="ConversationActivity_invalid_recipient">Le destinataire est invalide!</string>
<string name="ConversationActivity_added_to_home_screen">Ajouté à lécran daccueil</string>
@@ -82,7 +83,9 @@
<string name="ConversationActivity_to_send_photos_and_video_allow_signal_access_to_storage">Session a besoin d\'un accès au stockage pour envoyer des photos et des vidéos.</string>
<string name="ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video">Session a besoin de lautorisation Appareil photo afin de prendre des photos ou des vidéos, mais elle a été refusée définitivement. Veuillez accéder au menu des paramètres des applis, sélectionner Autorisations et activer Appareil photo.</string>
<string name="ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video">Session a besoin de lautorisation Appareil photo pour prendre des photos ou des vidéos</string>
<string name="ConversationActivity_search_position">%1$d de %2$d</string>
<string name="ConversationActivity_search_position">%1$d sur %2$d</string>
<string name="ConversationActivity_call_title">Autorisations d\'appel requises</string>
<string name="ConversationActivity_call_prompt">Vous pouvez activer la permission \"Appels vocaux et vidéo\" dans les paramètres de confidentialité.</string>
<!-- ConversationFragment -->
<plurals name="ConversationFragment_delete_selected_messages">
<item quantity="one">Supprimer le message sélectionné?</item>
@@ -99,13 +102,17 @@
<item quantity="other">Lenregistrement des %1$d médias dans la mémoire permettra à nimporte quelles autres applis de votre appareil dy accéder.\n\nContinuer?</item>
</plurals>
<plurals name="ConversationFragment_error_while_saving_attachments_to_sd_card">
<item quantity="one">Erreur denregistrement de la pièce jointe dans la mémoire!</item>
<item quantity="one">Erreur lors de lenregistrement de la pièce jointe dans la mémoire!</item>
<item quantity="other">Erreur denregistrement des pièces jointes dans la mémoire!</item>
</plurals>
<plurals name="ConversationFragment_saving_n_attachments">
<item quantity="one">Enregistrement de la pièce jointe</item>
<item quantity="other">Enregistrement de %1$d pièces jointes</item>
</plurals>
<plurals name="ConversationFragment_saving_n_attachments_to_sd_card">
<item quantity="one">Enregistrement de la pièce jointe dans la mémoire…</item>
<item quantity="other">Enregistrement de %1$d pièces jointes dans la mémoire…</item>
</plurals>
<!-- CreateProfileActivity -->
<string name="CreateProfileActivity_profile_photo">Photo de profil</string>
<!-- CustomDefaultPreference -->
@@ -156,7 +163,7 @@
<!-- MediaPickerActivity -->
<string name="MediaPickerActivity_send_to">Envoyer à %s</string>
<!-- MediaSendActivity -->
<string name="MediaSendActivity_add_a_caption">Ajouter un légende</string>
<string name="MediaSendActivity_add_a_caption">Ajouter une légende...</string>
<string name="MediaSendActivity_an_item_was_removed_because_it_exceeded_the_size_limit">Un élément a été supprimé, car il dépassait la taille limite</string>
<string name="MediaSendActivity_camera_unavailable">Lappareil photo nest pas disponible</string>
<string name="MediaSendActivity_message_to_s">Message à %s</string>
@@ -191,6 +198,7 @@
<string name="Slide_video">Vidéo</string>
<!-- SmsMessageRecord -->
<string name="SmsMessageRecord_received_corrupted_key_exchange_message">Vous avez reçu un message déchange de clés corrompu!</string>
<string name="SmsMessageRecord_received_key_exchange_message_for_invalid_protocol_version"> Le message d\'échange de clé reçu est pour une version du protocole invalide. </string>
<string name="SmsMessageRecord_received_message_with_new_safety_number_tap_to_process">Vous avez reçu un message avec un nouveau numéro de sécurité. Touchez pour le traiter et lafficher.</string>
<string name="SmsMessageRecord_secure_session_reset">Vous avez réinitialisé la session sécurisée.</string>
<string name="SmsMessageRecord_secure_session_reset_s">%s a réinitialisé la session sécurisée.</string>
@@ -201,7 +209,7 @@
<string name="ThreadRecord_secure_session_reset">La session sécurisée a été réinitialisée.</string>
<string name="ThreadRecord_draft">Brouillon :</string>
<string name="ThreadRecord_called">Vous avez appelé</string>
<string name="ThreadRecord_called_you">Vous a appelé</string>
<string name="ThreadRecord_called_you">Vous a appelé·e</string>
<string name="ThreadRecord_missed_call">Appel manqué</string>
<string name="ThreadRecord_media_message">Message multimédia</string>
<string name="ThreadRecord_s_is_on_signal">%s est sur Session!</string>
@@ -304,7 +312,7 @@
<string name="conversation_activity__send">Envoyer</string>
<string name="conversation_activity__compose_description">Rédaction dun message</string>
<string name="conversation_activity__emoji_toggle_description">Afficher, masquer le clavier des émojis</string>
<string name="conversation_activity__attachment_thumbnail">Imagette de pièces jointes</string>
<string name="conversation_activity__attachment_thumbnail">Vignette de pièce jointe</string>
<string name="conversation_activity__quick_attachment_drawer_toggle_camera_description">Afficher, masquer le tiroir permettant de lancer lappareil photo à basse résolution</string>
<string name="conversation_activity__quick_attachment_drawer_record_and_send_audio_description">Enregistrer et envoyer une pièce jointe audio</string>
<string name="conversation_activity__quick_attachment_drawer_lock_record_description">Verrouiller lenregistrement de pièces jointes audio</string>
@@ -378,9 +386,9 @@
<string name="arrays__settings_default">Valeur par défaut</string>
<string name="arrays__enabled">Activé</string>
<string name="arrays__disabled">Désactivé</string>
<string name="arrays__name_and_message">Nom et message</string>
<string name="arrays__name_only">Nom seulement</string>
<string name="arrays__no_name_or_message">Aucun nom ni message</string>
<string name="arrays__name_and_message">Nom et Contenu</string>
<string name="arrays__name_only">Nom uniquement</string>
<string name="arrays__no_name_or_message">Aucun nom ni contenu</string>
<string name="arrays__images">Images</string>
<string name="arrays__audio">Son</string>
<string name="arrays__video">Vidéo</string>
@@ -398,9 +406,14 @@
<item quantity="other">%d heures</item>
</plurals>
<!-- preferences.xml -->
<string name="preferences__pref_enter_sends_title">La touche Entrée envoie</string>
<string name="preferences__send_link_previews">Envoyer des aperçus de liens</string>
<string name="preferences__previews_are_supported_for">Les aperçus sont pris en charge pour les liens Imgur, Instagram, Pinterest, Reddit et YouTube</string>
<string name="preferences__pref_enter_sends_title">Envoyer avec bouton Entrée</string>
<string name="preferences__pref_enter_sends_summary">Appuyer sur la touche Entrée enverra un message au lieu de commencer une nouvelle ligne.</string>
<string name="preferences__send_link_previews">Envoyer les aperçus des liens</string>
<string name="preferences__link_previews">Aperçus des liens</string>
<string name="preferences__link_previews_summary">Générer les aperçus des liens pour les URLs supportés.</string>
<string name="preferences__pref_autoplay_audio_category">Messages audio</string>
<string name="preferences__pref_autoplay_audio_title">Lire automatiquement les messages audios</string>
<string name="preferences__pref_autoplay_audio_summary">Lire automatiquement les messages audio consécutifs.</string>
<string name="preferences__screen_security">Sécurité de lécran</string>
<string name="preferences__disable_screen_security_to_allow_screen_shots">Bloquer les captures décran dans la liste des récents et dans lappli</string>
<string name="preferences__notifications">Notifications</string>
@@ -408,6 +421,7 @@
<string name="preferences__led_color_unknown">Inconnue</string>
<string name="preferences__pref_led_blink_title">Rythme de clignotement de la DEL</string>
<string name="preferences__sound">Son</string>
<string name="preferences__in_app_sounds">Son à l\'ouverture de l\'application</string>
<string name="preferences__silent">Silencieux</string>
<string name="preferences__repeat_alerts">Répéter les alertes</string>
<string name="preferences__never">Jamais</string>
@@ -436,18 +450,27 @@
<string name="preferences__default">Valeur par défaut</string>
<string name="preferences__incognito_keyboard">Clavier incognito</string>
<string name="preferences__read_receipts">Accusés de lecture</string>
<string name="preferences__read_receipts_summary">Envoyer des accusés de lecture dans les conversations individuelles.</string>
<string name="preferences__typing_indicators">Indicateurs de saisie</string>
<string name="preferences__if_typing_indicators_are_disabled_you_wont_be_able_to_see_typing_indicators">Si les indicateurs de saisie sont désactivés, vous ne serez pas en mesure de voir les indicateurs de saisie des autres.</string>
<string name="preferences__typing_indicators_summary">Voir et envoyer les indicateurs de saisie dans les conversations un à un.</string>
<string name="preferences__request_keyboard_to_disable_personalized_learning">Demander au clavier de désactiver lapprentissage personnalisé</string>
<string name="preferences__light_theme">Clair</string>
<string name="preferences__dark_theme">Sombre</string>
<string name="preferences_chats__message_trimming">Élagage des messages</string>
<string name="preferences_chats__message_trimming_title">Raccourcir les communautés</string>
<string name="preferences_chats__message_trimming_summary">Supprimer des messages de plus de 6 mois dans des communautés qui ont plus de 2 000 messages.</string>
<string name="preferences_advanced__use_system_emoji">Utiliser les émojis du système</string>
<string name="preferences_advanced__disable_signal_built_in_emoji_support">Désactiver la prise en charge des émojis intégrés à Session</string>
<string name="preferences_app_protection__screen_security">Sécurité d\'écran</string>
<string name="preferences_chats__chats">Conversations</string>
<string name="preferences_notifications__messages">Messages</string>
<string name="preferences_notifications__in_chat_sounds">Sons des conversations</string>
<string name="preferences_notifications__content">Contenu de la notification</string>
<string name="preferences_notifications__content_message">Afficher :</string>
<string name="preferences_notifications__summary">Informations affichées dans les notifications.</string>
<string name="preferences_notifications__priority">Priorité</string>
<string name="preferences_app_protection__screenshot_notifications">Notifications de capture d\'écran</string>
<string name="preferences_app_protected__screenshot_notifications_summary">Recevoir une notification lorsqu\'un contact prend une capture d\'écran d\'une conversation individuelle.</string>
<!-- **************************************** -->
<!-- menus -->
<!-- **************************************** -->
@@ -460,7 +483,10 @@
<string name="conversation_context__menu_ban_user">Bannir l\'utilisateur</string>
<string name="conversation_context__menu_ban_and_delete_all">Bannir et supprimer tout</string>
<string name="conversation_context__menu_resend_message">Renvoyer le message</string>
<string name="conversation_context__menu_reply">Répondre</string>
<string name="conversation_context__menu_reply_to_message">Répondre au message</string>
<string name="conversation_context__menu_call">Appeler</string>
<string name="conversation_context__menu_select">Sélectionner</string>
<!-- conversation_context_image -->
<string name="conversation_context_image__save_attachment">Enregistrer la pièce jointe</string>
<!-- conversation_expiring_off -->
@@ -513,8 +539,8 @@
<string name="LocalBackupJob_creating_backup">Création de la sauvegarde…</string>
<string name="ProgressPreference_d_messages_so_far">%d messages pour linstant</string>
<string name="BackupUtil_never">Jamais</string>
<string name="preferences_app_protection__screen_lock">Verrouillage de lécran</string>
<string name="preferences_app_protection__lock_signal_access_with_android_screen_lock_or_fingerprint">Verrouiller laccès à Session avec le verrouillage de lécran dAndroid ou une empreinte</string>
<string name="preferences_app_protection__screen_lock">Verrouiller Session</string>
<string name="preferences_app_protection__lock_signal_access_with_android_screen_lock_or_fingerprint">Nécessite une empreinte digitale, un code PIN, un schéma ou un mot de passe pour déverrouiller Session.</string>
<string name="preferences_app_protection__screen_lock_inactivity_timeout">Délai dinactivité avant verrouillage de lécran</string>
<string name="AppProtectionPreferenceFragment_none">Aucune</string>
<!-- Conversation activity -->
@@ -522,6 +548,7 @@
<!-- Session -->
<string name="continue_2">Continuer</string>
<string name="copy">Copier</string>
<string name="close">Fermer</string>
<string name="invalid_url">URL non valide</string>
<string name="copied_to_clipboard">Copié dans le presse-papier</string>
<string name="next">Suivant</string>
@@ -573,12 +600,12 @@
<string name="activity_path_resolving_progress">Contact en cours…</string>
<string name="activity_create_private_chat_title">Nouvelle Session</string>
<string name="activity_create_private_chat_enter_session_id_tab_title">Saisir un Session ID</string>
<string name="activity_create_private_chat_scan_qr_code_tab_title">Scanner un Code QR</string>
<string name="activity_create_private_chat_scan_qr_code_explanation">Scannez le code QR d\'un utilisateur pour démarrer une session. Les codes QR peuvent se trouver en touchant l\'icône du code QR dans les paramètres du compte.</string>
<string name="activity_create_private_chat_scan_qr_code_tab_title">Scanner un QR Code</string>
<string name="activity_create_private_chat_scan_qr_code_explanation">Scannez le QR code d\'un utilisateur pour démarrer une session. Les QR codes peuvent se trouver en touchant l\'icône du QR code dans les paramètres du compte.</string>
<string name="fragment_enter_public_key_edit_text_hint">Entrer un Session ID ou un nom ONS</string>
<string name="fragment_enter_public_key_explanation">Les utilisateurs peuvent partager leur Session ID depuis les paramètres du compte ou en utilisant le code QR.</string>
<string name="fragment_enter_public_key_error_message">Veuillez vérifier le Session ID ou le nom ONS et réessayer.</string>
<string name="fragment_scan_qr_code_camera_access_explanation">Session a besoin d\'accéder à l\'appareil photo pour scanner les codes QR</string>
<string name="fragment_scan_qr_code_camera_access_explanation">Session a besoin d\'accéder à l\'appareil photo pour scanner les QR codes</string>
<string name="fragment_scan_qr_code_grant_camera_access_button_title">Autoriser l\'accès</string>
<string name="activity_create_closed_group_title">Nouveau groupe privé</string>
<string name="activity_create_closed_group_edit_text_hint">Saisissez un nom de groupe</string>
@@ -591,7 +618,7 @@
<string name="activity_join_public_chat_title">Joindre un groupe public</string>
<string name="activity_join_public_chat_error">Impossible de rejoindre le groupe</string>
<string name="activity_join_public_chat_enter_group_url_tab_title">URL du groupe public</string>
<string name="activity_join_public_chat_scan_qr_code_tab_title">Scannez le code QR</string>
<string name="activity_join_public_chat_scan_qr_code_tab_title">Scanner le QR Code</string>
<string name="activity_join_public_chat_scan_qr_code_explanation">Scannez le code QR du groupe public que vous souhaitez rejoindre</string>
<string name="fragment_enter_chat_url_edit_text_hint">Saisissez une URL de groupe public</string>
<string name="activity_settings_title">Paramètres</string>
@@ -600,6 +627,7 @@
<string name="activity_settings_display_name_too_long_error">Veuillez choisir un nom d\'utilisateur plus court</string>
<string name="activity_settings_privacy_button_title">Confidentialité</string>
<string name="activity_settings_notifications_button_title">Notifications</string>
<string name="activity_settings_message_requests_button_title">Demandes de message</string>
<string name="activity_settings_chats_button_title">Conversations</string>
<string name="activity_settings_devices_button_title">Appareils reliés</string>
<string name="activity_settings_invite_button_title">Inviter un ami</string>
@@ -612,25 +640,39 @@
<string name="activity_notification_settings_style_section_title">Style de notification</string>
<string name="activity_notification_settings_content_section_title">Contenu de notification</string>
<string name="activity_privacy_settings_title">Confidentialité</string>
<string name="activity_conversations_settings_title">Conversations</string>
<string name="activity_help_settings_title">Aide</string>
<string name="activity_help_settings__report_bug_title">Signaler un bug</string>
<string name="activity_help_settings__report_bug_summary">Exportez vos logs, puis télécharger le fichier au service d\'aide de Session.</string>
<string name="activity_help_settings__translate_session">Traduire Session</string>
<string name="activity_help_settings__feedback">Nous aimerions avoir votre avis</string>
<string name="activity_help_settings__faq">FAQ</string>
<string name="activity_help_settings__support">Assistance</string>
<string name="activity_help_settings__export_logs">Exporter les journaux</string>
<string name="preferences_notifications_strategy_category_title">Stratégie de notification</string>
<string name="preferences_notifications_strategy_category_fast_mode_title">Utiliser le Mode Rapide</string>
<string name="preferences_notifications_strategy_category_fast_mode_summary">Vous serez averti de nouveaux messages de manière fiable et immédiate en utilisant les serveurs de notification de Google.</string>
<string name="fragment_device_list_bottom_sheet_change_name_button_title">Modifier le nom</string>
<string name="fragment_device_list_bottom_sheet_unlink_device_button_title">Déconnecter l\'appareil</string>
<string name="dialog_seed_title">Votre phrase de récupération</string>
<string name="dialog_seed_explanation">Ceci est votre phrase de récupération. Elle vous permet de restaurer ou migrer votre Session ID vers un nouvel appareil.</string>
<string name="dialog_seed_explanation">Vous pouvez utiliser votre phrase de récupération pour restaurer votre compte ou relier un appareil.</string>
<string name="dialog_clear_all_data_title">Effacer toutes les données</string>
<string name="dialog_clear_all_data_explanation">Cela supprimera définitivement vos messages, vos sessions et vos contacts.</string>
<string name="dialog_clear_all_data_network_explanation">Souhaitez-vous effacer seulement cet appareil ou supprimer l\'ensemble de votre compte ?</string>
<string name="dialog_clear_all_data_message">Cela supprimera définitivement vos messages, sessions et contacts. Voulez-vous uniquement effacer cet appareil ou supprimer l\'intégralité de votre compte ?</string>
<string name="dialog_clear_all_data_clear_device_only">Effacer l\'appareil uniquement</string>
<string name="dialog_clear_all_data_clear_device_and_network">Effacer l\'appareil et le réseau</string>
<string name="dialog_clear_all_data_clear_device_and_network_confirmation">Êtes-vous sûr de vouloir supprimer vos données du réseau ? Si vous continuez, vous ne pourrez pas restaurer vos messages ou vos contacts.</string>
<string name="dialog_clear_all_data_clear">Effacer</string>
<string name="dialog_clear_all_data_local_only">Effacer seulement</string>
<string name="dialog_clear_all_data_clear_network">Compte complet</string>
<string name="activity_qr_code_title">Code QR</string>
<string name="activity_qr_code_view_my_qr_code_tab_title">Afficher mon code QR</string>
<string name="activity_qr_code_view_scan_qr_code_tab_title">Scanner le code QR</string>
<string name="activity_qr_code_view_scan_qr_code_explanation">Scannez le code QR d\'un autre utilisateur pour démarrer une session</string>
<string name="activity_qr_code_title">QR Code</string>
<string name="activity_qr_code_view_my_qr_code_tab_title">Afficher mon QR code</string>
<string name="activity_qr_code_view_scan_qr_code_tab_title">Scanner le QR Code</string>
<string name="activity_qr_code_view_scan_qr_code_explanation">Scannez le QR code d\'un autre utilisateur pour démarrer une session</string>
<string name="fragment_view_my_qr_code_title">Scannez-moi</string>
<string name="fragment_view_my_qr_code_explanation">Ceci est votre code QR. Les autres utilisateurs peuvent le scanner pour démarrer une session avec vous.</string>
<string name="fragment_view_my_qr_code_share_title">Partager le code QR</string>
<string name="fragment_view_my_qr_code_explanation">Ceci est votre QR code. Les autres utilisateurs peuvent le scanner pour démarrer une session avec vous.</string>
<string name="fragment_view_my_qr_code_share_title">Partager le QR code</string>
<string name="fragment_contact_selection_contacts_title">Contacts</string>
<string name="fragment_contact_selection_closed_groups_title">Groupes privés</string>
<string name="fragment_contact_selection_open_groups_title">Groupes publics</string>
@@ -664,8 +706,8 @@
<string name="activity_link_device_skip_prompt">Cela prend un certain temps, voulez-vous passer ?</string>
<string name="activity_link_device_link_device">Relier un appareil</string>
<string name="activity_link_device_recovery_phrase">Phrase de récupération</string>
<string name="activity_link_device_scan_qr_code">Scannez le code QR</string>
<string name="activity_link_device_qr_message">Allez dans Paramètres → Phrase de récupération sur votre autre appareil pour afficher votre code QR.</string>
<string name="activity_link_device_scan_qr_code">Scanner le QR Code</string>
<string name="activity_link_device_qr_message">Allez dans Paramètres → Phrase de récupération sur votre autre appareil pour afficher votre QR code.</string>
<string name="activity_join_public_chat_join_rooms">Ou rejoignez l\'un(e) de ceux-ci…</string>
<string name="activity_pn_mode_message_notifications">Notifications de message</string>
<string name="activity_pn_mode_explanation">Session peut vous avertir de la présence de nouveaux messages de deux façons.</string>
@@ -694,6 +736,7 @@
<string name="dialog_download_explanation">Êtes-vous sûr de vouloir télécharger le média envoyé par %s ?</string>
<string name="dialog_download_button_title">Télécharger</string>
<string name="activity_conversation_blocked_banner_text">%s est bloqué. Débloquer ?</string>
<string name="activity_conversation_block_user">Bloquer l\'utilisateur</string>
<string name="activity_conversation_attachment_prep_failed">La préparation de la pièce jointe pour l\'envoi a échoué.</string>
<string name="media">Médias</string>
<string name="UntrustedAttachmentView_download_attachment">Touchez pour télécharger %s</string>
@@ -711,6 +754,116 @@
<string name="activity_settings_support">Journal de débogage</string>
<string name="dialog_share_logs_title">Partager les logs</string>
<string name="dialog_share_logs_explanation">Voulez-vous exporter les logs de votre application pour pouvoir partager pour le dépannage ?</string>
<string name="conversation_pin">Code pin</string>
<string name="conversation_pin">Épingler</string>
<string name="conversation_unpin">Désépingler</string>
<string name="mark_all_as_read">Tout marquer comme lu</string>
<string name="global_search_contacts_groups">Contacts et Groupes</string>
<string name="global_search_messages">Messages</string>
<string name="activity_message_requests_title">Demandes de message</string>
<string name="message_requests_send_notice">Envoyer un message à cet utilisateur acceptera automatiquement sa demande de message et révélera votre ID de session.</string>
<string name="accept">Accepter</string>
<string name="decline">Refuser</string>
<string name="message_requests_clear_all">Effacer tout</string>
<string name="message_requests_decline_message">Êtes-vous sûr de vouloir refuser cette demande de message ?</string>
<string name="message_requests_block_message">Êtes-vous sûr de vouloir supprimer cette demande de message ?</string>
<string name="message_requests_deleted">Demande de message supprimée</string>
<string name="message_requests_clear_all_message">Êtes-vous sûr de vouloir supprimer toutes les demandes de message ?</string>
<string name="message_requests_cleared">Demandes de message supprimées</string>
<string name="message_requests_accepted">Votre demande de message a été acceptée.</string>
<string name="message_requests_pending">Votre demande de message est en attente.</string>
<string name="message_request_empty_state_message">Aucune demande de message en attente</string>
<string name="NewConversationButton_SessionTooltip">Message privé</string>
<string name="NewConversationButton_ClosedGroupTooltip">Groupes privés</string>
<string name="NewConversationButton_OpenGroupTooltip">Groupe public</string>
<string name="message_requests_notification">Vous avez une nouvelle demande de message</string>
<string name="CallNotificationBuilder_connecting">Connexion…</string>
<string name="NotificationBarManager__incoming_signal_call">Appel entrant</string>
<string name="NotificationBarManager__deny_call">Refuser lappel</string>
<string name="NotificationBarManager__answer_call">Répondre à lappel</string>
<string name="NotificationBarManager_call_in_progress">Appel en cours</string>
<string name="NotificationBarManager__cancel_call">Annuler lappel</string>
<string name="NotificationBarManager__establishing_signal_call">Établissement de l\'appel</string>
<string name="NotificationBarManager__end_call">Raccrocher</string>
<string name="accept_call">Accepter l\'appel</string>
<string name="decline_call">Refuser l\'appel</string>
<string name="preferences__voice_video_calls">Appels vocaux et vidéos</string>
<string name="preferences__calls_beta">Appels (Bêta)</string>
<string name="preferences__allow_access_voice_video">Active les appels vocaux et vidéo vers et depuis d\'autres utilisateurs.</string>
<string name="dialog_voice_video_title">Appels vocaux / vidéo</string>
<string name="dialog_voice_video_message">La version actuelle des appels vocaux/vidéo exposera votre adresse IP aux serveurs de la Fondation Oxen et aux utilisateurs appelés</string>
<string name="CallNotificationBuilder_first_call_title">Appel Manqué</string>
<string name="CallNotificationBuilder_first_call_message">Vous avez manqué un appel car vous devez activer la permission « Appels vocaux et vidéo » dans les paramètres de confidentialité.</string>
<string name="WebRtcCallActivity_Session_Call">Appel Session</string>
<string name="WebRtcCallActivity_Reconnecting">Reconnexion…</string>
<string name="CallNotificationBuilder_system_notification_title">Notifications</string>
<string name="CallNotificationBuilder_system_notification_message">Les notifications désactivées vous empêcheront de recevoir des appels, aller dans les paramètres de notification de session?</string>
<string name="dismiss">Rejeter</string>
<string name="activity_settings_conversations_button_title">Conversations</string>
<string name="activity_settings_message_appearance_button_title">Apparence</string>
<string name="activity_settings_help_button">Aide</string>
<string name="activity_appearance_themes_category">Thèmes</string>
<string name="ocean_dark_theme_name">Océan sombre</string>
<string name="classic_dark_theme_name">Sombre classique</string>
<string name="ocean_light_theme_name">Océan lumineux</string>
<string name="classic_light_theme_name">Clair classique</string>
<string name="activity_appearance_primary_color_category">Couleur principale</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__this_message">Ce message</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__recently_used">Fréquemment Utilisés</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__smileys_and_people">Émoticônes et personnes</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__nature" comment="Heading for an emoji list's category">Nature</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__food" comment="Heading for an emoji list's category">Nourriture</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__activities">Activités</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__places" comment="Heading for an emoji list's category">Voyage</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__objects">Objets</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__symbols">Symboles</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__flags">Drapeaux</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__emoticons">Emoticônes</string>
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__no_results_found">Aucun résultat trouvé</string>
<!-- ReactionsBottomSheetDialogFragment -->
<string name="ReactionsBottomSheetDialogFragment_all">Tous &#183; %1$d</string>
<!-- ReactionsConversationView -->
<string name="ReactionsConversationView_plus">+%1$d</string>
<!-- ReactionsRecipientAdapter -->
<string name="ReactionsRecipientAdapter_you">Vous</string>
<string name="reaction_notification">%1$s a réagi à un message %2$s</string>
<string name="ReactionsConversationView_show_less">Masquer les détails</string>
<string name="KeyboardPagerFragment_search_emoji">Rechercher un émoticône</string>
<string name="KeyboardPagerfragment_back_to_emoji">Retour à l\'émoticône</string>
<string name="KeyboardPagerfragment_clear_search_entry">Effacer la recherche</string>
<string name="activity_appearance_follow_system_category">Thème sombre automatique</string>
<string name="activity_appearance_follow_system_explanation">Faire correspondre aux paramètres systèmes</string>
<string name="go_to_device_notification_settings">Accédez aux paramètres de notifications de l\'appareil</string>
<string name="blocked_contacts_title">Contacts bloqués</string>
<string name="blocked_contacts_empty_state">Vous n\'avez aucun contact bloqué</string>
<string name="Unblock_dialog__title_single">Débloquer %s</string>
<string name="Unblock_dialog__title_multiple">Débloquer les utilisateurs</string>
<string name="Unblock_dialog__message">Êtes-vous sûr·e de vouloir débloquer %s ?</string>
<plurals name="Unblock_dialog__message_multiple_overflow">
<item quantity="one">et %d autre</item>
<item quantity="other">et %d autres</item>
</plurals>
<plurals name="ReactionsRecipientAdapter_other_reactors">
<item quantity="one">Et %1$d autre a réagi %2$s à ce message</item>
<item quantity="other">Et %1$d autres ont réagi %2$s à ce message</item>
</plurals>
<string name="dialog_new_conversation_title">Nouvelle conversation</string>
<string name="dialog_new_message_title">Nouveau message</string>
<string name="activity_create_group_title">Créer un groupe</string>
<string name="dialog_join_community_title">Rejoindre la communauté</string>
<string name="new_conversation_contacts_title">Contacts</string>
<string name="new_conversation_unknown_contacts_section_title">Inconnu·e</string>
<string name="fragment_enter_public_key_prompt">Commencez une nouvelle conversation en entrant l\'ID Session de quelqu\'un ou en lui partageant votre ID Session.</string>
<string name="activity_create_group_create_button_title">Créer</string>
<string name="search_contacts_hint">Rechercher parmi les contacts</string>
<string name="activity_join_public_chat_enter_community_url_tab_title">URL de la communauté</string>
<string name="fragment_enter_community_url_edit_text_hint">Entrez l\'URL de la communauté</string>
<string name="fragment_enter_community_url_join_button_title">Rejoindre</string>
<string name="new_conversation_dialog_back_button_content_description">Revenir en arrière</string>
<string name="new_conversation_dialog_close_button_content_description">Fermer la fenêtre</string>
<string name="ErrorNotifier_migration">Échec de la mise à jour de la base de données</string>
<string name="ErrorNotifier_migration_downgrade">Veuillez contacter le support pour signaler l\'erreur.</string>
<string name="delivery_status_sending">Envoi</string>
<string name="delivery_status_read">Lu</string>
<string name="delivery_status_sent">Envoyé</string>
<string name="delivery_status_failed">Échec denvoi</string>
</resources>

View File

@@ -77,6 +77,7 @@
<string name="ConversationActivity_attachment_exceeds_size_limits">Attachment exceeds size limits for the type of message you\'re sending.</string>
<string name="ConversationActivity_unable_to_record_audio">Unable to record audio!</string>
<string name="ConversationActivity_there_is_no_app_available_to_handle_this_link_on_your_device">There is no app available to handle this link on your device.</string>
<string name="ConversationActivity_copy_open_group_url">Copy Community URL</string>
<string name="ConversationActivity_invite_to_open_group">Add members</string>
<string name="ConversationActivity_to_send_audio_messages_allow_signal_access_to_your_microphone">Session needs microphone access to send audio messages.</string>
<string name="ConversationActivity_signal_requires_the_microphone_permission_in_order_to_send_audio_messages">Session needs microphone access to send audio messages, but it has been permanently denied. Please continue to app settings, select \"Permissions\", and enable \"Microphone\".</string>

View File

@@ -1,281 +0,0 @@
package org.thoughtcrime.securesms.util
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.mockito.kotlin.KStubbing
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.database.GroupDatabase
import org.thoughtcrime.securesms.database.LokiAPIDatabase
import org.thoughtcrime.securesms.database.LokiMessageDatabase
import org.thoughtcrime.securesms.database.LokiThreadDatabase
import org.thoughtcrime.securesms.database.MmsDatabase
import org.thoughtcrime.securesms.database.SmsDatabase
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.groups.OpenGroupMigrator
import org.thoughtcrime.securesms.groups.OpenGroupMigrator.OpenGroupMapping
import org.thoughtcrime.securesms.groups.OpenGroupMigrator.roomStub
class OpenGroupMigrationTests {
companion object {
const val EXAMPLE_LEGACY_ENCODED_OPEN_GROUP = "__loki_public_chat_group__!687474703a2f2f3131362e3230332e37302e33332e6f78656e"
const val EXAMPLE_NEW_ENCODED_OPEN_GROUP = "__loki_public_chat_group__!68747470733a2f2f6f70656e2e67657473657373696f6e2e6f72672e6f78656e"
const val OXEN_STUB_HEX = "6f78656e"
const val EXAMPLE_LEGACY_SERVER_ID = "http://116.203.70.33.oxen"
const val EXAMPLE_NEW_SERVER_ID = "https://open.getsession.org.oxen"
const val LEGACY_THREAD_ID = 1L
const val NEW_THREAD_ID = 2L
}
private fun legacyOpenGroupRecipient(additionalMocks: ((KStubbing<Recipient>) -> Unit) ? = null) = mock<Recipient> {
on { address } doReturn Address.fromSerialized(EXAMPLE_LEGACY_ENCODED_OPEN_GROUP)
on { isOpenGroupRecipient } doReturn true
additionalMocks?.let { it(this) }
}
private fun newOpenGroupRecipient(additionalMocks: ((KStubbing<Recipient>) -> Unit) ? = null) = mock<Recipient> {
on { address } doReturn Address.fromSerialized(EXAMPLE_NEW_ENCODED_OPEN_GROUP)
on { isOpenGroupRecipient } doReturn true
additionalMocks?.let { it(this) }
}
private fun legacyThreadRecord(additionalRecipientMocks: ((KStubbing<Recipient>) -> Unit) ? = null, additionalThreadMocks: ((KStubbing<ThreadRecord>) -> Unit)? = null) = mock<ThreadRecord> {
val returnedRecipient = legacyOpenGroupRecipient(additionalRecipientMocks)
on { recipient } doReturn returnedRecipient
on { threadId } doReturn LEGACY_THREAD_ID
}
private fun newThreadRecord(additionalRecipientMocks: ((KStubbing<Recipient>) -> Unit)? = null, additionalThreadMocks: ((KStubbing<ThreadRecord>) -> Unit)? = null) = mock<ThreadRecord> {
val returnedRecipient = newOpenGroupRecipient(additionalRecipientMocks)
on { recipient } doReturn returnedRecipient
on { threadId } doReturn NEW_THREAD_ID
}
@Test
fun `it should generate the correct room stubs for legacy groups`() {
val mockRecipient = legacyOpenGroupRecipient()
assertEquals(OXEN_STUB_HEX, mockRecipient.roomStub())
}
@Test
fun `it should generate the correct room stubs for new groups`() {
val mockNewRecipient = newOpenGroupRecipient()
assertEquals(OXEN_STUB_HEX, mockNewRecipient.roomStub())
}
@Test
fun `it should return correct mappings`() {
val legacyThread = legacyThreadRecord()
val newThread = newThreadRecord()
val expectedMapping = listOf(
OpenGroupMapping(OXEN_STUB_HEX, LEGACY_THREAD_ID, NEW_THREAD_ID)
)
assertTrue(expectedMapping.containsAll(OpenGroupMigrator.getExistingMappings(listOf(legacyThread), listOf(newThread))))
}
@Test
fun `it should return no mappings if there are no legacy open groups`() {
val mappings = OpenGroupMigrator.getExistingMappings(listOf(), listOf())
assertTrue(mappings.isEmpty())
}
@Test
fun `it should return no mappings if there are only new open groups`() {
val newThread = newThreadRecord()
val mappings = OpenGroupMigrator.getExistingMappings(emptyList(), listOf(newThread))
assertTrue(mappings.isEmpty())
}
@Test
fun `it should return null new thread in mappings if there are only legacy open groups`() {
val legacyThread = legacyThreadRecord()
val mappings = OpenGroupMigrator.getExistingMappings(listOf(legacyThread), emptyList())
val expectedMappings = listOf(
OpenGroupMapping(OXEN_STUB_HEX, LEGACY_THREAD_ID, null)
)
assertTrue(expectedMappings.containsAll(mappings))
}
@Test
fun `test migration thread DB calls legacy and returns if no legacy official groups`() {
val mockedThreadDb = mock<ThreadDatabase> {
on { legacyOxenOpenGroups } doReturn emptyList()
}
val mockedDbComponent = mock<DatabaseComponent> {
on { threadDatabase() } doReturn mockedThreadDb
}
OpenGroupMigrator.migrate(mockedDbComponent)
verify(mockedDbComponent).threadDatabase()
verify(mockedThreadDb).legacyOxenOpenGroups
verifyNoMoreInteractions(mockedThreadDb)
}
@Test
fun `it should migrate on thread, group and loki dbs with correct values for legacy only migration`() {
// mock threadDB
val capturedThreadId = argumentCaptor<Long>()
val capturedNewEncoded = argumentCaptor<String>()
val mockedThreadDb = mock<ThreadDatabase> {
val legacyThreadRecord = legacyThreadRecord()
on { legacyOxenOpenGroups } doReturn listOf(legacyThreadRecord)
on { httpsOxenOpenGroups } doReturn emptyList()
on { migrateEncodedGroup(capturedThreadId.capture(), capturedNewEncoded.capture()) } doAnswer {}
}
// mock groupDB
val capturedGroupLegacyEncoded = argumentCaptor<String>()
val capturedGroupNewEncoded = argumentCaptor<String>()
val mockedGroupDb = mock<GroupDatabase> {
on {
migrateEncodedGroup(
capturedGroupLegacyEncoded.capture(),
capturedGroupNewEncoded.capture()
)
} doAnswer {}
}
// mock LokiAPIDB
val capturedLokiLegacyGroup = argumentCaptor<String>()
val capturedLokiNewGroup = argumentCaptor<String>()
val mockedLokiApi = mock<LokiAPIDatabase> {
on { migrateLegacyOpenGroup(capturedLokiLegacyGroup.capture(), capturedLokiNewGroup.capture()) } doAnswer {}
}
val pubKey = OpenGroupApi.defaultServerPublicKey
val room = "oxen"
val legacyServer = OpenGroupApi.legacyDefaultServer
val newServer = OpenGroupApi.defaultServer
val lokiThreadOpenGroup = argumentCaptor<OpenGroup>()
val mockedLokiThreadDb = mock<LokiThreadDatabase> {
on { getOpenGroupChat(eq(LEGACY_THREAD_ID)) } doReturn OpenGroup(legacyServer, room, "Oxen", 0, pubKey)
on { setOpenGroupChat(lokiThreadOpenGroup.capture(), eq(LEGACY_THREAD_ID)) } doAnswer {}
}
val mockedDbComponent = mock<DatabaseComponent> {
on { threadDatabase() } doReturn mockedThreadDb
on { groupDatabase() } doReturn mockedGroupDb
on { lokiAPIDatabase() } doReturn mockedLokiApi
on { lokiThreadDatabase() } doReturn mockedLokiThreadDb
}
OpenGroupMigrator.migrate(mockedDbComponent)
// expect threadDB migration to reflect new thread values:
// thread ID = 1, encoded ID = new encoded ID
assertEquals(LEGACY_THREAD_ID, capturedThreadId.firstValue)
assertEquals(EXAMPLE_NEW_ENCODED_OPEN_GROUP, capturedNewEncoded.firstValue)
// expect groupDB migration to reflect new thread values:
// legacy encoded ID, new encoded ID
assertEquals(EXAMPLE_LEGACY_ENCODED_OPEN_GROUP, capturedGroupLegacyEncoded.firstValue)
assertEquals(EXAMPLE_NEW_ENCODED_OPEN_GROUP, capturedGroupNewEncoded.firstValue)
// expect Loki API DB migration to reflect new thread values:
assertEquals("${OpenGroupApi.legacyDefaultServer}.oxen", capturedLokiLegacyGroup.firstValue)
assertEquals("${OpenGroupApi.defaultServer}.oxen", capturedLokiNewGroup.firstValue)
assertEquals(newServer, lokiThreadOpenGroup.firstValue.server)
}
@Test
fun `it should migrate and delete legacy thread with conflicting new and old values`() {
// mock threadDB
val capturedThreadId = argumentCaptor<Long>()
val mockedThreadDb = mock<ThreadDatabase> {
val legacyThreadRecord = legacyThreadRecord()
val newThreadRecord = newThreadRecord()
on { legacyOxenOpenGroups } doReturn listOf(legacyThreadRecord)
on { httpsOxenOpenGroups } doReturn listOf(newThreadRecord)
on { deleteConversation(capturedThreadId.capture()) } doAnswer {}
}
// mock groupDB
val capturedGroupLegacyEncoded = argumentCaptor<String>()
val mockedGroupDb = mock<GroupDatabase> {
on { delete(capturedGroupLegacyEncoded.capture()) } doReturn true
}
// mock LokiAPIDB
val capturedLokiLegacyGroup = argumentCaptor<String>()
val capturedLokiNewGroup = argumentCaptor<String>()
val mockedLokiApi = mock<LokiAPIDatabase> {
on { migrateLegacyOpenGroup(capturedLokiLegacyGroup.capture(), capturedLokiNewGroup.capture()) } doAnswer {}
}
// mock messaging dbs
val migrateMmsFromThreadId = argumentCaptor<Long>()
val migrateMmsToThreadId = argumentCaptor<Long>()
val mockedMmsDb = mock<MmsDatabase> {
on { migrateThreadId(migrateMmsFromThreadId.capture(), migrateMmsToThreadId.capture()) } doAnswer {}
}
val migrateSmsFromThreadId = argumentCaptor<Long>()
val migrateSmsToThreadId = argumentCaptor<Long>()
val mockedSmsDb = mock<SmsDatabase> {
on { migrateThreadId(migrateSmsFromThreadId.capture(), migrateSmsToThreadId.capture()) } doAnswer {}
}
val lokiFromThreadId = argumentCaptor<Long>()
val lokiToThreadId = argumentCaptor<Long>()
val mockedLokiMessageDatabase = mock<LokiMessageDatabase> {
on { migrateThreadId(lokiFromThreadId.capture(), lokiToThreadId.capture()) } doAnswer {}
}
val mockedLokiThreadDb = mock<LokiThreadDatabase> {
on { removeOpenGroupChat(eq(LEGACY_THREAD_ID)) } doAnswer {}
}
val mockedDbComponent = mock<DatabaseComponent> {
on { threadDatabase() } doReturn mockedThreadDb
on { groupDatabase() } doReturn mockedGroupDb
on { lokiAPIDatabase() } doReturn mockedLokiApi
on { mmsDatabase() } doReturn mockedMmsDb
on { smsDatabase() } doReturn mockedSmsDb
on { lokiMessageDatabase() } doReturn mockedLokiMessageDatabase
on { lokiThreadDatabase() } doReturn mockedLokiThreadDb
}
OpenGroupMigrator.migrate(mockedDbComponent)
// should delete thread by thread ID
assertEquals(LEGACY_THREAD_ID, capturedThreadId.firstValue)
// should delete group by legacy encoded ID
assertEquals(EXAMPLE_LEGACY_ENCODED_OPEN_GROUP, capturedGroupLegacyEncoded.firstValue)
// should migrate SMS from legacy thread ID to new thread ID
assertEquals(LEGACY_THREAD_ID, migrateSmsFromThreadId.firstValue)
assertEquals(NEW_THREAD_ID, migrateSmsToThreadId.firstValue)
// should migrate MMS from legacy thread ID to new thread ID
assertEquals(LEGACY_THREAD_ID, migrateMmsFromThreadId.firstValue)
assertEquals(NEW_THREAD_ID, migrateMmsToThreadId.firstValue)
}
}