mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
22 Commits
beta-0.16.
...
beta-0.16.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef2286ab53 | ||
|
|
47b25f3221 | ||
|
|
c30bfa12ce | ||
|
|
d0fc04251d | ||
|
|
dcbb41eb7a | ||
|
|
999bdf8866 | ||
|
|
911c0c0fd9 | ||
|
|
99d8cc64a6 | ||
|
|
ba727d7568 | ||
|
|
ed01048f9f | ||
|
|
043ee3c58e | ||
|
|
6e0af7deda | ||
|
|
9591db2097 | ||
|
|
329a4c64f6 | ||
|
|
79015bc5ae | ||
|
|
27422ab9f9 | ||
|
|
abcb682498 | ||
|
|
5044127c46 | ||
|
|
0e4b8ca62e | ||
|
|
822017c69c | ||
|
|
eb6561b93d | ||
|
|
d24b1884a2 |
@@ -12,8 +12,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 22
|
||||
versionCode 1609
|
||||
versionName "0.16.9"
|
||||
versionCode 1610
|
||||
versionName "0.16.10"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
}
|
||||
|
||||
@@ -32,31 +32,31 @@ dependencies {
|
||||
def torBinaryDir = 'src/main/res/raw'
|
||||
|
||||
task downloadTorGeoIp(type: Download) {
|
||||
src 'https://briarproject.org/build/geoip-2017-05-02.zip'
|
||||
src 'https://briarproject.org/build/geoip-2017-09-06.zip'
|
||||
dest "$torBinaryDir/geoip.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
task downloadTorBinaryArm(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.9.11-arm.zip'
|
||||
src 'https://briarproject.org/build/tor-0.2.9.12-arm.zip'
|
||||
dest "$torBinaryDir/tor_arm.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
task downloadTorBinaryArmPie(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.9.11-arm-pie.zip'
|
||||
src 'https://briarproject.org/build/tor-0.2.9.12-arm-pie.zip'
|
||||
dest "$torBinaryDir/tor_arm_pie.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
task downloadTorBinaryX86(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.9.11-x86.zip'
|
||||
src 'https://briarproject.org/build/tor-0.2.9.12-x86.zip'
|
||||
dest "$torBinaryDir/tor_x86.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
task downloadTorBinaryX86Pie(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.9.11-x86-pie.zip'
|
||||
src 'https://briarproject.org/build/tor-0.2.9.12-x86-pie.zip'
|
||||
dest "$torBinaryDir/tor_x86_pie.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
@@ -64,31 +64,31 @@ task downloadTorBinaryX86Pie(type: Download) {
|
||||
task verifyTorGeoIp(type: Verify, dependsOn: 'downloadTorGeoIp') {
|
||||
src "$torBinaryDir/geoip.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum '51f4d1272fb867e1f3b36b67a584e2a33c40b40f62305457d799fd399cd77c9b'
|
||||
checksum 'fe49d3adb86d3c512373101422a017dbb86c85a570524663f09dd8ce143a24f3'
|
||||
}
|
||||
|
||||
task verifyTorBinaryArm(type: Verify, dependsOn: 'downloadTorBinaryArm') {
|
||||
src "$torBinaryDir/tor_arm.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum '1da6008663a8ad98b349e62acbbf42c379f65ec504fa467cb119c187cd5a4c6b'
|
||||
checksum '8ed0b347ffed1d6a4d2fd14495118eb92be83e9cc06e057e15220dc288b31688'
|
||||
}
|
||||
|
||||
task verifyTorBinaryArmPie(type: Verify, dependsOn: 'downloadTorBinaryArmPie') {
|
||||
src "$torBinaryDir/tor_arm_pie.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum 'eb061f880829e05f104690ac744848133f2dacef04759d425a2cff0df32c271e'
|
||||
checksum '64403262511c29f462ca5e7c7621bfc3c944898364d1d5ad35a016bb8a034283'
|
||||
}
|
||||
|
||||
task verifyTorBinaryX86(type: Verify, dependsOn: 'downloadTorBinaryX86') {
|
||||
src "$torBinaryDir/tor_x86.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum 'f5308aff8303daca082f82227d02b51ddedba4ab1d1420739ada0427ae5dbb41'
|
||||
checksum '61e014607a2079bcf1646289c67bff6372b1aded6e1d8d83d7791efda9a4d5ab'
|
||||
}
|
||||
|
||||
task verifyTorBinaryX86Pie(type: Verify, dependsOn: 'downloadTorBinaryX86Pie') {
|
||||
src "$torBinaryDir/tor_x86_pie.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum '889a6c81ac73d05d35ed610ca5a913cee44d333e4ae1749c2a107f2f7dd8197b'
|
||||
checksum '18fbc98356697dd0895836ab46d5c9877d1c539193464f7db1e82a65adaaf288'
|
||||
}
|
||||
|
||||
project.afterEvaluate {
|
||||
|
||||
@@ -11,7 +11,6 @@ import android.content.IntentFilter;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
@@ -30,23 +29,14 @@ import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletionService;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -61,8 +51,6 @@ import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERA
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||
import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
|
||||
@@ -79,10 +67,6 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(DroidtoothPlugin.class.getName());
|
||||
private static final String FOUND =
|
||||
"android.bluetooth.device.action.FOUND";
|
||||
private static final String DISCOVERY_FINISHED =
|
||||
"android.bluetooth.adapter.action.DISCOVERY_FINISHED";
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final AndroidExecutor androidExecutor;
|
||||
@@ -374,90 +358,6 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
return new DroidtoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsInvitations() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout, boolean alice) {
|
||||
if (!isRunning()) return null;
|
||||
// Use the invitation codes to generate the UUID
|
||||
byte[] b = r.nextBytes(UUID_BYTES);
|
||||
UUID uuid = UUID.nameUUIDFromBytes(b);
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Invitation UUID " + uuid);
|
||||
// Bind a server socket for receiving invitation connections
|
||||
BluetoothServerSocket ss;
|
||||
try {
|
||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||
"RFCOMM", uuid);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
// Create the background tasks
|
||||
CompletionService<BluetoothSocket> complete =
|
||||
new ExecutorCompletionService<>(ioExecutor);
|
||||
List<Future<BluetoothSocket>> futures = new ArrayList<>();
|
||||
if (alice) {
|
||||
// Return the first connected socket
|
||||
futures.add(complete.submit(new ListeningTask(ss)));
|
||||
futures.add(complete.submit(new DiscoveryTask(uuid.toString())));
|
||||
} else {
|
||||
// Return the first socket with readable data
|
||||
futures.add(complete.submit(new ReadableTask(
|
||||
new ListeningTask(ss))));
|
||||
futures.add(complete.submit(new ReadableTask(
|
||||
new DiscoveryTask(uuid.toString()))));
|
||||
}
|
||||
BluetoothSocket chosen = null;
|
||||
try {
|
||||
Future<BluetoothSocket> f = complete.poll(timeout, MILLISECONDS);
|
||||
if (f == null) return null; // No task completed within the timeout
|
||||
chosen = f.get();
|
||||
return new DroidtoothTransportConnection(this, chosen);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while exchanging invitations");
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
} catch (ExecutionException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
} finally {
|
||||
// Closing the socket will terminate the listener task
|
||||
tryToClose(ss);
|
||||
closeSockets(futures, chosen);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeSockets(final List<Future<BluetoothSocket>> futures,
|
||||
@Nullable final BluetoothSocket chosen) {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Future<BluetoothSocket> f : futures) {
|
||||
try {
|
||||
if (f.cancel(true)) {
|
||||
LOG.info("Cancelled task");
|
||||
} else {
|
||||
BluetoothSocket s = f.get();
|
||||
if (s != null && s != chosen) {
|
||||
LOG.info("Closing unwanted socket");
|
||||
s.close();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while closing sockets");
|
||||
return;
|
||||
} catch (ExecutionException | IOException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return true;
|
||||
@@ -472,7 +372,7 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
// No truncation necessary because COMMIT_LENGTH = 16
|
||||
UUID uuid = UUID.nameUUIDFromBytes(commitment);
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
||||
// Bind a server socket for receiving invitation connections
|
||||
// Bind a server socket for receiving key agreement connections
|
||||
BluetoothServerSocket ss;
|
||||
try {
|
||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||
@@ -536,115 +436,6 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private class DiscoveryTask implements Callable<BluetoothSocket> {
|
||||
|
||||
private final String uuid;
|
||||
|
||||
private DiscoveryTask(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BluetoothSocket call() throws Exception {
|
||||
// Repeat discovery until we connect or get interrupted
|
||||
while (true) {
|
||||
// Discover nearby devices
|
||||
LOG.info("Discovering nearby devices");
|
||||
List<String> addresses = discoverDevices();
|
||||
if (addresses.isEmpty()) {
|
||||
LOG.info("No devices discovered");
|
||||
continue;
|
||||
}
|
||||
// Connect to any device with the right UUID
|
||||
for (String address : addresses) {
|
||||
BluetoothSocket s = connect(address, uuid);
|
||||
if (s != null) {
|
||||
LOG.info("Outgoing connection");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> discoverDevices() throws InterruptedException {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(FOUND);
|
||||
filter.addAction(DISCOVERY_FINISHED);
|
||||
DiscoveryReceiver disco = new DiscoveryReceiver();
|
||||
appContext.registerReceiver(disco, filter);
|
||||
LOG.info("Starting discovery");
|
||||
adapter.startDiscovery();
|
||||
return disco.waitForAddresses();
|
||||
}
|
||||
}
|
||||
|
||||
private static class DiscoveryReceiver extends BroadcastReceiver {
|
||||
|
||||
private final CountDownLatch finished = new CountDownLatch(1);
|
||||
private final List<String> addresses = new CopyOnWriteArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action.equals(DISCOVERY_FINISHED)) {
|
||||
LOG.info("Discovery finished");
|
||||
ctx.unregisterReceiver(this);
|
||||
finished.countDown();
|
||||
} else if (action.equals(FOUND)) {
|
||||
BluetoothDevice d = intent.getParcelableExtra(EXTRA_DEVICE);
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Discovered device: " +
|
||||
scrubMacAddress(d.getAddress()));
|
||||
}
|
||||
addresses.add(d.getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> waitForAddresses() throws InterruptedException {
|
||||
finished.await();
|
||||
List<String> shuffled = new ArrayList<>(addresses);
|
||||
Collections.shuffle(shuffled);
|
||||
return shuffled;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ListeningTask implements Callable<BluetoothSocket> {
|
||||
|
||||
private final BluetoothServerSocket serverSocket;
|
||||
|
||||
private ListeningTask(BluetoothServerSocket serverSocket) {
|
||||
this.serverSocket = serverSocket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BluetoothSocket call() throws IOException {
|
||||
BluetoothSocket s = serverSocket.accept();
|
||||
LOG.info("Incoming connection");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReadableTask implements Callable<BluetoothSocket> {
|
||||
|
||||
private final Callable<BluetoothSocket> connectionTask;
|
||||
|
||||
private ReadableTask(Callable<BluetoothSocket> connectionTask) {
|
||||
this.connectionTask = connectionTask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BluetoothSocket call() throws Exception {
|
||||
BluetoothSocket s = connectionTask.call();
|
||||
InputStream in = s.getInputStream();
|
||||
while (in.available() == 0) {
|
||||
LOG.info("Waiting for data");
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
LOG.info("Data available");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
||||
|
||||
private final BluetoothServerSocket ss;
|
||||
|
||||
@@ -17,7 +17,6 @@ import net.freehaven.tor.control.EventHandler;
|
||||
import net.freehaven.tor.control.TorControlConnection;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
@@ -589,17 +588,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsInvitations() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout, boolean alice) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return false;
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.tor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Plugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
|
||||
import org.briarproject.bramble.util.IoUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -21,12 +22,12 @@ class TorTransportConnection extends AbstractDuplexTransportConnection {
|
||||
|
||||
@Override
|
||||
protected InputStream getInputStream() throws IOException {
|
||||
return socket.getInputStream();
|
||||
return IoUtils.getInputStream(socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OutputStream getOutputStream() throws IOException {
|
||||
return socket.getOutputStream();
|
||||
return IoUtils.getOutputStream(socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -10,8 +10,6 @@ public interface CryptoComponent {
|
||||
|
||||
SecretKey generateSecretKey();
|
||||
|
||||
PseudoRandom getPseudoRandom(int seed1, int seed2);
|
||||
|
||||
SecureRandom getSecureRandom();
|
||||
|
||||
KeyPair generateAgreementKeyPair();
|
||||
@@ -24,15 +22,6 @@ public interface CryptoComponent {
|
||||
|
||||
KeyParser getMessageKeyParser();
|
||||
|
||||
/** Generates a random invitation code. */
|
||||
int generateBTInvitationCode();
|
||||
|
||||
/**
|
||||
* Derives a confirmation code from the given master secret.
|
||||
* @param alice whether the code is for use by Alice or Bob.
|
||||
*/
|
||||
int deriveBTConfirmationCode(SecretKey master, boolean alice);
|
||||
|
||||
/**
|
||||
* Derives a stream header key from the given master secret.
|
||||
* @param alice whether the key is for use by Alice or Bob.
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package org.briarproject.bramble.api.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
/**
|
||||
* A deterministic pseudo-random number generator.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface PseudoRandom {
|
||||
|
||||
byte[] nextBytes(int bytes);
|
||||
}
|
||||
@@ -14,8 +14,9 @@ public interface StreamDecrypterFactory {
|
||||
StreamDecrypter createStreamDecrypter(InputStream in, StreamContext ctx);
|
||||
|
||||
/**
|
||||
* Creates a {@link StreamDecrypter} for decrypting an invitation stream.
|
||||
* Creates a {@link StreamDecrypter} for decrypting a contact exchange
|
||||
* stream.
|
||||
*/
|
||||
StreamDecrypter createInvitationStreamDecrypter(InputStream in,
|
||||
StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
|
||||
SecretKey headerKey);
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@ public interface StreamEncrypterFactory {
|
||||
StreamEncrypter createStreamEncrypter(OutputStream out, StreamContext ctx);
|
||||
|
||||
/**
|
||||
* Creates a {@link StreamEncrypter} for encrypting an invitation stream.
|
||||
* Creates a {@link StreamEncrypter} for encrypting a contact exchange
|
||||
* stream.
|
||||
*/
|
||||
StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
|
||||
StreamEncrypter createContactExchangeStreamDecrypter(OutputStream out,
|
||||
SecretKey headerKey);
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.briarproject.bramble.api.invitation;
|
||||
|
||||
public interface InvitationConstants {
|
||||
|
||||
/**
|
||||
* The connection timeout in milliseconds.
|
||||
*/
|
||||
long CONNECTION_TIMEOUT = 60 * 1000;
|
||||
|
||||
/**
|
||||
* The confirmation timeout in milliseconds.
|
||||
*/
|
||||
long CONFIRMATION_TIMEOUT = 60 * 1000;
|
||||
|
||||
/**
|
||||
* The number of bits in an invitation or confirmation code. Codes must fit
|
||||
* into six decimal digits.
|
||||
*/
|
||||
int CODE_BITS = 19;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package org.briarproject.bramble.api.invitation;
|
||||
|
||||
/**
|
||||
* An interface for receiving updates about the state of an
|
||||
* {@link InvitationTask}.
|
||||
*/
|
||||
public interface InvitationListener {
|
||||
|
||||
/** Called if a connection to the remote peer is established. */
|
||||
void connectionSucceeded();
|
||||
|
||||
/**
|
||||
* Called if a connection to the remote peer cannot be established. This
|
||||
* indicates that the protocol has ended unsuccessfully.
|
||||
*/
|
||||
void connectionFailed();
|
||||
|
||||
/** Called if key agreement with the remote peer succeeds. */
|
||||
void keyAgreementSucceeded(int localCode, int remoteCode);
|
||||
|
||||
/**
|
||||
* Called if key agreement with the remote peer fails or the connection is
|
||||
* lost. This indicates that the protocol has ended unsuccessfully.
|
||||
*/
|
||||
void keyAgreementFailed();
|
||||
|
||||
/** Called if the remote peer's confirmation check succeeds. */
|
||||
void remoteConfirmationSucceeded();
|
||||
|
||||
/**
|
||||
* Called if remote peer's confirmation check fails or the connection is
|
||||
* lost. This indicates that the protocol has ended unsuccessfully.
|
||||
*/
|
||||
void remoteConfirmationFailed();
|
||||
|
||||
/**
|
||||
* Called if the exchange of pseudonyms succeeds. This indicates that the
|
||||
* protocol has ended successfully.
|
||||
*/
|
||||
void pseudonymExchangeSucceeded(String remoteName);
|
||||
|
||||
/**
|
||||
* Called if the exchange of pseudonyms fails or the connection is lost.
|
||||
* This indicates that the protocol has ended unsuccessfully.
|
||||
*/
|
||||
void pseudonymExchangeFailed();
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package org.briarproject.bramble.api.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* A snapshot of the state of an {@link InvitationTask}.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class InvitationState {
|
||||
|
||||
private final int localInvitationCode, remoteInvitationCode;
|
||||
private final int localConfirmationCode, remoteConfirmationCode;
|
||||
private final boolean connected, connectionFailed;
|
||||
private final boolean localCompared, remoteCompared;
|
||||
private final boolean localMatched, remoteMatched;
|
||||
@Nullable
|
||||
private final String contactName;
|
||||
|
||||
public InvitationState(int localInvitationCode, int remoteInvitationCode,
|
||||
int localConfirmationCode, int remoteConfirmationCode,
|
||||
boolean connected, boolean connectionFailed, boolean localCompared,
|
||||
boolean remoteCompared, boolean localMatched,
|
||||
boolean remoteMatched, @Nullable String contactName) {
|
||||
this.localInvitationCode = localInvitationCode;
|
||||
this.remoteInvitationCode = remoteInvitationCode;
|
||||
this.localConfirmationCode = localConfirmationCode;
|
||||
this.remoteConfirmationCode = remoteConfirmationCode;
|
||||
this.connected = connected;
|
||||
this.connectionFailed = connectionFailed;
|
||||
this.localCompared = localCompared;
|
||||
this.remoteCompared = remoteCompared;
|
||||
this.localMatched = localMatched;
|
||||
this.remoteMatched = remoteMatched;
|
||||
this.contactName = contactName;
|
||||
}
|
||||
|
||||
public int getLocalInvitationCode() {
|
||||
return localInvitationCode;
|
||||
}
|
||||
|
||||
public int getRemoteInvitationCode() {
|
||||
return remoteInvitationCode;
|
||||
}
|
||||
|
||||
public int getLocalConfirmationCode() {
|
||||
return localConfirmationCode;
|
||||
}
|
||||
|
||||
public int getRemoteConfirmationCode() {
|
||||
return remoteConfirmationCode;
|
||||
}
|
||||
|
||||
public boolean getConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
public boolean getConnectionFailed() {
|
||||
return connectionFailed;
|
||||
}
|
||||
|
||||
public boolean getLocalCompared() {
|
||||
return localCompared;
|
||||
}
|
||||
|
||||
public boolean getRemoteCompared() {
|
||||
return remoteCompared;
|
||||
}
|
||||
|
||||
public boolean getLocalMatched() {
|
||||
return localMatched;
|
||||
}
|
||||
|
||||
public boolean getRemoteMatched() {
|
||||
return remoteMatched;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getContactName() {
|
||||
return contactName;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package org.briarproject.bramble.api.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
/**
|
||||
* A task for exchanging invitations with a remote peer.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface InvitationTask {
|
||||
|
||||
/**
|
||||
* Adds a listener to be informed of state changes and returns the
|
||||
* task's current state.
|
||||
*/
|
||||
InvitationState addListener(InvitationListener l);
|
||||
|
||||
/**
|
||||
* Removes the given listener.
|
||||
*/
|
||||
void removeListener(InvitationListener l);
|
||||
|
||||
/**
|
||||
* Asynchronously starts the connection process.
|
||||
*/
|
||||
void connect();
|
||||
|
||||
/**
|
||||
* Asynchronously informs the remote peer that the local peer's
|
||||
* confirmation codes matched.
|
||||
*/
|
||||
void localConfirmationSucceeded();
|
||||
|
||||
/**
|
||||
* Asynchronously informs the remote peer that the local peer's
|
||||
* confirmation codes did not match.
|
||||
*/
|
||||
void localConfirmationFailed();
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.briarproject.bramble.api.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
/**
|
||||
* Creates tasks for exchanging invitations with remote peers.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface InvitationTaskFactory {
|
||||
|
||||
/**
|
||||
* Creates a task using the given local and remote invitation codes.
|
||||
*/
|
||||
InvitationTask createTask(int localCode, int remoteCode);
|
||||
}
|
||||
@@ -32,11 +32,6 @@ public interface PluginManager {
|
||||
*/
|
||||
Collection<DuplexPlugin> getDuplexPlugins();
|
||||
|
||||
/**
|
||||
* Returns any duplex plugins that support invitations.
|
||||
*/
|
||||
Collection<DuplexPlugin> getInvitationPlugins();
|
||||
|
||||
/**
|
||||
* Returns any duplex plugins that support key agreement.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.bramble.api.plugin.duplex;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
@@ -23,20 +22,6 @@ public interface DuplexPlugin extends Plugin {
|
||||
@Nullable
|
||||
DuplexTransportConnection createConnection(ContactId c);
|
||||
|
||||
/**
|
||||
* Returns true if the plugin supports exchanging invitations.
|
||||
*/
|
||||
boolean supportsInvitations();
|
||||
|
||||
/**
|
||||
* Attempts to create and return an invitation connection to the remote
|
||||
* peer. Returns null if no connection can be established within the given
|
||||
* time.
|
||||
*/
|
||||
@Nullable
|
||||
DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout, boolean alice);
|
||||
|
||||
/**
|
||||
* Returns true if the plugin supports short-range key agreement.
|
||||
*/
|
||||
|
||||
@@ -15,9 +15,9 @@ public interface StreamReaderFactory {
|
||||
InputStream createStreamReader(InputStream in, StreamContext ctx);
|
||||
|
||||
/**
|
||||
* Creates an {@link InputStream InputStream} for reading from an
|
||||
* invitation stream.
|
||||
* Creates an {@link InputStream InputStream} for reading from a contact
|
||||
* exchangestream.
|
||||
*/
|
||||
InputStream createInvitationStreamReader(InputStream in,
|
||||
InputStream createContactExchangeStreamReader(InputStream in,
|
||||
SecretKey headerKey);
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ public interface StreamWriterFactory {
|
||||
OutputStream createStreamWriter(OutputStream out, StreamContext ctx);
|
||||
|
||||
/**
|
||||
* Creates an {@link OutputStream OutputStream} for writing to an
|
||||
* invitation stream.
|
||||
* Creates an {@link OutputStream OutputStream} for writing to a contact
|
||||
* exchange stream.
|
||||
*/
|
||||
OutputStream createInvitationStreamWriter(OutputStream out,
|
||||
OutputStream createContactExchangeStreamWriter(OutputStream out,
|
||||
SecretKey headerKey);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -59,4 +60,24 @@ public class IoUtils {
|
||||
offset += read;
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for a bug in Android 7, see
|
||||
// https://android-review.googlesource.com/#/c/271775/
|
||||
public static InputStream getInputStream(Socket s) throws IOException {
|
||||
try {
|
||||
return s.getInputStream();
|
||||
} catch (NullPointerException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for a bug in Android 7, see
|
||||
// https://android-review.googlesource.com/#/c/271775/
|
||||
public static OutputStream getOutputStream(Socket s) throws IOException {
|
||||
try {
|
||||
return s.getOutputStream();
|
||||
} catch (NullPointerException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import org.briarproject.bramble.db.DatabaseExecutorModule;
|
||||
import org.briarproject.bramble.db.DatabaseModule;
|
||||
import org.briarproject.bramble.event.EventModule;
|
||||
import org.briarproject.bramble.identity.IdentityModule;
|
||||
import org.briarproject.bramble.invitation.InvitationModule;
|
||||
import org.briarproject.bramble.keyagreement.KeyAgreementModule;
|
||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||
import org.briarproject.bramble.plugin.PluginModule;
|
||||
@@ -32,7 +31,6 @@ import dagger.Module;
|
||||
DatabaseExecutorModule.class,
|
||||
EventModule.class,
|
||||
IdentityModule.class,
|
||||
InvitationModule.class,
|
||||
KeyAgreementModule.class,
|
||||
LifecycleModule.class,
|
||||
PluginModule.class,
|
||||
|
||||
@@ -80,7 +80,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
private volatile boolean alice;
|
||||
|
||||
@Inject
|
||||
public ContactExchangeTaskImpl(DatabaseComponent db,
|
||||
ContactExchangeTaskImpl(DatabaseComponent db,
|
||||
AuthorFactory authorFactory, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory, Clock clock,
|
||||
ConnectionManager connectionManager, ContactManager contactManager,
|
||||
@@ -146,12 +146,12 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
|
||||
// Create the readers
|
||||
InputStream streamReader =
|
||||
streamReaderFactory.createInvitationStreamReader(in,
|
||||
streamReaderFactory.createContactExchangeStreamReader(in,
|
||||
alice ? bobHeaderKey : aliceHeaderKey);
|
||||
BdfReader r = bdfReaderFactory.createReader(streamReader);
|
||||
// Create the writers
|
||||
OutputStream streamWriter =
|
||||
streamWriterFactory.createInvitationStreamWriter(out,
|
||||
streamWriterFactory.createContactExchangeStreamWriter(out,
|
||||
alice ? aliceHeaderKey : bobHeaderKey);
|
||||
BdfWriter w = bdfWriterFactory.createWriter(streamWriter);
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
@@ -41,7 +40,6 @@ import java.util.logging.Logger;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static org.briarproject.bramble.api.invitation.InvitationConstants.CODE_BITS;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
|
||||
@@ -68,9 +66,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
return s.getBytes(Charset.forName("US-ASCII"));
|
||||
}
|
||||
|
||||
// KDF labels for bluetooth confirmation code derivation
|
||||
private static final byte[] BT_A_CONFIRM = ascii("ALICE_CONFIRMATION_CODE");
|
||||
private static final byte[] BT_B_CONFIRM = ascii("BOB_CONFIRMATION_CODE");
|
||||
// KDF labels for contact exchange stream header key derivation
|
||||
private static final byte[] A_INVITE = ascii("ALICE_INVITATION_KEY");
|
||||
private static final byte[] B_INVITE = ascii("BOB_INVITATION_KEY");
|
||||
@@ -171,14 +166,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
return new SecretKey(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PseudoRandom getPseudoRandom(int seed1, int seed2) {
|
||||
byte[] seed = new byte[INT_32_BYTES * 2];
|
||||
ByteUtils.writeUint32(seed1, seed, 0);
|
||||
ByteUtils.writeUint32(seed2, seed, INT_32_BYTES);
|
||||
return new PseudoRandomImpl(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecureRandom getSecureRandom() {
|
||||
return secureRandom;
|
||||
@@ -250,20 +237,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
return messageEncrypter.getKeyParser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int generateBTInvitationCode() {
|
||||
int codeBytes = (CODE_BITS + 7) / 8;
|
||||
byte[] random = new byte[codeBytes];
|
||||
secureRandom.nextBytes(random);
|
||||
return ByteUtils.readUint(random, CODE_BITS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deriveBTConfirmationCode(SecretKey master, boolean alice) {
|
||||
byte[] b = macKdf(master, alice ? BT_A_CONFIRM : BT_B_CONFIRM);
|
||||
return ByteUtils.readUint(b, CODE_BITS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey deriveHeaderKey(SecretKey master,
|
||||
boolean alice) {
|
||||
|
||||
@@ -32,7 +32,7 @@ class StreamDecrypterFactoryImpl implements StreamDecrypterFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamDecrypter createInvitationStreamDecrypter(InputStream in,
|
||||
public StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
|
||||
SecretKey headerKey) {
|
||||
return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey);
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
|
||||
SecretKey headerKey) {
|
||||
public StreamEncrypter createContactExchangeStreamDecrypter(
|
||||
OutputStream out, SecretKey headerKey) {
|
||||
AuthenticatedCipher cipher = cipherProvider.get();
|
||||
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
||||
crypto.getSecureRandom().nextBytes(streamHeaderNonce);
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
package org.briarproject.bramble.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.data.BdfReader;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriter;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
/**
|
||||
* A connection thread for the peer being Alice in the invitation protocol.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
class AliceConnector extends Connector {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(AliceConnector.class.getName());
|
||||
|
||||
AliceConnector(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory,
|
||||
ContactExchangeTask contactExchangeTask, ConnectorGroup group,
|
||||
DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
|
||||
super(crypto, bdfReaderFactory, bdfWriterFactory, contactExchangeTask,
|
||||
group, plugin, localAuthor, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Create an incoming or outgoing connection
|
||||
DuplexTransportConnection conn = createInvitationConnection(true);
|
||||
if (conn == null) return;
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
|
||||
// Don't proceed with more than one connection
|
||||
if (group.getAndSetConnected()) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " redundant");
|
||||
tryToClose(conn, false);
|
||||
return;
|
||||
}
|
||||
// Carry out the key agreement protocol
|
||||
InputStream in;
|
||||
OutputStream out;
|
||||
BdfReader r;
|
||||
BdfWriter w;
|
||||
SecretKey master;
|
||||
try {
|
||||
in = conn.getReader().getInputStream();
|
||||
out = conn.getWriter().getOutputStream();
|
||||
r = bdfReaderFactory.createReader(in);
|
||||
w = bdfWriterFactory.createWriter(out);
|
||||
// Alice goes first
|
||||
sendPublicKeyHash(w);
|
||||
byte[] hash = receivePublicKeyHash(r);
|
||||
sendPublicKey(w);
|
||||
byte[] key = receivePublicKey(r);
|
||||
master = deriveMasterSecret(hash, key, true);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
group.keyAgreementFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
} catch (GeneralSecurityException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
group.keyAgreementFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
}
|
||||
// The key agreement succeeded - derive the confirmation codes
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
|
||||
int aliceCode = crypto.deriveBTConfirmationCode(master, true);
|
||||
int bobCode = crypto.deriveBTConfirmationCode(master, false);
|
||||
group.keyAgreementSucceeded(aliceCode, bobCode);
|
||||
// Exchange confirmation results
|
||||
boolean localMatched, remoteMatched;
|
||||
try {
|
||||
localMatched = group.waitForLocalConfirmationResult();
|
||||
sendConfirmation(w, localMatched);
|
||||
remoteMatched = receiveConfirmation(r);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
group.remoteConfirmationFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while waiting for confirmation");
|
||||
group.remoteConfirmationFailed();
|
||||
tryToClose(conn, true);
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
if (remoteMatched) group.remoteConfirmationSucceeded();
|
||||
else group.remoteConfirmationFailed();
|
||||
if (!(localMatched && remoteMatched)) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " confirmation failed");
|
||||
tryToClose(conn, false);
|
||||
return;
|
||||
}
|
||||
// Confirmation succeeded - upgrade to a secure connection
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " confirmation succeeded");
|
||||
contactExchangeTask.startExchange(group, localAuthor, master, conn,
|
||||
plugin.getId(), true);
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package org.briarproject.bramble.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.data.BdfReader;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriter;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
/**
|
||||
* A connection thread for the peer being Bob in the invitation protocol.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
class BobConnector extends Connector {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BobConnector.class.getName());
|
||||
|
||||
BobConnector(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory,
|
||||
ContactExchangeTask contactExchangeTask, ConnectorGroup group,
|
||||
DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
|
||||
super(crypto, bdfReaderFactory, bdfWriterFactory, contactExchangeTask,
|
||||
group, plugin, localAuthor, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Create an incoming or outgoing connection
|
||||
DuplexTransportConnection conn = createInvitationConnection(false);
|
||||
if (conn == null) return;
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
|
||||
// Carry out the key agreement protocol
|
||||
InputStream in;
|
||||
OutputStream out;
|
||||
BdfReader r;
|
||||
BdfWriter w;
|
||||
SecretKey master;
|
||||
try {
|
||||
in = conn.getReader().getInputStream();
|
||||
out = conn.getWriter().getOutputStream();
|
||||
r = bdfReaderFactory.createReader(in);
|
||||
w = bdfWriterFactory.createWriter(out);
|
||||
// Alice goes first
|
||||
byte[] hash = receivePublicKeyHash(r);
|
||||
// Don't proceed with more than one connection
|
||||
if (group.getAndSetConnected()) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " redundant");
|
||||
tryToClose(conn, false);
|
||||
return;
|
||||
}
|
||||
sendPublicKeyHash(w);
|
||||
byte[] key = receivePublicKey(r);
|
||||
sendPublicKey(w);
|
||||
master = deriveMasterSecret(hash, key, false);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
group.keyAgreementFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
} catch (GeneralSecurityException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
group.keyAgreementFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
}
|
||||
// The key agreement succeeded - derive the confirmation codes
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
|
||||
int aliceCode = crypto.deriveBTConfirmationCode(master, true);
|
||||
int bobCode = crypto.deriveBTConfirmationCode(master, false);
|
||||
group.keyAgreementSucceeded(bobCode, aliceCode);
|
||||
// Exchange confirmation results
|
||||
boolean localMatched, remoteMatched;
|
||||
try {
|
||||
remoteMatched = receiveConfirmation(r);
|
||||
localMatched = group.waitForLocalConfirmationResult();
|
||||
sendConfirmation(w, localMatched);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
group.remoteConfirmationFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while waiting for confirmation");
|
||||
group.remoteConfirmationFailed();
|
||||
tryToClose(conn, true);
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
if (remoteMatched) group.remoteConfirmationSucceeded();
|
||||
else group.remoteConfirmationFailed();
|
||||
if (!(localMatched && remoteMatched)) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " confirmation failed");
|
||||
tryToClose(conn, false);
|
||||
return;
|
||||
}
|
||||
// Confirmation succeeded - upgrade to a secure connection
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " confirmation succeeded");
|
||||
contactExchangeTask.startExchange(group, localAuthor, master, conn,
|
||||
plugin.getId(), false);
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
package org.briarproject.bramble.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.data.BdfReader;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriter;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.invitation.InvitationConstants.CONNECTION_TIMEOUT;
|
||||
|
||||
// FIXME: This class has way too many dependencies
|
||||
@NotNullByDefault
|
||||
abstract class Connector extends Thread {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(Connector.class.getName());
|
||||
private static final String LABEL_PUBLIC_KEY =
|
||||
"org.briarproject.bramble.invitation.PUBLIC_KEY";
|
||||
|
||||
protected final CryptoComponent crypto;
|
||||
protected final BdfReaderFactory bdfReaderFactory;
|
||||
protected final BdfWriterFactory bdfWriterFactory;
|
||||
protected final ContactExchangeTask contactExchangeTask;
|
||||
protected final ConnectorGroup group;
|
||||
protected final DuplexPlugin plugin;
|
||||
protected final LocalAuthor localAuthor;
|
||||
protected final PseudoRandom random;
|
||||
protected final String pluginName;
|
||||
|
||||
private final KeyPair keyPair;
|
||||
private final KeyParser keyParser;
|
||||
|
||||
Connector(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory,
|
||||
ContactExchangeTask contactExchangeTask, ConnectorGroup group,
|
||||
DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
|
||||
super("Connector");
|
||||
this.crypto = crypto;
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.bdfWriterFactory = bdfWriterFactory;
|
||||
this.contactExchangeTask = contactExchangeTask;
|
||||
this.group = group;
|
||||
this.plugin = plugin;
|
||||
this.localAuthor = localAuthor;
|
||||
this.random = random;
|
||||
pluginName = plugin.getClass().getName();
|
||||
keyPair = crypto.generateAgreementKeyPair();
|
||||
keyParser = crypto.getAgreementKeyParser();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
DuplexTransportConnection createInvitationConnection(boolean alice) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " creating invitation connection");
|
||||
return plugin.createInvitationConnection(random, CONNECTION_TIMEOUT,
|
||||
alice);
|
||||
}
|
||||
|
||||
void sendPublicKeyHash(BdfWriter w) throws IOException {
|
||||
byte[] hash =
|
||||
crypto.hash(LABEL_PUBLIC_KEY, keyPair.getPublic().getEncoded());
|
||||
w.writeRaw(hash);
|
||||
w.flush();
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " sent hash");
|
||||
}
|
||||
|
||||
byte[] receivePublicKeyHash(BdfReader r) throws IOException {
|
||||
int hashLength = crypto.getHashLength();
|
||||
byte[] b = r.readRaw(hashLength);
|
||||
if (b.length < hashLength) throw new FormatException();
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " received hash");
|
||||
return b;
|
||||
}
|
||||
|
||||
void sendPublicKey(BdfWriter w) throws IOException {
|
||||
byte[] key = keyPair.getPublic().getEncoded();
|
||||
w.writeRaw(key);
|
||||
w.flush();
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " sent key");
|
||||
}
|
||||
|
||||
byte[] receivePublicKey(BdfReader r)
|
||||
throws GeneralSecurityException, IOException {
|
||||
byte[] b = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
|
||||
keyParser.parsePublicKey(b);
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " received key");
|
||||
return b;
|
||||
}
|
||||
|
||||
SecretKey deriveMasterSecret(byte[] hash, byte[] key, boolean alice)
|
||||
throws GeneralSecurityException {
|
||||
// Check that the hash matches the key
|
||||
byte[] keyHash =
|
||||
crypto.hash(LABEL_PUBLIC_KEY, keyPair.getPublic().getEncoded());
|
||||
if (!Arrays.equals(hash, keyHash)) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " hash does not match key");
|
||||
throw new GeneralSecurityException();
|
||||
}
|
||||
// Derive the master secret
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " deriving master secret");
|
||||
return crypto.deriveMasterSecret(key, keyPair, alice);
|
||||
}
|
||||
|
||||
void sendConfirmation(BdfWriter w, boolean confirmed) throws IOException {
|
||||
w.writeBoolean(confirmed);
|
||||
w.flush();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " sent confirmation: " + confirmed);
|
||||
}
|
||||
|
||||
boolean receiveConfirmation(BdfReader r) throws IOException {
|
||||
boolean confirmed = r.readBoolean();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " received confirmation: " + confirmed);
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
protected void tryToClose(DuplexTransportConnection conn,
|
||||
boolean exception) {
|
||||
try {
|
||||
LOG.info("Closing connection");
|
||||
conn.getReader().dispose(exception, true);
|
||||
conn.getWriter().dispose(exception);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
package org.briarproject.bramble.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeListener;
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.invitation.InvitationListener;
|
||||
import org.briarproject.bramble.api.invitation.InvitationState;
|
||||
import org.briarproject.bramble.api.invitation.InvitationTask;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.invitation.InvitationConstants.CONFIRMATION_TIMEOUT;
|
||||
|
||||
/**
|
||||
* A task consisting of one or more parallel connection attempts.
|
||||
*/
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class ConnectorGroup extends Thread implements InvitationTask,
|
||||
ContactExchangeListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ConnectorGroup.class.getName());
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
private final BdfWriterFactory bdfWriterFactory;
|
||||
private final ContactExchangeTask contactExchangeTask;
|
||||
private final IdentityManager identityManager;
|
||||
private final PluginManager pluginManager;
|
||||
private final int localInvitationCode, remoteInvitationCode;
|
||||
private final Collection<InvitationListener> listeners;
|
||||
private final AtomicBoolean connected;
|
||||
private final CountDownLatch localConfirmationLatch;
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
// The following are locking: lock
|
||||
private int localConfirmationCode = -1, remoteConfirmationCode = -1;
|
||||
private boolean connectionFailed = false;
|
||||
private boolean localCompared = false, remoteCompared = false;
|
||||
private boolean localMatched = false, remoteMatched = false;
|
||||
private String remoteName = null;
|
||||
|
||||
ConnectorGroup(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory,
|
||||
ContactExchangeTask contactExchangeTask,
|
||||
IdentityManager identityManager, PluginManager pluginManager,
|
||||
int localInvitationCode, int remoteInvitationCode) {
|
||||
super("ConnectorGroup");
|
||||
this.crypto = crypto;
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.bdfWriterFactory = bdfWriterFactory;
|
||||
this.contactExchangeTask = contactExchangeTask;
|
||||
this.identityManager = identityManager;
|
||||
this.pluginManager = pluginManager;
|
||||
this.localInvitationCode = localInvitationCode;
|
||||
this.remoteInvitationCode = remoteInvitationCode;
|
||||
listeners = new CopyOnWriteArrayList<InvitationListener>();
|
||||
connected = new AtomicBoolean(false);
|
||||
localConfirmationLatch = new CountDownLatch(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvitationState addListener(InvitationListener l) {
|
||||
lock.lock();
|
||||
try {
|
||||
listeners.add(l);
|
||||
return new InvitationState(localInvitationCode,
|
||||
remoteInvitationCode, localConfirmationCode,
|
||||
remoteConfirmationCode, connected.get(), connectionFailed,
|
||||
localCompared, remoteCompared, localMatched, remoteMatched,
|
||||
remoteName);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(InvitationListener l) {
|
||||
listeners.remove(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() {
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
LocalAuthor localAuthor;
|
||||
// Load the local pseudonym
|
||||
try {
|
||||
localAuthor = identityManager.getLocalAuthor();
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
lock.lock();
|
||||
try {
|
||||
connectionFailed = true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (InvitationListener l : listeners) l.connectionFailed();
|
||||
return;
|
||||
}
|
||||
// Start the connection threads
|
||||
Collection<Connector> connectors = new ArrayList<Connector>();
|
||||
// Alice is the party with the smaller invitation code
|
||||
if (localInvitationCode < remoteInvitationCode) {
|
||||
for (DuplexPlugin plugin : pluginManager.getInvitationPlugins()) {
|
||||
Connector c = createAliceConnector(plugin, localAuthor);
|
||||
connectors.add(c);
|
||||
c.start();
|
||||
}
|
||||
} else {
|
||||
for (DuplexPlugin plugin : pluginManager.getInvitationPlugins()) {
|
||||
Connector c = createBobConnector(plugin, localAuthor);
|
||||
connectors.add(c);
|
||||
c.start();
|
||||
}
|
||||
}
|
||||
// Wait for the connection threads to finish
|
||||
try {
|
||||
for (Connector c : connectors) c.join();
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while waiting for connectors");
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
// If none of the threads connected, inform the listeners
|
||||
if (!connected.get()) {
|
||||
lock.lock();
|
||||
try {
|
||||
connectionFailed = true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (InvitationListener l : listeners) l.connectionFailed();
|
||||
}
|
||||
}
|
||||
|
||||
private Connector createAliceConnector(DuplexPlugin plugin,
|
||||
LocalAuthor localAuthor) {
|
||||
PseudoRandom random = crypto.getPseudoRandom(localInvitationCode,
|
||||
remoteInvitationCode);
|
||||
return new AliceConnector(crypto, bdfReaderFactory, bdfWriterFactory,
|
||||
contactExchangeTask, this, plugin, localAuthor, random);
|
||||
}
|
||||
|
||||
private Connector createBobConnector(DuplexPlugin plugin,
|
||||
LocalAuthor localAuthor) {
|
||||
PseudoRandom random = crypto.getPseudoRandom(remoteInvitationCode,
|
||||
localInvitationCode);
|
||||
return new BobConnector(crypto, bdfReaderFactory, bdfWriterFactory,
|
||||
contactExchangeTask, this, plugin, localAuthor, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void localConfirmationSucceeded() {
|
||||
lock.lock();
|
||||
try {
|
||||
localCompared = true;
|
||||
localMatched = true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
localConfirmationLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void localConfirmationFailed() {
|
||||
lock.lock();
|
||||
try {
|
||||
localCompared = true;
|
||||
localMatched = false;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
localConfirmationLatch.countDown();
|
||||
}
|
||||
|
||||
boolean getAndSetConnected() {
|
||||
boolean redundant = connected.getAndSet(true);
|
||||
if (!redundant)
|
||||
for (InvitationListener l : listeners) l.connectionSucceeded();
|
||||
return redundant;
|
||||
}
|
||||
|
||||
void keyAgreementSucceeded(int localCode, int remoteCode) {
|
||||
lock.lock();
|
||||
try {
|
||||
localConfirmationCode = localCode;
|
||||
remoteConfirmationCode = remoteCode;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (InvitationListener l : listeners)
|
||||
l.keyAgreementSucceeded(localCode, remoteCode);
|
||||
}
|
||||
|
||||
void keyAgreementFailed() {
|
||||
for (InvitationListener l : listeners) l.keyAgreementFailed();
|
||||
}
|
||||
|
||||
boolean waitForLocalConfirmationResult() throws InterruptedException {
|
||||
localConfirmationLatch.await(CONFIRMATION_TIMEOUT, MILLISECONDS);
|
||||
lock.lock();
|
||||
try {
|
||||
return localMatched;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void remoteConfirmationSucceeded() {
|
||||
lock.lock();
|
||||
try {
|
||||
remoteCompared = true;
|
||||
remoteMatched = true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (InvitationListener l : listeners) l.remoteConfirmationSucceeded();
|
||||
}
|
||||
|
||||
void remoteConfirmationFailed() {
|
||||
lock.lock();
|
||||
try {
|
||||
remoteCompared = true;
|
||||
remoteMatched = false;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (InvitationListener l : listeners) l.remoteConfirmationFailed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contactExchangeSucceeded(Author remoteAuthor) {
|
||||
String name = remoteAuthor.getName();
|
||||
lock.lock();
|
||||
try {
|
||||
remoteName = name;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (InvitationListener l : listeners)
|
||||
l.pseudonymExchangeSucceeded(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void duplicateContact(Author remoteAuthor) {
|
||||
// TODO differentiate
|
||||
for (InvitationListener l : listeners) l.pseudonymExchangeFailed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contactExchangeFailed() {
|
||||
for (InvitationListener l : listeners) l.pseudonymExchangeFailed();
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.briarproject.bramble.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.invitation.InvitationTaskFactory;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class InvitationModule {
|
||||
|
||||
@Provides
|
||||
InvitationTaskFactory provideInvitationTaskFactory(
|
||||
InvitationTaskFactoryImpl invitationTaskFactory) {
|
||||
return invitationTaskFactory;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package org.briarproject.bramble.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.invitation.InvitationTask;
|
||||
import org.briarproject.bramble.api.invitation.InvitationTaskFactory;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class InvitationTaskFactoryImpl implements InvitationTaskFactory {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
private final BdfWriterFactory bdfWriterFactory;
|
||||
private final ContactExchangeTask contactExchangeTask;
|
||||
private final IdentityManager identityManager;
|
||||
private final PluginManager pluginManager;
|
||||
|
||||
@Inject
|
||||
InvitationTaskFactoryImpl(CryptoComponent crypto,
|
||||
BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory,
|
||||
ContactExchangeTask contactExchangeTask,
|
||||
IdentityManager identityManager, PluginManager pluginManager) {
|
||||
this.crypto = crypto;
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.bdfWriterFactory = bdfWriterFactory;
|
||||
this.contactExchangeTask = contactExchangeTask;
|
||||
this.identityManager = identityManager;
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvitationTask createTask(int localCode, int remoteCode) {
|
||||
return new ConnectorGroup(crypto, bdfReaderFactory, bdfWriterFactory,
|
||||
contactExchangeTask, identityManager, pluginManager,
|
||||
localCode, remoteCode);
|
||||
}
|
||||
}
|
||||
@@ -164,14 +164,6 @@ class PluginManagerImpl implements PluginManager, Service {
|
||||
return new ArrayList<DuplexPlugin>(duplexPlugins);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<DuplexPlugin> getInvitationPlugins() {
|
||||
List<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
|
||||
for (DuplexPlugin d : duplexPlugins)
|
||||
if (d.supportsInvitations()) supported.add(d);
|
||||
return supported;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<DuplexPlugin> getKeyAgreementPlugins() {
|
||||
List<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.bramble.plugin.tcp;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
@@ -281,17 +280,6 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsInvitations() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout, boolean alice) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return false;
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.tcp;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Plugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
|
||||
import org.briarproject.bramble.util.IoUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -24,12 +25,12 @@ class TcpTransportConnection extends AbstractDuplexTransportConnection {
|
||||
|
||||
@Override
|
||||
protected InputStream getInputStream() throws IOException {
|
||||
return socket.getInputStream();
|
||||
return IoUtils.getInputStream(socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OutputStream getOutputStream() throws IOException {
|
||||
return socket.getOutputStream();
|
||||
return IoUtils.getOutputStream(socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.bramble.reporting;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.util.IoUtils;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
@@ -130,7 +131,7 @@ public class DevReportServer {
|
||||
OutputStream out = null;
|
||||
try {
|
||||
socket.setSoTimeout(SOCKET_TIMEOUT_MS);
|
||||
in = socket.getInputStream();
|
||||
in = IoUtils.getInputStream(socket);
|
||||
reportDir.mkdirs();
|
||||
reportFile = File.createTempFile(FILE_PREFIX, FILE_SUFFIX,
|
||||
reportDir);
|
||||
|
||||
@@ -93,7 +93,7 @@ class DevReporterImpl implements DevReporter {
|
||||
InputStream in = null;
|
||||
try {
|
||||
Socket s = connectToDevelopers();
|
||||
out = s.getOutputStream();
|
||||
out = IoUtils.getOutputStream(s);
|
||||
in = new FileInputStream(f);
|
||||
IoUtils.copyAndClose(in, out);
|
||||
f.delete();
|
||||
|
||||
@@ -57,8 +57,8 @@ class SocksSocket extends Socket {
|
||||
|
||||
// Connect to the proxy
|
||||
super.connect(proxy, connectToProxyTimeout);
|
||||
OutputStream out = getOutputStream();
|
||||
InputStream in = getInputStream();
|
||||
OutputStream out = IoUtils.getOutputStream(this);
|
||||
InputStream in = IoUtils.getInputStream(this);
|
||||
|
||||
// Request SOCKS 5 with no authentication
|
||||
sendMethodRequest(out);
|
||||
|
||||
@@ -29,10 +29,10 @@ class StreamReaderFactoryImpl implements StreamReaderFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream createInvitationStreamReader(InputStream in,
|
||||
public InputStream createContactExchangeStreamReader(InputStream in,
|
||||
SecretKey headerKey) {
|
||||
return new StreamReaderImpl(
|
||||
streamDecrypterFactory.createInvitationStreamDecrypter(in,
|
||||
streamDecrypterFactory.createContactExchangeStreamDecrypter(in,
|
||||
headerKey));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,10 @@ class StreamWriterFactoryImpl implements StreamWriterFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream createInvitationStreamWriter(OutputStream out,
|
||||
public OutputStream createContactExchangeStreamWriter(OutputStream out,
|
||||
SecretKey headerKey) {
|
||||
return new StreamWriterImpl(
|
||||
streamEncrypterFactory.createInvitationStreamEncrypter(out,
|
||||
streamEncrypterFactory.createContactExchangeStreamDecrypter(out,
|
||||
headerKey));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.spongycastle.crypto.Digest;
|
||||
import org.spongycastle.crypto.engines.Salsa20Engine;
|
||||
@@ -11,11 +10,11 @@ import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
@NotThreadSafe
|
||||
@NotNullByDefault
|
||||
class PseudoRandomImpl implements PseudoRandom {
|
||||
class PseudoRandom {
|
||||
|
||||
private final Salsa20Engine cipher = new Salsa20Engine();
|
||||
|
||||
PseudoRandomImpl(byte[] seed) {
|
||||
PseudoRandom(byte[] seed) {
|
||||
// Hash the seed to produce a 32-byte key
|
||||
byte[] key = new byte[32];
|
||||
Digest digest = new Blake2sDigest();
|
||||
@@ -26,8 +25,7 @@ class PseudoRandomImpl implements PseudoRandom {
|
||||
cipher.init(true, new ParametersWithIV(new KeyParameter(key), nonce));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] nextBytes(int length) {
|
||||
byte[] nextBytes(int length) {
|
||||
byte[] in = new byte[length], out = new byte[length];
|
||||
cipher.processBytes(in, 0, length, out, 0);
|
||||
return out;
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SecureRandomSpi;
|
||||
@@ -19,7 +17,7 @@ class PseudoSecureRandom extends SecureRandom {
|
||||
private final PseudoRandom pseudoRandom;
|
||||
|
||||
private PseudoSecureRandomSpi(byte[] seed) {
|
||||
pseudoRandom = new PseudoRandomImpl(seed);
|
||||
pseudoRandom = new PseudoRandom(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
@@ -19,33 +18,23 @@ import org.briarproject.bramble.util.OsUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletionService;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.bluetooth.BluetoothStateException;
|
||||
import javax.bluetooth.DiscoveryAgent;
|
||||
import javax.bluetooth.LocalDevice;
|
||||
import javax.microedition.io.Connector;
|
||||
import javax.microedition.io.StreamConnection;
|
||||
import javax.microedition.io.StreamConnectionNotifier;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static javax.bluetooth.DiscoveryAgent.GIAC;
|
||||
@@ -67,7 +56,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
private final Backoff backoff;
|
||||
private final DuplexPluginCallback callback;
|
||||
private final int maxLatency;
|
||||
private final Semaphore discoverySemaphore = new Semaphore(1);
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
private volatile boolean running = false;
|
||||
@@ -273,95 +261,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
return new BluetoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsInvitations() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout, boolean alice) {
|
||||
if (!running) return null;
|
||||
// Use the invitation codes to generate the UUID
|
||||
byte[] b = r.nextBytes(UUID_BYTES);
|
||||
String uuid = UUID.nameUUIDFromBytes(b).toString();
|
||||
String url = makeUrl("localhost", uuid);
|
||||
// Make the device discoverable if possible
|
||||
makeDeviceDiscoverable();
|
||||
// Bind a server socket for receiving invitation connections
|
||||
final StreamConnectionNotifier ss;
|
||||
try {
|
||||
ss = (StreamConnectionNotifier) Connector.open(url);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
if (!running) {
|
||||
tryToClose(ss);
|
||||
return null;
|
||||
}
|
||||
// Create the background tasks
|
||||
CompletionService<StreamConnection> complete =
|
||||
new ExecutorCompletionService<>(ioExecutor);
|
||||
List<Future<StreamConnection>> futures = new ArrayList<>();
|
||||
if (alice) {
|
||||
// Return the first connected socket
|
||||
futures.add(complete.submit(new ListeningTask(ss)));
|
||||
futures.add(complete.submit(new DiscoveryTask(uuid)));
|
||||
} else {
|
||||
// Return the first socket with readable data
|
||||
futures.add(complete.submit(new ReadableTask(
|
||||
new ListeningTask(ss))));
|
||||
futures.add(complete.submit(new ReadableTask(
|
||||
new DiscoveryTask(uuid))));
|
||||
}
|
||||
StreamConnection chosen = null;
|
||||
try {
|
||||
Future<StreamConnection> f = complete.poll(timeout, MILLISECONDS);
|
||||
if (f == null) return null; // No task completed within the timeout
|
||||
chosen = f.get();
|
||||
return new BluetoothTransportConnection(this, chosen);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while exchanging invitations");
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
} catch (ExecutionException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
} finally {
|
||||
// Closing the socket will terminate the listener task
|
||||
tryToClose(ss);
|
||||
closeSockets(futures, chosen);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeSockets(final List<Future<StreamConnection>> futures,
|
||||
@Nullable final StreamConnection chosen) {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Future<StreamConnection> f : futures) {
|
||||
try {
|
||||
if (f.cancel(true)) {
|
||||
LOG.info("Cancelled task");
|
||||
} else {
|
||||
StreamConnection s = f.get();
|
||||
if (s != null && s != chosen) {
|
||||
LOG.info("Closing unwanted socket");
|
||||
s.close();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while closing sockets");
|
||||
return;
|
||||
} catch (ExecutionException | IOException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return true;
|
||||
@@ -376,7 +275,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
String url = makeUrl("localhost", uuid);
|
||||
// Make the device discoverable if possible
|
||||
makeDeviceDiscoverable();
|
||||
// Bind a server socket for receiving invitation connections
|
||||
// Bind a server socket for receiving key agreementconnections
|
||||
final StreamConnectionNotifier ss;
|
||||
try {
|
||||
ss = (StreamConnectionNotifier) Connector.open(url);
|
||||
@@ -431,77 +330,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private class DiscoveryTask implements Callable<StreamConnection> {
|
||||
|
||||
private final String uuid;
|
||||
|
||||
private DiscoveryTask(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamConnection call() throws Exception {
|
||||
// Repeat discovery until we connect or get interrupted
|
||||
DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
|
||||
while (true) {
|
||||
if (!discoverySemaphore.tryAcquire())
|
||||
throw new Exception("Discovery is already in progress");
|
||||
try {
|
||||
InvitationListener listener =
|
||||
new InvitationListener(discoveryAgent, uuid);
|
||||
discoveryAgent.startInquiry(GIAC, listener);
|
||||
String url = listener.waitForUrl();
|
||||
if (url != null) {
|
||||
StreamConnection s = connect(url);
|
||||
if (s != null) {
|
||||
LOG.info("Outgoing connection");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
discoverySemaphore.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ListeningTask implements Callable<StreamConnection> {
|
||||
|
||||
private final StreamConnectionNotifier serverSocket;
|
||||
|
||||
private ListeningTask(StreamConnectionNotifier serverSocket) {
|
||||
this.serverSocket = serverSocket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamConnection call() throws Exception {
|
||||
StreamConnection s = serverSocket.acceptAndOpen();
|
||||
LOG.info("Incoming connection");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReadableTask implements Callable<StreamConnection> {
|
||||
|
||||
private final Callable<StreamConnection> connectionTask;
|
||||
|
||||
private ReadableTask(Callable<StreamConnection> connectionTask) {
|
||||
this.connectionTask = connectionTask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamConnection call() throws Exception {
|
||||
StreamConnection s = connectionTask.call();
|
||||
InputStream in = s.openInputStream();
|
||||
while (in.available() == 0) {
|
||||
LOG.info("Waiting for data");
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
LOG.info("Data available");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
||||
|
||||
private final StreamConnectionNotifier ss;
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.bluetooth.BluetoothStateException;
|
||||
import javax.bluetooth.DataElement;
|
||||
import javax.bluetooth.DeviceClass;
|
||||
import javax.bluetooth.DiscoveryAgent;
|
||||
import javax.bluetooth.DiscoveryListener;
|
||||
import javax.bluetooth.RemoteDevice;
|
||||
import javax.bluetooth.ServiceRecord;
|
||||
import javax.bluetooth.UUID;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
class InvitationListener implements DiscoveryListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(InvitationListener.class.getName());
|
||||
|
||||
private final AtomicInteger searches = new AtomicInteger(1);
|
||||
private final CountDownLatch finished = new CountDownLatch(1);
|
||||
private final DiscoveryAgent discoveryAgent;
|
||||
private final String uuid;
|
||||
|
||||
private volatile String url = null;
|
||||
|
||||
InvitationListener(DiscoveryAgent discoveryAgent, String uuid) {
|
||||
this.discoveryAgent = discoveryAgent;
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
String waitForUrl() throws InterruptedException {
|
||||
finished.await();
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deviceDiscovered(RemoteDevice device, DeviceClass deviceClass) {
|
||||
UUID[] uuids = new UUID[] {new UUID(uuid, false)};
|
||||
// Try to discover the services associated with the UUID
|
||||
try {
|
||||
discoveryAgent.searchServices(null, uuids, device, this);
|
||||
searches.incrementAndGet();
|
||||
} catch (BluetoothStateException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void servicesDiscovered(int transaction, ServiceRecord[] services) {
|
||||
for (ServiceRecord record : services) {
|
||||
// Does this service have a URL?
|
||||
String serviceUrl = record.getConnectionURL(
|
||||
ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
|
||||
if (serviceUrl == null) continue;
|
||||
// Does this service have the UUID we're looking for?
|
||||
Collection<String> uuids = new TreeSet<>();
|
||||
findNestedClassIds(record.getAttributeValue(0x1), uuids);
|
||||
for (String u : uuids) {
|
||||
if (uuid.equalsIgnoreCase(u)) {
|
||||
// The UUID matches - store the URL
|
||||
url = serviceUrl;
|
||||
finished.countDown();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inquiryCompleted(int discoveryType) {
|
||||
if (searches.decrementAndGet() == 0) finished.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serviceSearchCompleted(int transaction, int response) {
|
||||
if (searches.decrementAndGet() == 0) finished.countDown();
|
||||
}
|
||||
|
||||
// UUIDs are sometimes buried in nested data elements
|
||||
private void findNestedClassIds(Object o, Collection<String> ids) {
|
||||
o = getDataElementValue(o);
|
||||
if (o instanceof Enumeration<?>) {
|
||||
for (Object o1 : Collections.list((Enumeration<?>) o))
|
||||
findNestedClassIds(o1, ids);
|
||||
} else if (o instanceof UUID) {
|
||||
ids.add(o.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private Object getDataElementValue(Object o) {
|
||||
if (o instanceof DataElement) {
|
||||
// Bluecove throws an exception if the type is unknown
|
||||
try {
|
||||
return ((DataElement) o).getValue();
|
||||
} catch (ClassCastException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.bramble.plugin.modem;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
@@ -167,17 +166,6 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
|
||||
return new ModemTransportConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsInvitations() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout, boolean alice) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = pt_BR: pt-rBR, fr_FR: fr
|
||||
lang_map = pt_BR: pt-rBR, fr_FR: fr, nb_NO: nb
|
||||
|
||||
[briar.stringsxml-5]
|
||||
file_filter = src/main/res/values-<lang>/strings.xml
|
||||
|
||||
@@ -78,8 +78,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 22
|
||||
versionCode 1609
|
||||
versionName "0.16.9"
|
||||
versionCode 1610
|
||||
versionName "0.16.10"
|
||||
applicationId "org.briarproject.briar.beta"
|
||||
resValue "string", "app_package", "org.briarproject.briar.beta"
|
||||
resValue "string", "app_name", "Briar Beta"
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
<uses-permission android:name="android.permission.READ_LOGS"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<!-- Since API 23, this is needed to add contacts via Bluetooth -->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
<application
|
||||
android:name="org.briarproject.briar.android.BriarApplicationImpl"
|
||||
@@ -292,16 +290,6 @@
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.invitation.AddContactActivity"
|
||||
android:label="@string/add_contact_title"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.keyagreement.KeyAgreementActivity"
|
||||
android:label="@string/add_contact_title"
|
||||
|
||||
@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.invitation.InvitationTaskFactory;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
||||
@@ -89,8 +88,6 @@ public interface AndroidComponent
|
||||
|
||||
EventBus eventBus();
|
||||
|
||||
InvitationTaskFactory invitationTaskFactory();
|
||||
|
||||
AndroidNotificationManager androidNotificationManager();
|
||||
|
||||
ScreenFilterMonitor screenFilterMonitor();
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.briarproject.briar.android.forum.ForumModule;
|
||||
import org.briarproject.briar.android.introduction.ContactChooserFragment;
|
||||
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
||||
import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
|
||||
import org.briarproject.briar.android.invitation.AddContactActivity;
|
||||
import org.briarproject.briar.android.keyagreement.IntroFragment;
|
||||
import org.briarproject.briar.android.keyagreement.KeyAgreementActivity;
|
||||
import org.briarproject.briar.android.keyagreement.ShowQrCodeFragment;
|
||||
@@ -91,8 +90,6 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(PanicPreferencesActivity activity);
|
||||
|
||||
void inject(AddContactActivity activity);
|
||||
|
||||
void inject(KeyAgreementActivity activity);
|
||||
|
||||
void inject(ConversationActivity activity);
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.briarproject.briar.android.threaded.ThreadItemAdapter;
|
||||
import org.briarproject.briar.android.threaded.ThreadListActivity;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController;
|
||||
import org.briarproject.briar.api.forum.Forum;
|
||||
import org.briarproject.briar.api.forum.ForumPostHeader;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
@@ -41,7 +40,7 @@ import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_BOD
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class ForumActivity extends
|
||||
ThreadListActivity<Forum, ThreadItemAdapter<ForumItem>, ForumItem, ForumPostHeader>
|
||||
ThreadListActivity<Forum, ForumItem, ThreadItemAdapter<ForumItem>>
|
||||
implements ForumListener {
|
||||
|
||||
@Inject
|
||||
@@ -53,7 +52,7 @@ public class ForumActivity extends
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThreadListController<Forum, ForumItem, ForumPostHeader> getController() {
|
||||
protected ThreadListController<Forum, ForumItem> getController() {
|
||||
return forumController;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,11 @@ import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController;
|
||||
import org.briarproject.briar.api.forum.Forum;
|
||||
import org.briarproject.briar.api.forum.ForumPostHeader;
|
||||
|
||||
@NotNullByDefault
|
||||
interface ForumController
|
||||
extends ThreadListController<Forum, ForumItem, ForumPostHeader> {
|
||||
interface ForumController extends ThreadListController<Forum, ForumItem> {
|
||||
|
||||
interface ForumListener extends ThreadListListener<ForumPostHeader> {
|
||||
interface ForumListener extends ThreadListListener<ForumItem> {
|
||||
@UiThread
|
||||
void onForumLeft(ContactId c);
|
||||
}
|
||||
|
||||
@@ -75,10 +75,10 @@ class ForumControllerImpl extends
|
||||
super.eventOccurred(e);
|
||||
|
||||
if (e instanceof ForumPostReceivedEvent) {
|
||||
ForumPostReceivedEvent pe = (ForumPostReceivedEvent) e;
|
||||
if (pe.getGroupId().equals(getGroupId())) {
|
||||
ForumPostReceivedEvent f = (ForumPostReceivedEvent) e;
|
||||
if (f.getGroupId().equals(getGroupId())) {
|
||||
LOG.info("Forum post received, adding...");
|
||||
onForumPostHeaderReceived(pe.getForumPostHeader());
|
||||
onForumPostReceived(f.getHeader(), f.getBody());
|
||||
}
|
||||
} else if (e instanceof ForumInvitationResponseReceivedEvent) {
|
||||
ForumInvitationResponseReceivedEvent f =
|
||||
@@ -90,10 +90,10 @@ class ForumControllerImpl extends
|
||||
onForumInvitationAccepted(r.getContactId());
|
||||
}
|
||||
} else if (e instanceof ContactLeftShareableEvent) {
|
||||
ContactLeftShareableEvent s = (ContactLeftShareableEvent) e;
|
||||
if (s.getGroupId().equals(getGroupId())) {
|
||||
ContactLeftShareableEvent c = (ContactLeftShareableEvent) e;
|
||||
if (c.getGroupId().equals(getGroupId())) {
|
||||
LOG.info("Forum left by contact");
|
||||
onForumLeft(s.getContactId());
|
||||
onForumLeft(c.getContactId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,11 +195,12 @@ class ForumControllerImpl extends
|
||||
return new ForumItem(header, body);
|
||||
}
|
||||
|
||||
private void onForumPostHeaderReceived(final ForumPostHeader h) {
|
||||
private void onForumPostReceived(ForumPostHeader h, String body) {
|
||||
final ForumItem item = buildItem(h, body);
|
||||
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onHeaderReceived(h);
|
||||
listener.onItemReceived(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ public class ForumListFragment extends BaseEventFragment implements
|
||||
} else if (e instanceof ForumPostReceivedEvent) {
|
||||
ForumPostReceivedEvent f = (ForumPostReceivedEvent) e;
|
||||
LOG.info("Forum post added, updating item");
|
||||
updateItem(f.getGroupId(), f.getForumPostHeader());
|
||||
updateItem(f.getGroupId(), f.getHeader());
|
||||
} else if (e instanceof ForumInvitationRequestReceivedEvent) {
|
||||
LOG.info("Forum invitation received, reloading available forums");
|
||||
loadAvailableForums();
|
||||
|
||||
@@ -1,449 +0,0 @@
|
||||
package org.briarproject.briar.android.invitation;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.invitation.InvitationListener;
|
||||
import org.briarproject.bramble.api.invitation.InvitationState;
|
||||
import org.briarproject.bramble.api.invitation.InvitationTask;
|
||||
import org.briarproject.bramble.api.invitation.InvitationTaskFactory;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.api.android.ReferenceManager;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH;
|
||||
import static org.briarproject.briar.android.invitation.ConfirmationCodeView.ConfirmationState.CONNECTED;
|
||||
import static org.briarproject.briar.android.invitation.ConfirmationCodeView.ConfirmationState.DETAILS;
|
||||
import static org.briarproject.briar.android.invitation.ConfirmationCodeView.ConfirmationState.WAIT_FOR_CONTACT;
|
||||
|
||||
public class AddContactActivity extends BriarActivity
|
||||
implements InvitationListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(AddContactActivity.class.getName());
|
||||
|
||||
@Inject
|
||||
CryptoComponent crypto;
|
||||
@Inject
|
||||
InvitationTaskFactory invitationTaskFactory;
|
||||
@Inject
|
||||
ReferenceManager referenceManager;
|
||||
|
||||
private AddContactView view = null;
|
||||
private InvitationTask task = null;
|
||||
private long taskHandle = -1;
|
||||
private AuthorId localAuthorId = null;
|
||||
private int localInvitationCode = -1, remoteInvitationCode = -1;
|
||||
private int localConfirmationCode = -1, remoteConfirmationCode = -1;
|
||||
private boolean connected = false, connectionFailed = false;
|
||||
private boolean localCompared = false, remoteCompared = false;
|
||||
private boolean localMatched = false, remoteMatched = false;
|
||||
private String contactName = null;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject
|
||||
volatile IdentityManager identityManager;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
if (state == null) {
|
||||
// This is a new activity
|
||||
setView(new ChooseIdentityView(this));
|
||||
} else {
|
||||
// Restore the activity's state
|
||||
byte[] b = state.getByteArray("briar.LOCAL_AUTHOR_ID");
|
||||
if (b != null) localAuthorId = new AuthorId(b);
|
||||
taskHandle = state.getLong("briar.TASK_HANDLE", -1);
|
||||
task = referenceManager.getReference(taskHandle,
|
||||
InvitationTask.class);
|
||||
|
||||
if (task == null) {
|
||||
// No background task - we must be in an initial or final state
|
||||
localInvitationCode = state.getInt("briar.LOCAL_CODE");
|
||||
remoteInvitationCode = state.getInt("briar.REMOTE_CODE");
|
||||
connectionFailed = state.getBoolean("briar.FAILED");
|
||||
contactName = state.getString("briar.CONTACT_NAME");
|
||||
if (contactName != null) {
|
||||
localCompared = remoteCompared = true;
|
||||
localMatched = remoteMatched = true;
|
||||
}
|
||||
// Set the appropriate view for the state
|
||||
if (localInvitationCode == -1) {
|
||||
setView(new ChooseIdentityView(this));
|
||||
} else if (remoteInvitationCode == -1) {
|
||||
setView(new InvitationCodeView(this));
|
||||
} else if (connectionFailed) {
|
||||
setView(new ErrorView(this, R.string.connection_failed,
|
||||
R.string.could_not_find_contact));
|
||||
} else if (contactName == null) {
|
||||
setView(new ErrorView(this, R.string.codes_do_not_match,
|
||||
R.string.interfering));
|
||||
} else {
|
||||
showToastAndFinish();
|
||||
}
|
||||
} else {
|
||||
// A background task exists - listen to it and get its state
|
||||
InvitationState s = task.addListener(this);
|
||||
localInvitationCode = s.getLocalInvitationCode();
|
||||
remoteInvitationCode = s.getRemoteInvitationCode();
|
||||
localConfirmationCode = s.getLocalConfirmationCode();
|
||||
remoteConfirmationCode = s.getRemoteConfirmationCode();
|
||||
connected = s.getConnected();
|
||||
connectionFailed = s.getConnectionFailed();
|
||||
localCompared = s.getLocalCompared();
|
||||
remoteCompared = s.getRemoteCompared();
|
||||
localMatched = s.getLocalMatched();
|
||||
remoteMatched = s.getRemoteMatched();
|
||||
contactName = s.getContactName();
|
||||
// Set the appropriate view for the state
|
||||
if (localInvitationCode == -1) {
|
||||
setView(new ChooseIdentityView(this));
|
||||
} else if (remoteInvitationCode == -1) {
|
||||
setView(new InvitationCodeView(this));
|
||||
} else if (connectionFailed) {
|
||||
setView(new ErrorView(AddContactActivity.this,
|
||||
R.string.connection_failed,
|
||||
R.string.could_not_find_contact));
|
||||
} else if (connected && localConfirmationCode == -1) {
|
||||
setView(new ConfirmationCodeView(this, CONNECTED));
|
||||
} else if (localConfirmationCode == -1) {
|
||||
setView(new InvitationCodeView(this, true));
|
||||
} else if (!localCompared) {
|
||||
setView(new ConfirmationCodeView(this));
|
||||
} else if (!remoteCompared) {
|
||||
setView(new ConfirmationCodeView(this, WAIT_FOR_CONTACT));
|
||||
} else if (localMatched && remoteMatched) {
|
||||
if (contactName == null) {
|
||||
setView(new ConfirmationCodeView(this, DETAILS));
|
||||
} else {
|
||||
showToastAndFinish();
|
||||
}
|
||||
} else {
|
||||
setView(new ErrorView(this, R.string.codes_do_not_match,
|
||||
R.string.interfering));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
private void showToastAndFinish() {
|
||||
String format = getString(R.string.contact_added_toast);
|
||||
String text = String.format(format, contactName);
|
||||
Toast.makeText(this, text, LENGTH_LONG).show();
|
||||
supportFinishAfterTransition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
view.populate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle state) {
|
||||
super.onSaveInstanceState(state);
|
||||
if (localAuthorId != null) {
|
||||
byte[] b = localAuthorId.getBytes();
|
||||
state.putByteArray("briar.LOCAL_AUTHOR_ID", b);
|
||||
}
|
||||
state.putInt("briar.LOCAL_CODE", localInvitationCode);
|
||||
state.putInt("briar.REMOTE_CODE", remoteInvitationCode);
|
||||
state.putBoolean("briar.FAILED", connectionFailed);
|
||||
state.putString("briar.CONTACT_NAME", contactName);
|
||||
if (task != null) state.putLong("briar.TASK_HANDLE", taskHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (task != null) task.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int request, int result, Intent data) {
|
||||
if (request == REQUEST_BLUETOOTH) {
|
||||
if (result != RESULT_CANCELED) reset(new InvitationCodeView(this));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
void setView(AddContactView view) {
|
||||
this.view = view;
|
||||
view.init(this);
|
||||
setContentView(view);
|
||||
getSupportActionBar().setTitle(R.string.add_contact_title);
|
||||
}
|
||||
|
||||
void reset(AddContactView view) {
|
||||
// Don't reset localAuthorId
|
||||
task = null;
|
||||
taskHandle = -1;
|
||||
localInvitationCode = -1;
|
||||
localConfirmationCode = remoteConfirmationCode = -1;
|
||||
connected = connectionFailed = false;
|
||||
localCompared = remoteCompared = false;
|
||||
localMatched = remoteMatched = false;
|
||||
contactName = null;
|
||||
setView(view);
|
||||
}
|
||||
|
||||
void loadLocalAuthor() {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
LocalAuthor author = identityManager.getLocalAuthor();
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading author took " + duration + " ms");
|
||||
setLocalAuthorId(author.getId());
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void setLocalAuthorId(final AuthorId localAuthorId) {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AddContactActivity.this.localAuthorId = localAuthorId;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int getLocalInvitationCode() {
|
||||
if (localInvitationCode == -1)
|
||||
localInvitationCode = crypto.generateBTInvitationCode();
|
||||
return localInvitationCode;
|
||||
}
|
||||
|
||||
int getRemoteInvitationCode() {
|
||||
return remoteInvitationCode;
|
||||
}
|
||||
|
||||
void remoteInvitationCodeEntered(int code) {
|
||||
if (localAuthorId == null) throw new IllegalStateException();
|
||||
if (localInvitationCode == -1) throw new IllegalStateException();
|
||||
remoteInvitationCode = code;
|
||||
|
||||
// change UI to show a progress indicator
|
||||
setView(new InvitationCodeView(this, true));
|
||||
|
||||
task = invitationTaskFactory.createTask(localInvitationCode, code);
|
||||
taskHandle = referenceManager.putReference(task, InvitationTask.class);
|
||||
task.addListener(AddContactActivity.this);
|
||||
// Add a second listener so we can remove the first in onDestroy(),
|
||||
// allowing the activity to be garbage collected if it's destroyed
|
||||
task.addListener(new ReferenceCleaner(referenceManager, taskHandle));
|
||||
task.connect();
|
||||
}
|
||||
|
||||
int getLocalConfirmationCode() {
|
||||
return localConfirmationCode;
|
||||
}
|
||||
|
||||
void remoteConfirmationCodeEntered(int code) {
|
||||
localCompared = true;
|
||||
if (code == remoteConfirmationCode) {
|
||||
localMatched = true;
|
||||
if (remoteMatched) {
|
||||
setView(new ConfirmationCodeView(this, DETAILS));
|
||||
} else if (remoteCompared) {
|
||||
setView(new ErrorView(this, R.string.codes_do_not_match,
|
||||
R.string.interfering));
|
||||
} else {
|
||||
setView(new ConfirmationCodeView(this, WAIT_FOR_CONTACT));
|
||||
}
|
||||
task.localConfirmationSucceeded();
|
||||
} else {
|
||||
localMatched = false;
|
||||
setView(new ErrorView(this, R.string.codes_do_not_match,
|
||||
R.string.interfering));
|
||||
task.localConfirmationFailed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionSucceeded() {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
connected = true;
|
||||
setView(new ConfirmationCodeView(AddContactActivity.this,
|
||||
CONNECTED));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionFailed() {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
connectionFailed = true;
|
||||
setView(new ErrorView(AddContactActivity.this,
|
||||
R.string.connection_failed,
|
||||
R.string.could_not_find_contact));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyAgreementSucceeded(final int localCode,
|
||||
final int remoteCode) {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
localConfirmationCode = localCode;
|
||||
remoteConfirmationCode = remoteCode;
|
||||
setView(new ConfirmationCodeView(AddContactActivity.this));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyAgreementFailed() {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
connectionFailed = true;
|
||||
setView(new ErrorView(AddContactActivity.this,
|
||||
R.string.connection_failed,
|
||||
R.string.could_not_find_contact));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remoteConfirmationSucceeded() {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
remoteCompared = true;
|
||||
remoteMatched = true;
|
||||
if (localMatched) {
|
||||
setView(new ConfirmationCodeView(AddContactActivity.this,
|
||||
DETAILS));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remoteConfirmationFailed() {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
remoteCompared = true;
|
||||
remoteMatched = false;
|
||||
if (localMatched) {
|
||||
setView(new ErrorView(AddContactActivity.this,
|
||||
R.string.codes_do_not_match, R.string.interfering));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pseudonymExchangeSucceeded(final String remoteName) {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
contactName = remoteName;
|
||||
showToastAndFinish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pseudonymExchangeFailed() {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setView(new ErrorView(AddContactActivity.this,
|
||||
R.string.connection_failed,
|
||||
R.string.could_not_find_contact));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the reference to the invitation task when the task completes.
|
||||
* This class is static to prevent memory leaks.
|
||||
*/
|
||||
private static class ReferenceCleaner implements InvitationListener {
|
||||
|
||||
private final ReferenceManager referenceManager;
|
||||
private final long handle;
|
||||
|
||||
private ReferenceCleaner(ReferenceManager referenceManager,
|
||||
long handle) {
|
||||
this.referenceManager = referenceManager;
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionSucceeded() {
|
||||
// Wait for key agreement to succeed or fail
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionFailed() {
|
||||
referenceManager.removeReference(handle, InvitationTask.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyAgreementSucceeded(int localCode, int remoteCode) {
|
||||
// Wait for remote confirmation to succeed or fail
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyAgreementFailed() {
|
||||
referenceManager.removeReference(handle, InvitationTask.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remoteConfirmationSucceeded() {
|
||||
// Wait for the pseudonym exchange to succeed or fail
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remoteConfirmationFailed() {
|
||||
referenceManager.removeReference(handle, InvitationTask.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pseudonymExchangeSucceeded(String remoteName) {
|
||||
referenceManager.removeReference(handle, InvitationTask.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pseudonymExchangeFailed() {
|
||||
referenceManager.removeReference(handle, InvitationTask.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.briarproject.briar.android.invitation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
abstract class AddContactView extends LinearLayout {
|
||||
|
||||
static final public int CODE_LEN = 6;
|
||||
protected AddContactActivity container = null;
|
||||
|
||||
AddContactView(Context ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
void init(AddContactActivity container) {
|
||||
this.container = container;
|
||||
populate();
|
||||
}
|
||||
|
||||
abstract void populate();
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package org.briarproject.briar.android.invitation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH;
|
||||
|
||||
class ChooseIdentityView extends AddContactView implements OnClickListener {
|
||||
|
||||
ChooseIdentityView(Context ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
void populate() {
|
||||
removeAllViews();
|
||||
Context ctx = getContext();
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) ctx.getSystemService
|
||||
(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View view = inflater.inflate(R.layout.invitation_bluetooth_start, this);
|
||||
|
||||
Button continueButton = (Button) view.findViewById(R.id.continueButton);
|
||||
continueButton.setOnClickListener(this);
|
||||
|
||||
container.loadLocalAuthor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
|
||||
i.putExtra(EXTRA_DISCOVERABLE_DURATION, 120);
|
||||
container.startActivityForResult(i, REQUEST_BLUETOOTH);
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package org.briarproject.briar.android.invitation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import static android.content.Context.INPUT_METHOD_SERVICE;
|
||||
|
||||
class ConfirmationCodeView extends AddContactView {
|
||||
|
||||
public enum ConfirmationState { CONNECTED, ENTER_CODE, WAIT_FOR_CONTACT, DETAILS }
|
||||
private ConfirmationState state;
|
||||
|
||||
ConfirmationCodeView(Context ctx) {
|
||||
super(ctx);
|
||||
this.state = ConfirmationState.ENTER_CODE;
|
||||
}
|
||||
|
||||
ConfirmationCodeView(Context ctx, ConfirmationState state) {
|
||||
super(ctx);
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
void populate() {
|
||||
removeAllViews();
|
||||
Context ctx = getContext();
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) ctx.getSystemService
|
||||
(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View view = inflater.inflate(R.layout.invitation_bluetooth_confirmation_code, this);
|
||||
|
||||
// local confirmation code
|
||||
TextView code = (TextView) view.findViewById(R.id.codeView);
|
||||
int localCode = container.getLocalConfirmationCode();
|
||||
code.setText(String.format("%06d", localCode));
|
||||
|
||||
if (state != ConfirmationState.ENTER_CODE) {
|
||||
// hide views we no longer need
|
||||
view.findViewById(R.id.enterCodeTextView).setVisibility(View.GONE);
|
||||
view.findViewById(R.id.codeEntryView).setVisibility(View.GONE);
|
||||
view.findViewById(R.id.continueButton).setVisibility(View.GONE);
|
||||
|
||||
// show progress indicator
|
||||
view.findViewById(R.id.progressBar).setVisibility(View.VISIBLE);
|
||||
|
||||
// show what we are waiting for
|
||||
TextView connecting = (TextView) view.findViewById(R.id.waitingView);
|
||||
int textId;
|
||||
if (state == ConfirmationState.CONNECTED) {
|
||||
textId = R.string.calculating_confirmation_code;
|
||||
view.findViewById(R.id.yourConfirmationCodeView).setVisibility(View.GONE);
|
||||
view.findViewById(R.id.codeView).setVisibility(View.GONE);
|
||||
} else if (state == ConfirmationState.WAIT_FOR_CONTACT) {
|
||||
textId = R.string.waiting_for_contact;
|
||||
} else {
|
||||
textId = R.string.exchanging_contact_details;
|
||||
}
|
||||
connecting.setText(ctx.getString(textId));
|
||||
connecting.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else {
|
||||
// handle click on continue button
|
||||
final EditText codeEntry = (EditText) view.findViewById(R.id.codeEntryView);
|
||||
final Button continueButton = (Button) view.findViewById(R.id.continueButton);
|
||||
continueButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
send(codeEntry);
|
||||
}
|
||||
});
|
||||
|
||||
// activate continue button only when we have a 6 digit (CODE_LEN) code
|
||||
codeEntry.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
continueButton.setEnabled(codeEntry.getText().length() == CODE_LEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
});
|
||||
|
||||
codeEntry.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if (actionId == EditorInfo.IME_ACTION_GO && v.getText().length() == CODE_LEN) {
|
||||
send(v);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void send(TextView codeEntry) {
|
||||
int code = Integer.parseInt(codeEntry.getText().toString());
|
||||
container.remoteConfirmationCodeEntered(code);
|
||||
|
||||
// Hide the soft keyboard
|
||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||
((InputMethodManager) o).hideSoftInputFromWindow(codeEntry.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package org.briarproject.briar.android.invitation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH;
|
||||
|
||||
class ErrorView extends AddContactView implements OnClickListener {
|
||||
|
||||
private final int error;
|
||||
private final int explanation;
|
||||
|
||||
ErrorView(Context ctx) {
|
||||
super(ctx);
|
||||
this.error = R.string.connection_failed;
|
||||
this.explanation = R.string.could_not_find_contact;
|
||||
}
|
||||
|
||||
ErrorView(Context ctx, int error, int explanation) {
|
||||
super(ctx);
|
||||
this.error = error;
|
||||
this.explanation = explanation;
|
||||
}
|
||||
|
||||
@Override
|
||||
void populate() {
|
||||
removeAllViews();
|
||||
Context ctx = getContext();
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) ctx.getSystemService
|
||||
(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View view = inflater.inflate(R.layout.invitation_error, this);
|
||||
|
||||
TextView errorView = (TextView) view.findViewById(R.id.errorTextView);
|
||||
errorView.setText(ctx.getString(error));
|
||||
|
||||
TextView explanationView = (TextView) view.findViewById(R.id.explanationTextView);
|
||||
explanationView.setText(ctx.getString(explanation));
|
||||
|
||||
Button tryAgainButton = (Button) view.findViewById(R.id.tryAgainButton);
|
||||
tryAgainButton.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
|
||||
i.putExtra(EXTRA_DISCOVERABLE_DURATION, 120);
|
||||
container.startActivityForResult(i, REQUEST_BLUETOOTH);
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
package org.briarproject.briar.android.invitation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import static android.content.Context.INPUT_METHOD_SERVICE;
|
||||
|
||||
class InvitationCodeView extends AddContactView {
|
||||
|
||||
private boolean waiting;
|
||||
|
||||
InvitationCodeView(Context ctx, boolean waiting) {
|
||||
super(ctx);
|
||||
this.waiting = waiting;
|
||||
}
|
||||
|
||||
InvitationCodeView(Context ctx) {
|
||||
this(ctx, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
void populate() {
|
||||
removeAllViews();
|
||||
Context ctx = getContext();
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) ctx.getSystemService
|
||||
(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View view = inflater.inflate(R.layout.invitation_bluetooth_invitation_code, this);
|
||||
|
||||
// local invitation code
|
||||
TextView code = (TextView) view.findViewById(R.id.codeView);
|
||||
int localCode = container.getLocalInvitationCode();
|
||||
code.setText(String.format("%06d", localCode));
|
||||
|
||||
if (waiting) {
|
||||
// hide views we no longer need
|
||||
view.findViewById(R.id.enterCodeTextView).setVisibility(View.GONE);
|
||||
view.findViewById(R.id.codeEntryView).setVisibility(View.GONE);
|
||||
view.findViewById(R.id.continueButton).setVisibility(View.GONE);
|
||||
|
||||
// show progress indicator
|
||||
view.findViewById(R.id.progressBar).setVisibility(View.VISIBLE);
|
||||
|
||||
// show which code we are waiting for
|
||||
TextView connecting = (TextView) view.findViewById(R.id.waitingView);
|
||||
int remoteCode = container.getRemoteInvitationCode();
|
||||
String format = container.getString(R.string.searching_format);
|
||||
connecting.setText(String.format(format, remoteCode));
|
||||
connecting.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else {
|
||||
// handle click on continue button
|
||||
final EditText codeEntry = (EditText) view.findViewById(R.id.codeEntryView);
|
||||
final Button continueButton = (Button) view.findViewById(R.id.continueButton);
|
||||
continueButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
send(codeEntry);
|
||||
}
|
||||
});
|
||||
|
||||
// activate continue button only when we have a 6 digit (CODE_LEN) code
|
||||
codeEntry.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
continueButton.setEnabled(codeEntry.getText().length() == CODE_LEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
});
|
||||
|
||||
codeEntry.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if (actionId == EditorInfo.IME_ACTION_GO && v.getText().length() == CODE_LEN) {
|
||||
send(v);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void send(TextView codeEntry) {
|
||||
int code = Integer.parseInt(codeEntry.getText().toString());
|
||||
container.remoteInvitationCodeEntered(code);
|
||||
|
||||
// Hide the soft keyboard
|
||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||
((InputMethodManager) o).hideSoftInputFromWindow(codeEntry.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,7 +29,6 @@ import org.briarproject.briar.android.privategroup.memberlist.GroupMemberListAct
|
||||
import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity;
|
||||
import org.briarproject.briar.android.threaded.ThreadListActivity;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||
import org.briarproject.briar.api.privategroup.Visibility;
|
||||
|
||||
@@ -44,7 +43,7 @@ import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class GroupActivity extends
|
||||
ThreadListActivity<PrivateGroup, GroupMessageAdapter, GroupMessageItem, GroupMessageHeader>
|
||||
ThreadListActivity<PrivateGroup, GroupMessageItem, GroupMessageAdapter>
|
||||
implements GroupListener, OnClickListener {
|
||||
|
||||
@Inject
|
||||
@@ -60,7 +59,7 @@ public class GroupActivity extends
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThreadListController<PrivateGroup, GroupMessageItem, GroupMessageHeader> getController() {
|
||||
protected ThreadListController<PrivateGroup, GroupMessageItem> getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
@@ -276,7 +275,7 @@ public class GroupActivity extends
|
||||
public void onGroupDissolved() {
|
||||
setGroupEnabled(false);
|
||||
AlertDialog.Builder builder =
|
||||
new AlertDialog.Builder(this, R.style.BriarDialogTheme);
|
||||
new AlertDialog.Builder(this, R.style.BriarDialogTheme);
|
||||
builder.setTitle(getString(R.string.groups_dissolved_dialog_title));
|
||||
builder.setMessage(getString(R.string.groups_dissolved_dialog_message));
|
||||
builder.setNeutralButton(R.string.ok, null);
|
||||
|
||||
@@ -8,13 +8,11 @@ import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||
import org.briarproject.briar.api.privategroup.Visibility;
|
||||
|
||||
public interface GroupController
|
||||
extends
|
||||
ThreadListController<PrivateGroup, GroupMessageItem, GroupMessageHeader> {
|
||||
extends ThreadListController<PrivateGroup, GroupMessageItem> {
|
||||
|
||||
void loadLocalAuthor(
|
||||
ResultExceptionHandler<LocalAuthor, DbException> handler);
|
||||
@@ -22,7 +20,8 @@ public interface GroupController
|
||||
void isDissolved(
|
||||
ResultExceptionHandler<Boolean, DbException> handler);
|
||||
|
||||
interface GroupListener extends ThreadListListener<GroupMessageHeader> {
|
||||
interface GroupListener extends ThreadListListener<GroupMessageItem> {
|
||||
|
||||
@UiThread
|
||||
void onContactRelationshipRevealed(AuthorId memberId,
|
||||
ContactId contactId, Visibility v);
|
||||
|
||||
@@ -80,14 +80,15 @@ class GroupControllerImpl extends
|
||||
super.eventOccurred(e);
|
||||
|
||||
if (e instanceof GroupMessageAddedEvent) {
|
||||
GroupMessageAddedEvent gmae = (GroupMessageAddedEvent) e;
|
||||
if (!gmae.isLocal() && gmae.getGroupId().equals(getGroupId())) {
|
||||
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
|
||||
if (!g.isLocal() && g.getGroupId().equals(getGroupId())) {
|
||||
LOG.info("Group message received, adding...");
|
||||
final GroupMessageHeader h = gmae.getHeader();
|
||||
final GroupMessageItem item =
|
||||
buildItem(g.getHeader(), g.getBody());
|
||||
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onHeaderReceived(h);
|
||||
listener.onItemReceived(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.acra.ACRA;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
@@ -46,6 +47,7 @@ import static android.media.RingtoneManager.EXTRA_RINGTONE_TITLE;
|
||||
import static android.media.RingtoneManager.EXTRA_RINGTONE_TYPE;
|
||||
import static android.media.RingtoneManager.TYPE_NOTIFICATION;
|
||||
import static android.provider.Settings.System.DEFAULT_NOTIFICATION_URI;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
|
||||
@@ -400,10 +402,15 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
} else {
|
||||
// The user chose a ringtone other than the default
|
||||
Ringtone r = RingtoneManager.getRingtone(getContext(), uri);
|
||||
String name = r.getTitle(getContext());
|
||||
s.putBoolean(PREF_NOTIFY_SOUND, true);
|
||||
s.put(PREF_NOTIFY_RINGTONE_NAME, name);
|
||||
s.put(PREF_NOTIFY_RINGTONE_URI, uri.toString());
|
||||
if (r == null) {
|
||||
Toast.makeText(getContext(), R.string.cannot_load_ringtone,
|
||||
LENGTH_SHORT).show();
|
||||
} else {
|
||||
String name = r.getTitle(getContext());
|
||||
s.putBoolean(PREF_NOTIFY_SOUND, true);
|
||||
s.put(PREF_NOTIFY_RINGTONE_NAME, name);
|
||||
s.put(PREF_NOTIFY_RINGTONE_URI, uri.toString());
|
||||
}
|
||||
}
|
||||
storeSettings(s);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ package org.briarproject.briar.android.threaded;
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.client.MessageTree;
|
||||
import org.briarproject.briar.api.client.MessageTree.MessageNode;
|
||||
import org.briarproject.briar.client.MessageTreeImpl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -13,8 +15,7 @@ import java.util.List;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
public class NestedTreeList<T extends MessageTree.MessageNode>
|
||||
implements Iterable<T> {
|
||||
public class NestedTreeList<T extends MessageNode> implements Iterable<T> {
|
||||
|
||||
private final MessageTree<T> tree = new MessageTreeImpl<>();
|
||||
private List<T> depthFirstCollection = new ArrayList<>();
|
||||
@@ -38,14 +39,14 @@ public class NestedTreeList<T extends MessageTree.MessageNode>
|
||||
return depthFirstCollection.get(index);
|
||||
}
|
||||
|
||||
public int indexOf(T elem) {
|
||||
return depthFirstCollection.indexOf(elem);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return depthFirstCollection.size();
|
||||
}
|
||||
|
||||
public boolean contains(MessageId m) {
|
||||
return tree.contains(m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return depthFirstCollection.iterator();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.briar.android.threaded;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
@@ -28,7 +27,6 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
protected final NestedTreeList<I> items = new NestedTreeList<>();
|
||||
private final ThreadItemListener<I> listener;
|
||||
private final LinearLayoutManager layoutManager;
|
||||
private final Handler handler = new Handler();
|
||||
|
||||
private volatile int revision = 0;
|
||||
|
||||
@@ -104,6 +102,10 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
return NO_POSITION; // Not found
|
||||
}
|
||||
|
||||
boolean contains(MessageId m) {
|
||||
return items.contains(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights the item with the given {@link MessageId}
|
||||
* and disables the highlight for a previously highlighted item, if any.
|
||||
@@ -184,6 +186,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
}
|
||||
|
||||
static class UnreadCount {
|
||||
|
||||
final int top, bottom;
|
||||
|
||||
private UnreadCount(int top, int bottom) {
|
||||
@@ -193,6 +196,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
}
|
||||
|
||||
public interface ThreadItemListener<I> {
|
||||
|
||||
void onUnreadItemVisible(I item);
|
||||
|
||||
void onReplyClick(I item);
|
||||
|
||||
@@ -16,6 +16,7 @@ public class ThreadItemListImpl<I extends ThreadItem> extends ArrayList<I>
|
||||
return bottomVisibleItemId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFirstVisibleId(@Nullable MessageId bottomVisibleItemId) {
|
||||
this.bottomVisibleItemId = bottomVisibleItemId;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.briarproject.briar.android.view.TextInputView;
|
||||
import org.briarproject.briar.android.view.TextInputView.TextInputListener;
|
||||
import org.briarproject.briar.android.view.UnreadMessageButton;
|
||||
import org.briarproject.briar.api.client.NamedGroup;
|
||||
import org.briarproject.briar.api.client.PostHeader;
|
||||
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -49,9 +48,9 @@ import static org.briarproject.briar.android.threaded.ThreadItemAdapter.UnreadCo
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadItemAdapter<I>, I extends ThreadItem, H extends PostHeader>
|
||||
public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadItem, A extends ThreadItemAdapter<I>>
|
||||
extends BriarActivity
|
||||
implements ThreadListListener<H>, TextInputListener, SharingListener,
|
||||
implements ThreadListListener<I>, TextInputListener, SharingListener,
|
||||
ThreadItemListener<I>, ThreadListDataSource {
|
||||
|
||||
protected static final String KEY_REPLY_ID = "replyId";
|
||||
@@ -68,7 +67,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
@Nullable
|
||||
private MessageId replyId;
|
||||
|
||||
protected abstract ThreadListController<G, I, H> getController();
|
||||
protected abstract ThreadListController<G, I> getController();
|
||||
|
||||
@Inject
|
||||
protected SharingController sharingController;
|
||||
@@ -190,8 +189,8 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
if (items.isEmpty()) {
|
||||
list.showData();
|
||||
} else {
|
||||
initList(items);
|
||||
updateTextInput(replyId);
|
||||
displayItems(items);
|
||||
updateTextInput();
|
||||
}
|
||||
} else {
|
||||
LOG.info("Concurrent update, reloading");
|
||||
@@ -206,7 +205,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
});
|
||||
}
|
||||
|
||||
private void initList(final ThreadItemList<I> items) {
|
||||
private void displayItems(final ThreadItemList<I> items) {
|
||||
adapter.setItems(items);
|
||||
MessageId messageId = items.getFirstVisibleItemId();
|
||||
if (messageId != null)
|
||||
@@ -253,9 +252,8 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
ThreadItem replyItem = adapter.getHighlightedItem();
|
||||
if (replyItem != null) {
|
||||
outState.putByteArray(KEY_REPLY_ID, replyItem.getId().getBytes());
|
||||
if (replyId != null) {
|
||||
outState.putByteArray(KEY_REPLY_ID, replyId.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +271,9 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (adapter.getHighlightedItem() != null) {
|
||||
updateTextInput(null);
|
||||
textInput.setText("");
|
||||
replyId = null;
|
||||
updateTextInput();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
@@ -289,7 +289,8 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
|
||||
@Override
|
||||
public void onReplyClick(final I item) {
|
||||
updateTextInput(item.getId());
|
||||
replyId = item.getId();
|
||||
updateTextInput();
|
||||
if (textInput.isKeyboardOpen()) {
|
||||
scrollToItemAtTop(item);
|
||||
} else {
|
||||
@@ -339,15 +340,15 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
snackbar.show();
|
||||
}
|
||||
|
||||
private void updateTextInput(@Nullable MessageId replyItemId) {
|
||||
if (replyItemId != null) {
|
||||
private void updateTextInput() {
|
||||
if (replyId != null) {
|
||||
textInput.setHint(R.string.forum_message_reply_hint);
|
||||
textInput.requestFocus();
|
||||
textInput.showSoftKeyboard();
|
||||
} else {
|
||||
textInput.setHint(R.string.forum_new_message_hint);
|
||||
}
|
||||
adapter.setHighlightedItem(replyItemId);
|
||||
adapter.setHighlightedItem(replyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -374,25 +375,15 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
getController().createAndStoreMessage(text, replyItem, handler);
|
||||
textInput.hideSoftKeyboard();
|
||||
textInput.setText("");
|
||||
updateTextInput(null);
|
||||
replyId = null;
|
||||
updateTextInput();
|
||||
}
|
||||
|
||||
protected abstract int getMaxBodyLength();
|
||||
|
||||
@Override
|
||||
public void onHeaderReceived(H header) {
|
||||
getController().loadItem(header,
|
||||
new UiResultExceptionHandler<I, DbException>(this) {
|
||||
@Override
|
||||
public void onResultUi(final I result) {
|
||||
addItem(result, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
public void onItemReceived(I item) {
|
||||
addItem(item, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -400,8 +391,15 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
supportFinishAfterTransition();
|
||||
}
|
||||
|
||||
protected void addItem(I item, boolean isLocal) {
|
||||
private void addItem(I item, boolean isLocal) {
|
||||
adapter.incrementRevision();
|
||||
MessageId parent = item.getParentId();
|
||||
if (parent != null && !adapter.contains(parent)) {
|
||||
// We've incremented the adapter's revision, so the item will be
|
||||
// loaded when its parent has been loaded
|
||||
LOG.info("Ignoring item with missing parent");
|
||||
return;
|
||||
}
|
||||
adapter.add(item);
|
||||
|
||||
if (isLocal) {
|
||||
|
||||
@@ -12,14 +12,13 @@ import org.briarproject.briar.android.controller.ActivityLifecycleController;
|
||||
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
||||
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.briar.api.client.NamedGroup;
|
||||
import org.briarproject.briar.api.client.PostHeader;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface ThreadListController<G extends NamedGroup, I extends ThreadItem, H extends PostHeader>
|
||||
public interface ThreadListController<G extends NamedGroup, I extends ThreadItem>
|
||||
extends ActivityLifecycleController {
|
||||
|
||||
void setGroupId(GroupId groupId);
|
||||
@@ -29,9 +28,8 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
|
||||
void loadSharingContacts(
|
||||
ResultExceptionHandler<Collection<ContactId>, DbException> handler);
|
||||
|
||||
void loadItem(H header, ResultExceptionHandler<I, DbException> handler);
|
||||
|
||||
void loadItems(ResultExceptionHandler<ThreadItemList<I>, DbException> handler);
|
||||
void loadItems(
|
||||
ResultExceptionHandler<ThreadItemList<I>, DbException> handler);
|
||||
|
||||
void markItemRead(I item);
|
||||
|
||||
@@ -42,9 +40,10 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
|
||||
|
||||
void deleteNamedGroup(ExceptionHandler<DbException> handler);
|
||||
|
||||
interface ThreadListListener<H> extends ThreadListDataSource {
|
||||
interface ThreadListListener<I> extends ThreadListDataSource {
|
||||
|
||||
@UiThread
|
||||
void onHeaderReceived(H header);
|
||||
void onItemReceived(I item);
|
||||
|
||||
@UiThread
|
||||
void onGroupRemoved();
|
||||
|
||||
@@ -39,9 +39,9 @@ import static java.util.logging.Level.WARNING;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends ThreadItem, H extends PostHeader, M extends ThreadedMessage, L extends ThreadListListener<H>>
|
||||
public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends ThreadItem, H extends PostHeader, M extends ThreadedMessage, L extends ThreadListListener<I>>
|
||||
extends DbControllerImpl
|
||||
implements ThreadListController<G, I, H>, EventListener {
|
||||
implements ThreadListController<G, I>, EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ThreadListControllerImpl.class.getName());
|
||||
@@ -203,35 +203,6 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
|
||||
@DatabaseExecutor
|
||||
protected abstract String loadMessageBody(H header) throws DbException;
|
||||
|
||||
@Override
|
||||
public void loadItem(final H header,
|
||||
final ResultExceptionHandler<I, DbException> handler) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
String body;
|
||||
if (!bodyCache.containsKey(header.getId())) {
|
||||
body = loadMessageBody(header);
|
||||
bodyCache.put(header.getId(), body);
|
||||
} else {
|
||||
body = bodyCache.get(header.getId());
|
||||
}
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading item took " + duration + " ms");
|
||||
I item = buildItem(header, body);
|
||||
handler.onResult(item);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
handler.onException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markItemRead(I item) {
|
||||
markItemsRead(Collections.singletonList(item));
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<RelativeLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/margin_activity_vertical"
|
||||
android:paddingEnd="@dimen/margin_activity_horizontal"
|
||||
android:paddingRight="@dimen/margin_activity_horizontal"
|
||||
android:paddingStart="@dimen/margin_activity_horizontal"
|
||||
android:paddingLeft="@dimen/margin_activity_horizontal"
|
||||
android:paddingTop="@dimen/margin_activity_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/connectedView"
|
||||
style="@style/BriarTextTitle"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/connected_to_contact"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:drawableLeft="@drawable/navigation_accept"
|
||||
android:drawableStart="@drawable/navigation_accept"
|
||||
android:gravity="center_vertical"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/yourConfirmationCodeView"
|
||||
style="@style/BriarTextBody"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/your_confirmation_code"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:layout_below="@+id/connectedView"
|
||||
android:layout_centerHorizontal="true"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/codeView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:textSize="50sp"
|
||||
android:textColor="@color/briar_text_secondary"
|
||||
android:layout_below="@+id/yourConfirmationCodeView"
|
||||
android:layout_centerHorizontal="true"
|
||||
tools:text="1337"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/waitingView"
|
||||
style="@style/BriarTextBody"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/searching_format"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:layout_below="@+id/codeView"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:visibility="gone"
|
||||
android:gravity="center_horizontal"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:layout_below="@+id/waitingView"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/enterCodeTextView"
|
||||
style="@style/BriarTextBody"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/enter_confirmation_code"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:layout_below="@+id/codeView"
|
||||
android:layout_centerHorizontal="true"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/codeEntryView"
|
||||
layout="@layout/view_code_entry"
|
||||
android:layout_below="@+id/enterCodeTextView"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_medium"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/continueButton"
|
||||
style="@style/BriarButton.Default"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/continue_button"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:enabled="false"
|
||||
android:layout_below="@+id/codeEntryView"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_margin="@dimen/margin_medium"/>
|
||||
|
||||
</RelativeLayout>
|
||||
</ScrollView>
|
||||
@@ -1,96 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<RelativeLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/margin_activity_vertical"
|
||||
android:paddingEnd="@dimen/margin_activity_horizontal"
|
||||
android:paddingRight="@dimen/margin_activity_horizontal"
|
||||
android:paddingStart="@dimen/margin_activity_horizontal"
|
||||
android:paddingLeft="@dimen/margin_activity_horizontal"
|
||||
android:paddingTop="@dimen/margin_activity_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/yourCodeView"
|
||||
style="@style/BriarTextBody"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/your_invitation_code"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:layout_centerHorizontal="true"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/codeView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:textSize="50sp"
|
||||
android:textColor="@color/briar_text_secondary"
|
||||
android:layout_below="@+id/yourCodeView"
|
||||
android:layout_centerHorizontal="true"
|
||||
tools:text="1337"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/waitingView"
|
||||
style="@style/BriarTextBody"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/searching_format"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:layout_below="@+id/codeView"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:visibility="gone"
|
||||
android:gravity="center_horizontal"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:indeterminate="true"
|
||||
android:layout_below="@+id/waitingView"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/enterCodeTextView"
|
||||
style="@style/BriarTextBody"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/enter_invitation_code"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:layout_below="@+id/codeView"
|
||||
android:layout_centerHorizontal="true"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/codeEntryView"
|
||||
layout="@layout/view_code_entry"
|
||||
android:layout_below="@+id/enterCodeTextView"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_medium"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/continueButton"
|
||||
style="@style/BriarButton.Default"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/continue_button"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:enabled="false"
|
||||
android:layout_below="@+id/codeEntryView"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_margin="@dimen/margin_medium"/>
|
||||
|
||||
</RelativeLayout>
|
||||
</ScrollView>
|
||||
@@ -1,60 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/margin_activity_vertical"
|
||||
android:paddingEnd="@dimen/margin_activity_horizontal"
|
||||
android:paddingLeft="@dimen/margin_activity_horizontal"
|
||||
android:paddingRight="@dimen/margin_activity_horizontal"
|
||||
android:paddingStart="@dimen/margin_activity_horizontal"
|
||||
android:paddingTop="@dimen/margin_activity_vertical">
|
||||
|
||||
<TextView
|
||||
style="@style/BriarTextBody"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/your_nickname"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/border_spinner"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:spinnerMode="dropdown"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/bluetooth"/>
|
||||
|
||||
<TextView
|
||||
style="@style/BriarTextBody"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:text="@string/face_to_face"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/continueButton"
|
||||
style="@style/BriarButton.Default"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:text="@string/continue_button"/>
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
@@ -1,43 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="@dimen/margin_activity_vertical"
|
||||
android:paddingEnd="@dimen/margin_activity_horizontal"
|
||||
android:paddingStart="@dimen/margin_activity_horizontal"
|
||||
android:paddingTop="@dimen/margin_activity_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/errorTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/connection_failed"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
android:textColor="@color/briar_text_primary"
|
||||
android:drawableStart="@drawable/alerts_and_states_error"
|
||||
android:drawableLeft="@drawable/alerts_and_states_error"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="@dimen/margin_medium"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/explanationTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/interfering"
|
||||
android:textColor="@color/briar_text_primary"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:padding="@dimen/margin_medium"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/tryAgainButton"
|
||||
style="@style/BriarButton.Default"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/try_again_button"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_margin="@dimen/margin_medium"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<EditText
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/codeEntryView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="number"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:textSize="@dimen/text_size_xlarge"
|
||||
android:ems="4"
|
||||
android:maxLines="1"
|
||||
android:maxLength="6"
|
||||
android:layout_margin="@dimen/margin_medium"
|
||||
android:imeOptions="actionGo"
|
||||
tools:text="123456"/>
|
||||
@@ -3,18 +3,18 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_group_member_list"
|
||||
android:icon="@drawable/ic_group_white"
|
||||
android:title="@string/groups_member_list"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_group_invite"
|
||||
android:icon="@drawable/social_share_white"
|
||||
android:title="@string/groups_invite_members"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_group_member_list"
|
||||
android:icon="@drawable/ic_group_white"
|
||||
android:title="@string/groups_member_list"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_group_reveal"
|
||||
android:icon="@drawable/ic_visibility_white"
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Kontakt hinzufügen</string>
|
||||
<string name="your_nickname">Wähle die zu verwendende Identität:</string>
|
||||
<string name="face_to_face">Um einen neuen Kontakt hinzufügen zu können, ist eine reale Begegnung erforderlich.\n\nDadurch wird eine betrügerische Impersonation und der unautorisierte Zugriff auf die Kommunikation verhindert.</string>
|
||||
<string name="face_to_face">Um einen neuen Kontakt hinzuzufügen, ist es notwendig, dass sich beide Kontakte an einem Ort treffen.\n\nDadurch wird betrügerische Identitätsvortäuschung und unautorisierter Kommunikationszugriff verhindert.</string>
|
||||
<string name="continue_button">Weiter</string>
|
||||
<string name="your_invitation_code">Dein Einladungs-Code ist</string>
|
||||
<string name="enter_invitation_code">Bitte gib den Einladungs-Code Deines Kontakts an:</string>
|
||||
@@ -250,7 +250,7 @@
|
||||
<string name="blogs_blog_post_created">Blogbeitrag erstellt</string>
|
||||
<string name="blogs_blog_post_received">Neuen Blogbeitrag empfangen</string>
|
||||
<string name="blogs_blog_post_scroll_to">Scrolle zu</string>
|
||||
<string name="blogs_feed_empty_state">Dies ist die globale Blog-Zeitleiste.\n\nOffensichtlich hat noch niemand etwas veröffentlicht.\n\nSei die oder der erste und tipp auf das Stift-Icon, um einen neuen Blogbeitrag zu verfassen.</string>
|
||||
<string name="blogs_feed_empty_state">Dies ist die globale Blog-Zeitleiste.\n\nOffensichtlich hat noch niemand etwas veröffentlicht.\n\nSei die oder der Erste und tippe auf das Stift-Icon, um einen neuen Blogbeitrag zu verfassen.</string>
|
||||
<string name="blogs_remove_blog">Blog entfernen</string>
|
||||
<string name="blogs_remove_blog_dialog_message">Bist Du sicher, dass Du diesen Blog und alle dazugehörigen Beiträge löschen möchtest?\nBeachte, dass dies nicht den Blog auf Geräten anderer Leute löscht.</string>
|
||||
<string name="blogs_remove_blog_ok">Blog entfernen</string>
|
||||
|
||||
@@ -2,70 +2,70 @@
|
||||
<resources>
|
||||
<!--Setup-->
|
||||
<string name="setup_title">Configuration de Briar</string>
|
||||
<string name="setup_explanation">Votre compte Briar est enregistré chiffré sur votre appareil et non sur le \"cloud\". Si vous désinstallez Briar ou si vous oubliez votre mot de passe, votre compte et vos données sont irrécupérables.</string>
|
||||
<string name="choose_nickname">Choisissez votre surnom</string>
|
||||
<string name="choose_password">Choisissez votre mot de passe</string>
|
||||
<string name="confirm_password">Confirmez votre mot de passe</string>
|
||||
<string name="setup_explanation">Votre compte Briar est enregistré chiffré sur votre appareil et non sur le nuage. Si vous désinstallez Briar ou oubliez votre mot de passe, votre compte et vos données sont irrécupérables.</string>
|
||||
<string name="choose_nickname">Choisir votre pseudonyme</string>
|
||||
<string name="choose_password">Choisir votre mot de passe</string>
|
||||
<string name="confirm_password">Confirmer votre mot de passe</string>
|
||||
<string name="name_too_long">Le nom est trop long</string>
|
||||
<string name="password_too_weak">Le mot de passe est trop faible</string>
|
||||
<string name="passwords_do_not_match">Les mots de passes ne correspondent pas</string>
|
||||
<string name="passwords_do_not_match">Les mots de passe ne correspondent pas</string>
|
||||
<string name="create_account_button">Créer un compte</string>
|
||||
<!--Login-->
|
||||
<string name="enter_password">Tapez votre mot de passe :</string>
|
||||
<string name="try_again">Mot de passe incorrecte, essayer à nouveau</string>
|
||||
<string name="sign_in_button">Se connecter</string>
|
||||
<string name="enter_password">Saisir votre mot de passe :</string>
|
||||
<string name="try_again">Le mot de passe est erroné, ressayez</string>
|
||||
<string name="sign_in_button">Connexion</string>
|
||||
<string name="forgotten_password">J\'ai oublié mon mot de passe</string>
|
||||
<string name="dialog_title_lost_password">Mot de passe oublié</string>
|
||||
<string name="dialog_message_lost_password">Votre compte Briar est enregistré chiffré sur votre appareil et non sur le \"cloud\", par conséquent, votre mot de passe ne peut être réinitialisé. Voulez-vous supprimer votre compte et démarrer à nouveau ?\n\nAttention : vos identités, contacts et messages seront définitivement perdus.</string>
|
||||
<string name="startup_failed_notification_title">Briar n\'a pas pu démarrer</string>
|
||||
<string name="startup_failed_notification_text">Vous devrez peut-être réinstaller Briar.</string>
|
||||
<string name="dialog_message_lost_password">Votre compte Briar est enregistré chiffré sur votre appareil et non sur le nuage et nous ne pouvons donc pas réinitialiser votre mot de passe. Voulez-vous supprimer votre compte et recommencer ?\n\nAttention : vos identités, contacts et messages seront perdus irrémédiablement.</string>
|
||||
<string name="startup_failed_notification_title">Impossible de démarrer Briar</string>
|
||||
<string name="startup_failed_notification_text">Il vous faudra peut-être réinstaller Briar.</string>
|
||||
<string name="startup_failed_activity_title">Échec de démarrage de Briar</string>
|
||||
<string name="startup_failed_db_error">Pour une raison indéterminée, votre base de donnée Briar est corrompue et irrécupérable. Vos comptes, données et contacts sont perdus. Vous devez malheureusement réinstaller Briar et configurer un nouveau compte.</string>
|
||||
<string name="startup_failed_service_error">Briar n\'a pas pu démarrer un module nécessaire. Réinstaller Briar résout généralement ce problème. Veuillez noter que vous perdrez votre compte et toutes les données associées puisque Briar n\'utilise pas de serveurs centralisés pour enregistrer les données.</string>
|
||||
<string name="startup_failed_db_error">Pour une raison indéterminée, votre base de données Briar est corrompue sans espoir de récupération. Vos comptes, données et contacts sont perdus. Vous devez malheureusement réinstaller Briar et configurer un nouveau compte.</string>
|
||||
<string name="startup_failed_service_error">Briar n\'a pas pu démarrer un greffon exigé. Réinstaller Briar résout généralement ce problème. Veuillez cependant noter que vous perdrez votre compte et toutes données relatives puisque Briar n\'utilise pas de serveurs centralisés sur lesquels enregistrer vos données.</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="one">Ceci est une version béta de Briar. Votre compte expirera dans %d jour et ne peut pas être renouvelé.</item>
|
||||
<item quantity="other">Ceci est une version béta de Briar. Votre compte expirera dans %d jours et ne peut pas être renouvelé.</item>
|
||||
<item quantity="one">Ceci est une version bêta de Briar. Votre compte arrivera à expiration dans %d jour et ne peut pas être renouvelé.</item>
|
||||
<item quantity="other">Ceci est une version bêta de Briar. Votre compte arrivera à expiration dans %d jours et ne peut pas être renouvelé.</item>
|
||||
</plurals>
|
||||
<string name="expiry_date_reached">Ce logiciel a expiré.\nMerci de l\'avoir testé!</string>
|
||||
<string name="expiry_date_reached">Ce logiciel est arrivé à expiration.\nMerci de l\'avoir testé !</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">Ouvrir le panneau de navigation</string>
|
||||
<string name="nav_drawer_close_description">Fermer le panneau de navigation</string>
|
||||
<string name="nav_drawer_open_description">Ouvrir le tiroir de navigation</string>
|
||||
<string name="nav_drawer_close_description">Fermer le tiroir de navigation</string>
|
||||
<string name="contact_list_button">Contacts</string>
|
||||
<string name="groups_button">Groupes privés</string>
|
||||
<string name="forums_button">Forums</string>
|
||||
<string name="blogs_button">Blogs</string>
|
||||
<string name="blogs_button">Blogues</string>
|
||||
<string name="settings_button">Paramètres</string>
|
||||
<string name="sign_out_button">Se déconnecter</string>
|
||||
<string name="sign_out_button">Déconnexion</string>
|
||||
<!--Transports-->
|
||||
<string name="transport_tor">Internet</string>
|
||||
<string name="transport_bt">Bluetooth</string>
|
||||
<string name="transport_lan">Wifi</string>
|
||||
<string name="transport_lan">Wi-Fi</string>
|
||||
<!--Notifications-->
|
||||
<string name="ongoing_notification_title">Connecté à Briar</string>
|
||||
<string name="ongoing_notification_text">Toucher pour ouvrir Briar.</string>
|
||||
<plurals name="private_message_notification_text">
|
||||
<item quantity="one">Nouveau message privé.</item>
|
||||
<item quantity="other">%d Nouveaux messages privés.</item>
|
||||
<item quantity="one">Un nouveau message privé.</item>
|
||||
<item quantity="other">%d nouveaux messages privés.</item>
|
||||
</plurals>
|
||||
<plurals name="group_message_notification_text">
|
||||
<item quantity="one">Nouveau message de groupe.</item>
|
||||
<item quantity="one">Un nouveau message de groupe.</item>
|
||||
<item quantity="other">%d nouveaux messages de groupe.</item>
|
||||
</plurals>
|
||||
<plurals name="forum_post_notification_text">
|
||||
<item quantity="one">Nouveau post de forum.</item>
|
||||
<item quantity="other">%d nouveaux messages de forum.</item>
|
||||
<item quantity="one">Un nouvel article de forum.</item>
|
||||
<item quantity="other">%d nouveaux articles de forum.</item>
|
||||
</plurals>
|
||||
<plurals name="blog_post_notification_text">
|
||||
<item quantity="one">Nouveau post de blog.</item>
|
||||
<item quantity="other">%d Nouveaux messages de blog.</item>
|
||||
<item quantity="one">Un nouveau billet de blogue.</item>
|
||||
<item quantity="other">%d nouveaux billets de blogue.</item>
|
||||
</plurals>
|
||||
<!--Misc-->
|
||||
<string name="now">Maintenant</string>
|
||||
<string name="now">maintenant</string>
|
||||
<string name="show">Afficher</string>
|
||||
<string name="hide">Cacher</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="cancel">Annuler</string>
|
||||
<string name="got_it">Je l\'ai</string>
|
||||
<string name="got_it">Compris</string>
|
||||
<string name="delete">Supprimer</string>
|
||||
<string name="accept">Accepter</string>
|
||||
<string name="decline">Refuser</string>
|
||||
@@ -78,163 +78,163 @@
|
||||
<string name="no_data">Aucune donnée</string>
|
||||
<string name="ellipsis">...</string>
|
||||
<string name="text_too_long">Le texte saisi est trop long</string>
|
||||
<string name="show_onboarding">Afficher la boite de dialogue d\'Aide</string>
|
||||
<string name="show_onboarding">Afficher la fenêtre d\'aide</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">Vous semblez être nouveau ici et n\'avez aucun contact.\n\nTouchez l\'icône + en haut et suivez les instructions pour en ajouter.\n\nRappel : vous pouvez ajouter des contacts uniquement en les rencontrant face à face, ceci pour éviter le vol d\'identité et que vos messages ne soient lus par d\'autres.</string>
|
||||
<string name="no_contacts">Il semble que soyez nouveau ici, sans encore aucun contact.\n\nTouchez l\'icône + en haut et suivez les instructions pour ajouter des amis à votre liste.\n\nVeuillez ne pas oublier que vous pouvez seulement ajouter des contacts en les rencontrant directement, afin d\'éviter que quelqu\'un se fasse passer pour vous et puisse lire vos messages à l\'avenir.</string>
|
||||
<string name="date_no_private_messages">Aucun message.</string>
|
||||
<string name="no_private_messages">Ceci est la vue de conversation.\n\nIl semble y avoir un manque de conversation.\n\nAppuyez sur le champ de saisie en bas pour en démarrer une. </string>
|
||||
<string name="message_hint">Taper le message</string>
|
||||
<string name="no_private_messages">Ceci est la vue des conversations.\n\nIl semble ne pas y en avoir.\n\nIl suffit de toucher le champ de saisie, en bas, pour en démarrer une.</string>
|
||||
<string name="message_hint">Rédiger le message</string>
|
||||
<string name="delete_contact">Supprimer le contact</string>
|
||||
<string name="dialog_title_delete_contact">Confirmer la suppression du contact</string>
|
||||
<string name="dialog_message_delete_contact">Êtes-vous sûr de vouloir supprimer ce contact et tous les messages échangés avec celui-ci ?</string>
|
||||
<string name="contact_deleted_toast">Contact supprimé</string>
|
||||
<string name="dialog_message_delete_contact">Voulez-vous vraiment supprimer ce contact et tous les messages associés ?</string>
|
||||
<string name="contact_deleted_toast">Le contact a été supprimé</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Ajouter un contact</string>
|
||||
<string name="your_nickname">Choisissez l\'identité que vous souhaitez utiliser :</string>
|
||||
<string name="face_to_face">Vous devez rencontrer la personne que vous voulez ajouter comme contact.\n\nCeci pour éviter le vol d\'identité et que vos messages ne soient lus par d\'autres.</string>
|
||||
<string name="your_nickname">Choisir l\'identité que vous souhaitez utiliser :</string>
|
||||
<string name="face_to_face">Vous devez rencontrer la personne que vous voulez ajouter comme contact, afin d\'éviter que quelqu\'un se fasse passer pour vous et puisse lire vos messages à l\'avenir.</string>
|
||||
<string name="continue_button">Continuer</string>
|
||||
<string name="your_invitation_code">Votre code d\'invitation est </string>
|
||||
<string name="your_invitation_code">Votre code d\'invitation est</string>
|
||||
<string name="enter_invitation_code">Veuillez saisir le code d\'invitation de votre contact :</string>
|
||||
<string name="searching_format">Recherche de contacts avec le code d\'invitation %06d\u2026</string>
|
||||
<string name="connection_failed">Échec de connexion</string>
|
||||
<string name="could_not_find_contact">Briar n\'a pas trouvé de contacts à proximité</string>
|
||||
<string name="try_again_button">Essayer à nouveau</string>
|
||||
<string name="could_not_find_contact">Briar n\'a pas trouvé votre contact à proximité</string>
|
||||
<string name="try_again_button">Ressayer</string>
|
||||
<string name="connected_to_contact">Connecté au contact</string>
|
||||
<string name="calculating_confirmation_code">Calcul du code de confirmation\u2026</string>
|
||||
<string name="your_confirmation_code">Votre code de confirmation est </string>
|
||||
<string name="your_confirmation_code">Votre code de confirmation est</string>
|
||||
<string name="enter_confirmation_code">Veuillez saisir le code de confirmation de votre contact :</string>
|
||||
<string name="waiting_for_contact">Attente du contact\u2026</string>
|
||||
<string name="waiting_for_contact_to_scan">En attente de scan et de la connexion du contact\u2026</string>
|
||||
<string name="exchanging_contact_details">Échange des détails du contact\u2026</string>
|
||||
<string name="waiting_for_contact">En attente du contact\u2026</string>
|
||||
<string name="waiting_for_contact_to_scan">En attente de recherche et de connexion du contact\u2026</string>
|
||||
<string name="exchanging_contact_details">Échange des renseignements de contact\u2026</string>
|
||||
<string name="codes_do_not_match">Les codes ne correspondent pas</string>
|
||||
<string name="interfering">Il se pourrait que quelqu\'un essaye d\'interférer avec votre connexion</string>
|
||||
<string name="interfering">Cela pourrait signer que quelqu\'un tente d\'interférer avec votre connexion</string>
|
||||
<string name="contact_added_toast">Contact ajouté : %s</string>
|
||||
<string name="contact_already_exists">Le contact %s existe déjà</string>
|
||||
<string name="contact_exchange_failed">L\'échange de contact a échoué</string>
|
||||
<string name="qr_code_invalid">Le code QR n\'est pas valide</string>
|
||||
<string name="contact_exchange_failed">Échec d\'échange de contacts</string>
|
||||
<string name="qr_code_invalid">Le code QR est invalide</string>
|
||||
<string name="connecting_to_device">Connexion à l\'appareil\u2026</string>
|
||||
<string name="authenticating_with_device">Autentification avec l\'appareil\u2026</string>
|
||||
<string name="connection_aborted_local">Nous avons interrompu la connexion ! Il est possible que quelqu\'un tente d\'interférer avec celle-ci</string>
|
||||
<string name="connection_aborted_remote">Votre contact a interrompu la connexion ! Il est possible que quelqu\'un tente d\'interférer avec celle-ci</string>
|
||||
<string name="connection_aborted_local">Nous avons interrompu la connexion ! Cela pourrait signer que quelqu\'un tente d\'interférer avec votre connexion</string>
|
||||
<string name="connection_aborted_remote">Votre contact a interrompu la connexion ! Cela pourrait signer que quelqu\'un tente d\'interférer avec votre connexion</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">Introduire vos contacts</string>
|
||||
<string name="introduction_onboarding_text">Vous pouvez introduire vos contacts les uns les autres, ils n\'ont donc pas besoin de se rencontrer pour se connecter avec Briar.</string>
|
||||
<string name="introduction_activity_title">Sélectionner contact </string>
|
||||
<string name="introduction_message_title">Introduire des contacts</string>
|
||||
<string name="introduction_message_hint">Ajouter un message (optionnel)</string>
|
||||
<string name="introduction_button">Effectuer l\'introduction</string>
|
||||
<string name="introduction_sent">Votre admission a été envoyée.</string>
|
||||
<string name="introduction_error">Une erreur s\'est produite lors de l\'admission.</string>
|
||||
<string name="introduction_response_error">Erreur lors de la réponse à l\'admission</string>
|
||||
<string name="introduction_request_sent">Vous avez demandé à %2$s d\'admettre %1$s.</string>
|
||||
<string name="introduction_request_received">%1$s vous a demandé l\'admission de %2$s. Souhaitez-vous ajouter %2$s à votre liste de contacts ?</string>
|
||||
<string name="introduction_request_exists_received">%1$s vous a demandé l\'admission de %2$s mais %2$s est déjà dans votre liste de contacts. Puisque %1$s ne le sait pas, vous pouvez tout de même répondre : </string>
|
||||
<string name="introduction_request_answered_received">%1$s a demandé l\'admission de %2$s.</string>
|
||||
<string name="introduction_response_accepted_sent">Vous avez accepté l\'admission de %1$s.</string>
|
||||
<string name="introduction_response_declined_sent">Vous avez refusé l\'admission de %1$s.</string>
|
||||
<string name="introduction_response_accepted_received">%1$s a accepté l\'admission de %2$s.</string>
|
||||
<string name="introduction_response_declined_received">%1$s a refusé l\'admission de %2$s.</string>
|
||||
<string name="introduction_response_declined_received_by_introducee">%1$s annonce que %2$s a refusé l\'admission.</string>
|
||||
<string name="introduction_onboarding_title">Présenter vos contacts</string>
|
||||
<string name="introduction_onboarding_text">Vous pouvez présenter vos contacts mutuellement, afin qu\'ils n\'aient pas à se rencontrer en personne pour se connecter les uns aux autres avec Briar.</string>
|
||||
<string name="introduction_activity_title">Sélectionner un contact </string>
|
||||
<string name="introduction_message_title">Présenter des contacts</string>
|
||||
<string name="introduction_message_hint">Ajouter un message (facultatif)</string>
|
||||
<string name="introduction_button">Faire les présentations</string>
|
||||
<string name="introduction_sent">Votre présentation a été envoyée.</string>
|
||||
<string name="introduction_error">Une erreur est survenue lors de la présentation.</string>
|
||||
<string name="introduction_response_error">Erreur de réponse à la présentation</string>
|
||||
<string name="introduction_request_sent">Vous avez demandé de présenter %1$s à %2$s.</string>
|
||||
<string name="introduction_request_received">%1$s a demandé de vous présenter à %2$s. Souhaitez-vous ajouter %2$s à votre liste de contacts ?</string>
|
||||
<string name="introduction_request_exists_received">%1$s a demandé de vous présenter à %2$s, mais %2$s est déjà dans votre liste de contacts. Puisque %1$s pourrait ne pas le savoir, vous pouvez tout de même répondre :</string>
|
||||
<string name="introduction_request_answered_received">%1$s a demandé de vous présenter à %2$s.</string>
|
||||
<string name="introduction_response_accepted_sent">Vous avez accepté d\'être présenté à %1$s.</string>
|
||||
<string name="introduction_response_declined_sent">Vous avez refusé d\'être présenté à %1$s.</string>
|
||||
<string name="introduction_response_accepted_received">%1$s a accepté d\'être présenté à %2$s.</string>
|
||||
<string name="introduction_response_declined_received">%1$s a refusé d\'être présenté à %2$s.</string>
|
||||
<string name="introduction_response_declined_received_by_introducee">%1$s annonce que %2$s a refusé la présentation.</string>
|
||||
<plurals name="introduction_notification_text">
|
||||
<item quantity="one">Nouveau contact ajouté.</item>
|
||||
<item quantity="other">%d nouveaux contacts ajoutés.</item>
|
||||
<item quantity="one">Un nouveau contact a été ajouté.</item>
|
||||
<item quantity="other">%d nouveaux contacts ont été ajoutés.</item>
|
||||
</plurals>
|
||||
<!--Private Groups-->
|
||||
<string name="groups_list_empty">Vous n\'êtes membres d\'aucun groupe.\n\nTouchez l\'icône + en haut pour en créer un ou demandez à vos contacts de vous inviter à l\'un de leurs groupes.</string>
|
||||
<string name="groups_list_empty">Vous ne participez à aucun groupe.\n\nTouchez l\'icône +, en haut, pour en créer un ou demandez à vos contacts de vous inviter dans l\'un des leurs.</string>
|
||||
<string name="groups_created_by">Créé par %s</string>
|
||||
<plurals name="messages">
|
||||
<item quantity="one">%d message</item>
|
||||
<item quantity="other">%d messages</item>
|
||||
</plurals>
|
||||
<string name="groups_group_is_empty">Ce groupe est vide</string>
|
||||
<string name="groups_group_is_dissolved">Ce groupe a été supprimé</string>
|
||||
<string name="groups_group_is_dissolved">Ce groupe a été dissous</string>
|
||||
<string name="groups_remove">Supprimer</string>
|
||||
<string name="groups_create_group_title">Créer un groupe privé</string>
|
||||
<string name="groups_create_group_button">Créer un groupe</string>
|
||||
<string name="groups_create_group_invitation_button">Envoyer invitation</string>
|
||||
<string name="groups_create_group_hint">Choisissez un nom pour votre groupe privé</string>
|
||||
<string name="groups_invitation_sent">Invitation envoyée au groupe</string>
|
||||
<string name="groups_message_sent">Message envoyé</string>
|
||||
<string name="groups_member_list">Liste de participants</string>
|
||||
<string name="groups_create_group_invitation_button">Envoyer une invitation</string>
|
||||
<string name="groups_create_group_hint">Choisir un nom pour votre groupe privé</string>
|
||||
<string name="groups_invitation_sent">L\'invitation de groupe a été envoyée</string>
|
||||
<string name="groups_message_sent">Le message a été envoyé</string>
|
||||
<string name="groups_member_list">Liste des participants</string>
|
||||
<string name="groups_invite_members">Inviter des participants</string>
|
||||
<string name="groups_member_created_you">Vous avez créé le groupe</string>
|
||||
<string name="groups_member_created">%s a créé le groupe</string>
|
||||
<string name="groups_member_joined_you">Vous avez rejoint le groupe</string>
|
||||
<string name="groups_member_joined">%s a rejoint le groupe</string>
|
||||
<string name="groups_member_joined_you">Vous vous êtes joint au groupe</string>
|
||||
<string name="groups_member_joined">%s s\'est joint au groupe</string>
|
||||
<string name="groups_leave">Quitter le groupe</string>
|
||||
<string name="groups_leave_dialog_title">Confirmer la sortie du groupe</string>
|
||||
<string name="groups_leave_dialog_message">Êtes-vous sûr de vouloir quitter ce groupe ?</string>
|
||||
<string name="groups_dissolve">Supprimer le groupe</string>
|
||||
<string name="groups_dissolve_dialog_title">Confirmer la suppression du groupe</string>
|
||||
<string name="groups_dissolve_dialog_message">Êtes-vous sûr de vouloir supprimer ce groupe ?\n\nLes autres participants ne pourront plus continuer leur conversation et pourraient ne pas recevoir les derniers messages.</string>
|
||||
<string name="groups_dissolve_button">Supprimer</string>
|
||||
<string name="groups_dissolved_dialog_title">Le Groupe A Été Supprimé</string>
|
||||
<string name="groups_dissolved_dialog_message">L\'initiateur de ce groupe l\'a supprimé.\n\nVous ne pouvez plus y écrire de messages et il se peut que vous n\'ayez pas reçu tout ceux qui y ont été publiés.</string>
|
||||
<string name="groups_leave_dialog_message">Voulez-vous vraiment quitter ce groupe ?</string>
|
||||
<string name="groups_dissolve">Dissoudre le groupe</string>
|
||||
<string name="groups_dissolve_dialog_title">Confirmer la dissolution du groupe</string>
|
||||
<string name="groups_dissolve_dialog_message">Voulez-vous vraiment dissoudre ce groupe ?\n\nLes autres participants ne pourront pas poursuivre leur conversation et ne recevront peut-être pas les derniers messages.</string>
|
||||
<string name="groups_dissolve_button">Dissoudre</string>
|
||||
<string name="groups_dissolved_dialog_title">Le groupe a été dissous</string>
|
||||
<string name="groups_dissolved_dialog_message">Le créateur de ce groupe l\'a dissous.\n\nVous ne pouvez plus écrire de messages au groupe et ne recevrez peut-être pas tous ceux qui y ont été publiés.</string>
|
||||
<!--Private Group Invitations-->
|
||||
<string name="groups_invitations_title">Invitations du groupe</string>
|
||||
<string name="groups_invitations_invitation_sent">Vous avez invité %1$s a rejoindre le groupe \"%2$s\".</string>
|
||||
<string name="groups_invitations_invitation_received">%1$s vous a invité a rejoindre le groupe \"%2$s\".</string>
|
||||
<string name="groups_invitations_joined">Groupe rejoint</string>
|
||||
<string name="groups_invitations_declined">Invitation du groupe déclinée</string>
|
||||
<string name="groups_invitations_title">Invitations de groupe</string>
|
||||
<string name="groups_invitations_invitation_sent">Vous avez invité %1$s à se joindre au groupe « %2$s ».</string>
|
||||
<string name="groups_invitations_invitation_received">%1$s vous a invité à vous joindre au groupe « %2$s ».</string>
|
||||
<string name="groups_invitations_joined">S\'est joint au groupe</string>
|
||||
<string name="groups_invitations_declined">L\'invitation de groupe a été refusée</string>
|
||||
<plurals name="groups_invitations_open">
|
||||
<item quantity="one">%d invitation de groupe en attente</item>
|
||||
<item quantity="other">%d invitations de groupe en attente</item>
|
||||
</plurals>
|
||||
<string name="groups_invitations_response_accepted_sent">Vous avez accepté l\'invitation du groupe de %s.</string>
|
||||
<string name="groups_invitations_response_declined_sent">Vous avez refusé l\'invitation du groupe de %s.</string>
|
||||
<string name="groups_invitations_response_accepted_received">%s a accepté l\'invitation du groupe.</string>
|
||||
<string name="groups_invitations_response_declined_received">%s a décliné l\'invitation du groupe.</string>
|
||||
<string name="sharing_status_groups">Seul l\'initiateur du groupe peut inviter de nouveaux membres. Voici la liste des membres actuels.</string>
|
||||
<string name="groups_invitations_response_accepted_sent">Vous avez accepté l\'invitation de groupe de %s.</string>
|
||||
<string name="groups_invitations_response_declined_sent">Vous avez refusé l\'invitation de groupe de %s.</string>
|
||||
<string name="groups_invitations_response_accepted_received">%s a accepté l\'invitation de groupe.</string>
|
||||
<string name="groups_invitations_response_declined_received">%s a refusé l\'invitation de groupe.</string>
|
||||
<string name="sharing_status_groups">Seul le créateur peut inviter de nouveaux participants. Voici la liste des membres actuels du groupe.</string>
|
||||
<!--Private Groups Revealing Contacts-->
|
||||
<string name="groups_reveal_contacts">Dévoiler les contacts</string>
|
||||
<string name="groups_reveal_dialog_message">Vous pouvez choisir de dévoiler les contacts à tous les membres actuels et futurs de ce groupe.\n\nDévoiler les contacts vous assure une connexion au groupe plus rapide et plus fiable car vous pouvez communiquer avec les contacts dévoilés même si l\'initiateur du groupe est hors ligne.</string>
|
||||
<string name="groups_reveal_dialog_message">Vous pouvez choisir de dévoiler les contacts à tous les participants actuels et futurs de ce groupe.\n\nDévoiler les contacts accélère et fiabilise votre connexion au groupe, car vous pouvez communiquer avec les contacts dévoilés même si le créateur du groupe est hors ligne.</string>
|
||||
<string name="groups_reveal_visible">Votre lien avec le contact est visible par le groupe</string>
|
||||
<string name="groups_reveal_visible_revealed_by_us">Votre lien avec le contact est visible par le groupe (dévoilé par vous-même)</string>
|
||||
<string name="groups_reveal_visible_revealed_by_contact">Votre lien avec le contact est invisible par le groupe (dévoilé par %s)</string>
|
||||
<string name="groups_reveal_invisible">Votre lien avec le contact est invisible par le groupe</string>
|
||||
<string name="groups_reveal_visible_revealed_by_us">Votre lien avec le contact est visible par le groupe (dévoilé par vous)</string>
|
||||
<string name="groups_reveal_visible_revealed_by_contact">Votre lien avec le contact est visible par le groupe (dévoilé par %s)</string>
|
||||
<string name="groups_reveal_invisible">Votre lien avec le contact n\'est pas visible par le groupe</string>
|
||||
<!--Forums-->
|
||||
<string name="no_forums">Vous n\'avez pas de forums.\n\nPourquoi ne pas en créer un en touchant l\'icône + en haut ?\n\nVous pouvez aussi demander à vos contacts d\'en partager avec vous.</string>
|
||||
<string name="no_forums">Vous n\'avez pas encore de forums.\n\nPourquoi ne pas en créer un en touchant l\'icône + située en haut ?\n\nVous pouvez aussi demander à vos contacts d\'en partager avec vous.</string>
|
||||
<string name="create_forum_title">Créer un forum</string>
|
||||
<string name="choose_forum_hint">Choisir un nom pour le Forum </string>
|
||||
<string name="choose_forum_hint">Choisir un nom pour votre forum </string>
|
||||
<string name="create_forum_button">Créer un forum</string>
|
||||
<string name="forum_created_toast">Forum créé</string>
|
||||
<string name="no_forum_posts">Ce forum est vide.\n\nUtilisez l\'icône crayon, en haut pour écrire le premier message.\n\nVous sentez-vous seul, ici? Partager ce forum avec d\'autres contacts !</string>
|
||||
<string name="no_posts">Aucun message</string>
|
||||
<string name="forum_created_toast">Le forum a été créé</string>
|
||||
<string name="no_forum_posts">Ce forum est vide.\n\nUtilisez l\'icône de crayon, en haut, pour rédiger le premier article.\n\nVous sentez-vous seul ici ? Partagez ce forum avec vos contacts !</string>
|
||||
<string name="no_posts">Aucun article</string>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">%d message</item>
|
||||
<item quantity="other">%d messages</item>
|
||||
<item quantity="one">%d article</item>
|
||||
<item quantity="other">%d articles</item>
|
||||
</plurals>
|
||||
<string name="forum_new_entry_posted">Message de forum saisi</string>
|
||||
<string name="forum_new_entry_posted">L\'entrée de forum a été publiée</string>
|
||||
<string name="forum_new_message_hint">Nouvelle entrée</string>
|
||||
<string name="forum_message_reply_hint">Nouvelle réponse</string>
|
||||
<string name="btn_reply">Répondre</string>
|
||||
<string name="forum_leave">Quitter le forum</string>
|
||||
<string name="dialog_title_leave_forum">Confirmer la sortie du forum</string>
|
||||
<string name="dialog_message_leave_forum">Êtes-vous sûr de vouloir quitter ce forum ? Les contacts avec qui vous l\'avez partagé pourraient ne plus recevoir ses mises à jour.</string>
|
||||
<string name="dialog_message_leave_forum">Voulez-vous vraiment quitter ce forum ? Les contacts avec qui vous l\'avez partagé pourraient ne plus en recevoir les mises à jour.</string>
|
||||
<string name="dialog_button_leave">Quitter</string>
|
||||
<string name="forum_left_toast">Quitté le forum</string>
|
||||
<string name="forum_left_toast">A quitté le forum</string>
|
||||
<!--Forum Sharing-->
|
||||
<string name="forum_share_button">Partagé le forum</string>
|
||||
<string name="contacts_selected">Contacts sélectionnés</string>
|
||||
<string name="activity_share_toolbar_header">Choisissez des contacts</string>
|
||||
<string name="no_contacts_selector">Vous semblez être nouveau ici et n\'avez pas encore de contacts.\n\nRevenez lorsque vous aurez ajouté votre premier contact.</string>
|
||||
<string name="forum_shared_snackbar">Forum partagé avec les contacts choisis</string>
|
||||
<string name="forum_share_message">Ajouter un message (optionnel)</string>
|
||||
<string name="forum_share_error">Une erreur s\'est produite lors du partage du forum.</string>
|
||||
<string name="forum_invitation_received">%1$s a partagé le forum \"%2$s\" avec vous.</string>
|
||||
<string name="forum_invitation_sent">Vous avez partagé le forum \"%1$s\" avec %2$s.</string>
|
||||
<string name="forum_share_button">Partager le forum</string>
|
||||
<string name="contacts_selected">Des contacts ont été sélectionnés</string>
|
||||
<string name="activity_share_toolbar_header">Choisir des contacts</string>
|
||||
<string name="no_contacts_selector">Il semble que soyez nouveau ici, sans encore aucun contact.\n\nRevenez ici après avoir ajouté votre premier contact.</string>
|
||||
<string name="forum_shared_snackbar">Le forum a été partagé avec les contacts choisis</string>
|
||||
<string name="forum_share_message">Ajouter un message (facultatif)</string>
|
||||
<string name="forum_share_error">Une erreur est survenue lors du partage de ce forum.</string>
|
||||
<string name="forum_invitation_received">%1$s a partagé le forum « %2$s » avec vous.</string>
|
||||
<string name="forum_invitation_sent">Vous avez partagé le forum « %1$s » avec %2$s.</string>
|
||||
<string name="forum_invitations_title">Invitations au forum</string>
|
||||
<string name="forum_invitation_exists">Vous avez déjà accepté une invitation à ce forum. En acceptant d\'autre invitations, vous augmentez et renforcez la communication de ce forum.</string>
|
||||
<string name="forum_joined_toast">Forum rejoint</string>
|
||||
<string name="forum_declined_toast">Invitation au forum refusée</string>
|
||||
<string name="forum_invitation_exists">Vous avez déjà accepté une invitation à ce forum. En acceptant d\'autres invitations, vous augmenterez et renforcerez la communication de ce forum.</string>
|
||||
<string name="forum_joined_toast">Vous vous êtes joint au forum</string>
|
||||
<string name="forum_declined_toast">L\'invitation au forum a été refusée</string>
|
||||
<string name="shared_by_format">Partagé par %s</string>
|
||||
<string name="forum_invitation_already_sharing">Déjà en cours de partage</string>
|
||||
<string name="forum_invitation_response_accepted_sent">Vous avez accepté l\'invitation au forum de %s.</string>
|
||||
<string name="forum_invitation_response_declined_sent">Vous avez refusé l\'invitation au forum de %s.</string>
|
||||
<string name="forum_invitation_already_sharing">Le forum est déjà partagé</string>
|
||||
<string name="forum_invitation_response_accepted_sent">Vous avez accepté l\'invitation de %s au forum.</string>
|
||||
<string name="forum_invitation_response_declined_sent">Vous avez refusé l\'invitation de %s au forum.</string>
|
||||
<string name="forum_invitation_response_accepted_received">%s a accepté l\'invitation au forum.</string>
|
||||
<string name="forum_invitation_response_declined_received">%s a décliné l\'invitation au forum.</string>
|
||||
<string name="sharing_status">Etat de partage</string>
|
||||
<string name="sharing_status_forum">Tous les participants d\'un forum peuvent le partager avec leurs contacts. Vous partagez ce forum avec les contacts suivants. Il y a peut-être d\'autres participants que vous ne pouvez voir.</string>
|
||||
<string name="forum_invitation_response_declined_received">%s a refusé l\'invitation au forum.</string>
|
||||
<string name="sharing_status">État de partage</string>
|
||||
<string name="sharing_status_forum">Tous les participants d\'un forum peuvent le partager avec leurs contacts. Vous partagez ce forum avec les contacts suivants. Il peut aussi y avoir d\'autres participants que vous ne pouvez pas voir.</string>
|
||||
<string name="shared_with">Partagé avec %1$d (%2$d en ligne)</string>
|
||||
<plurals name="forums_shared">
|
||||
<item quantity="one">%d forum partagé par des contacts</item>
|
||||
@@ -242,126 +242,126 @@
|
||||
</plurals>
|
||||
<string name="nobody">Personne</string>
|
||||
<!--Blogs-->
|
||||
<string name="blogs_other_blog_empty_state">Ce blog est actuellement vide.\n\nSoit l\'auteur n\'a encore rien écrit, soit la personne qui l\'a partagé avec vous doit venir en ligne pour que les messages soient synchronisés.</string>
|
||||
<string name="read_more">Lire la suite</string>
|
||||
<string name="blogs_write_blog_post">Écrire un message de blog</string>
|
||||
<string name="blogs_write_blog_post_body_hint">Saisir ici votre message de blog</string>
|
||||
<string name="blogs_other_blog_empty_state">Ce blogue est actuellement vide.\n\nSoit l\'auteur n\'a encore rien écrit, soit la personne qui l\'a partagé avec vous doit se connecter pour que les articles soient synchronisés.</string>
|
||||
<string name="read_more">en lire davantage</string>
|
||||
<string name="blogs_write_blog_post">Écrire un article de blogue</string>
|
||||
<string name="blogs_write_blog_post_body_hint">Saisir votre message de blogue ici</string>
|
||||
<string name="blogs_publish_blog_post">Publier</string>
|
||||
<string name="blogs_blog_post_created">Message de blog créé</string>
|
||||
<string name="blogs_blog_post_received">Nouveau message de blog reçu</string>
|
||||
<string name="blogs_blog_post_scroll_to">Défiler jusqu\'à</string>
|
||||
<string name="blogs_feed_empty_state">Ceci est le flux global des blogs.\n\nIl semble que personne n\'ait encore rien écrit.\n\nSoyez le premier et touchez l\'icône stylet pour écrire un message de blog.</string>
|
||||
<string name="blogs_remove_blog">Supprimer le blog</string>
|
||||
<string name="blogs_remove_blog_dialog_message">Êtes-vous sûr de vouloir supprimer ce blog et tous ses messages ?\nNotez que ceci ne supprimera pas le blog sur les appareils des autres personnes.</string>
|
||||
<string name="blogs_remove_blog_ok">Supprimer le blog</string>
|
||||
<string name="blogs_blog_removed">Blog supprimé</string>
|
||||
<string name="blogs_reblog_comment_hint">Ajouter un commentaire (optionnel)</string>
|
||||
<string name="blogs_reblog_button">Reblog</string>
|
||||
<string name="blogs_blog_post_created">L\'article de blogue a été créé</string>
|
||||
<string name="blogs_blog_post_received">Un nouvel article de blogue a été reçu</string>
|
||||
<string name="blogs_blog_post_scroll_to">Faire défiler jusqu\'à</string>
|
||||
<string name="blogs_feed_empty_state">Ceci est le flux global des blogues.\n\nIl semble que personne n\'ait encore rien écrit.\n\nSoyez le premier et touchez l\'icône de crayon pour rédiger un nouvel article de blogue.</string>
|
||||
<string name="blogs_remove_blog">Supprimer le blogue</string>
|
||||
<string name="blogs_remove_blog_dialog_message">Voulez-vous vraiment supprimer ce blogue et tous ses messages ?\nNotez que cela ne le supprimera pas des appareils d\'autrui.</string>
|
||||
<string name="blogs_remove_blog_ok">Supprimer le blogue</string>
|
||||
<string name="blogs_blog_removed">Le blogue a été supprimé</string>
|
||||
<string name="blogs_reblog_comment_hint">Ajouter un commentaire (facultatif)</string>
|
||||
<string name="blogs_reblog_button">Rebloguer</string>
|
||||
<!--Blog Sharing-->
|
||||
<string name="blogs_sharing_share">Partager le blog</string>
|
||||
<string name="blogs_sharing_error">Une erreur s\'est produite lors du partage du blog.</string>
|
||||
<string name="blogs_sharing_button">Partager le blog</string>
|
||||
<string name="blogs_sharing_snackbar">Blog partagé avec les contacts choisis</string>
|
||||
<string name="blogs_sharing_response_accepted_sent">Vous avez accepté l\'invitation au blog de %s.</string>
|
||||
<string name="blogs_sharing_response_declined_sent">Vous avez refusé l\'invitation au blog de %s.</string>
|
||||
<string name="blogs_sharing_response_accepted_received">%s a accepté l\'invitation au blog.</string>
|
||||
<string name="blogs_sharing_response_declined_received">%s a décliné l\'invitation au blog.</string>
|
||||
<string name="blogs_sharing_invitation_received">%1$s a partagé le blog \"%2$s\" avec vous.</string>
|
||||
<string name="blogs_sharing_invitation_sent">Vous avez partagé le blog \"%1$s\" avec %2$s.</string>
|
||||
<string name="blogs_sharing_invitations_title">Invitations au blog</string>
|
||||
<string name="blogs_sharing_joined_toast">Abonné au Blog</string>
|
||||
<string name="blogs_sharing_declined_toast">Invitation au blog refusée</string>
|
||||
<string name="sharing_status_blog">Quiconque est abonné à un blog peut le partager avec ses contacts. Vous partagez ce blog avec les contacts suivants. Il y a peut-être d\'autres abonnés que vous ne pouvez voir.</string>
|
||||
<string name="blogs_sharing_share">Partager le blogue</string>
|
||||
<string name="blogs_sharing_error">Une erreur est survenue lors du partage du blogue.</string>
|
||||
<string name="blogs_sharing_button">Partager le blogue</string>
|
||||
<string name="blogs_sharing_snackbar">Le blogue a été partagé avec les contacts choisis</string>
|
||||
<string name="blogs_sharing_response_accepted_sent">Vous avez accepté l\'invitation de %s au blogue.</string>
|
||||
<string name="blogs_sharing_response_declined_sent">Vous avez refusé l\'invitation de %s au blogue.</string>
|
||||
<string name="blogs_sharing_response_accepted_received">%s a accepté l\'invitation au blogue.</string>
|
||||
<string name="blogs_sharing_response_declined_received">%s a refusé l\'invitation au blogue.</string>
|
||||
<string name="blogs_sharing_invitation_received">%1$s a partagé le blogue « %2$s » avec vous.</string>
|
||||
<string name="blogs_sharing_invitation_sent">Vous avez partagé le blogue « %1$s » avec %2$s.</string>
|
||||
<string name="blogs_sharing_invitations_title">Invitations au blogue</string>
|
||||
<string name="blogs_sharing_joined_toast">Est abonné au blogue</string>
|
||||
<string name="blogs_sharing_declined_toast">L\'invitation au blogue a été refusée</string>
|
||||
<string name="sharing_status_blog">Quiconque est abonné à un blogue peut le partager avec ses contacts. Vous partagez ce blogue avec les contacts suivants. Il peut aussi y avoir d\'autres abonnés que vous ne pouvez pas voir.</string>
|
||||
<!--RSS Feeds-->
|
||||
<string name="blogs_rss_feeds_import">Import de flux RSS</string>
|
||||
<string name="blogs_rss_feeds_import">Importer un flux RSS</string>
|
||||
<string name="blogs_rss_feeds_import_button">Importer</string>
|
||||
<string name="blogs_rss_feeds_import_hint">Saisir l\'URL du Flux RSS</string>
|
||||
<string name="blogs_rss_feeds_import_error">Désolé, une erreur s\'est produite lors de l’importation du flux.</string>
|
||||
<string name="blogs_rss_feeds_manage">Gérer le flux RSS</string>
|
||||
<string name="blogs_rss_feeds_manage_imported">Importé :</string>
|
||||
<string name="blogs_rss_feeds_import_hint">Saisir l\'URL du flux RSS</string>
|
||||
<string name="blogs_rss_feeds_import_error">Nous sommes désolé ! Une erreur est survenue lors de l’importation de votre flux.</string>
|
||||
<string name="blogs_rss_feeds_manage">Gérer les flux RSS</string>
|
||||
<string name="blogs_rss_feeds_manage_imported">Importés :</string>
|
||||
<string name="blogs_rss_feeds_manage_author">Auteur :</string>
|
||||
<string name="blogs_rss_feeds_manage_updated">Dernière mise à jour :</string>
|
||||
<string name="blogs_rss_remove_feed">Supprimer le flux</string>
|
||||
<string name="blogs_rss_remove_feed_dialog_message">Êtes-vous sûr de vouloir supprimer ce flux et tous ses messages ?\nLes messages que vous avez partagés ne seront pas supprimés des appareils des autres personnes.</string>
|
||||
<string name="blogs_rss_remove_feed_dialog_message">Voulez-vous vraiment supprimer ce flux et tous ses messages ?\nLes articles que vous avez partagés ne seront pas supprimés des appareils d\'autrui.</string>
|
||||
<string name="blogs_rss_remove_feed_ok">Supprimer le flux</string>
|
||||
<string name="blogs_rss_feeds_manage_delete_error">Le flux n\'a pas pu être supprimé !</string>
|
||||
<string name="blogs_rss_feeds_manage_empty_state">Vous n\'avez importé aucun flux RSS.\n\nPourquoi ne pas cliquer le plus dans le coin en haut à droite pour ajouter votre premier ?</string>
|
||||
<string name="blogs_rss_feeds_manage_error">Problème lors du chargement de vos flux. Réessayer ultérieurement.</string>
|
||||
<string name="blogs_rss_feeds_manage_delete_error">Impossible de supprimer le flux !</string>
|
||||
<string name="blogs_rss_feeds_manage_empty_state">Vous n\'avez pas encore importé de flux RSS.\n\nPourquoi ne pas cliquer sur l\'icône +, en haut à droite, pour ajouter votre premier flux ?</string>
|
||||
<string name="blogs_rss_feeds_manage_error">Un problème est survenu lors du chargement de vos flux. Veuillez ressayer ultérieurement.</string>
|
||||
<!--Settings Network-->
|
||||
<string name="network_settings_title">Réseaux </string>
|
||||
<string name="bluetooth_setting">Connecter par Bluetooth</string>
|
||||
<string name="network_settings_title">Réseaux</string>
|
||||
<string name="bluetooth_setting">Se connecter par Bluetooth</string>
|
||||
<string name="bluetooth_setting_enabled">Lorsque des contacts sont à proximité</string>
|
||||
<string name="bluetooth_setting_disabled">Uniquement lors de l\'ajout de contacts</string>
|
||||
<string name="tor_network_setting">Connecter via Tor</string>
|
||||
<string name="tor_network_setting">Se connecter avec Tor</string>
|
||||
<string name="tor_network_setting_never">Jamais</string>
|
||||
<string name="tor_network_setting_wifi">Seulement par Wi-Fi</string>
|
||||
<string name="tor_network_setting_always">Par Wi-Fi ou données mobiles</string>
|
||||
<string name="tor_network_setting_always">En utilisant le Wi-Fi ou le service mobile de données</string>
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">Sécurité</string>
|
||||
<string name="change_password">Modifier le mot de passe</string>
|
||||
<string name="current_password">Tapez le mot de passe actuel :</string>
|
||||
<string name="choose_new_password">Choisir le nouveau mot de passe :</string>
|
||||
<string name="confirm_new_password">Confirmer le nouveau mot de passe :</string>
|
||||
<string name="change_password">Changer le mot de passe</string>
|
||||
<string name="current_password">Saisir votre mot de passe actuel :</string>
|
||||
<string name="choose_new_password">Choisir votre nouveau mot de passe :</string>
|
||||
<string name="confirm_new_password">Confirmer votre nouveau mot de passe :</string>
|
||||
<string name="password_changed">Le mot de passe a été changé.</string>
|
||||
<string name="panic_setting">Paramètres du bouton Panique</string>
|
||||
<string name="panic_setting_title">Bouton Panique</string>
|
||||
<string name="panic_setting_hint">Configurer Briar pour l\'utilisation de l\'application du bouton Panique</string>
|
||||
<string name="panic_app_setting_title">Application bouton Panique</string>
|
||||
<string name="unknown_app">une application inconnue</string>
|
||||
<string name="panic_app_setting_summary">Aucune application définie</string>
|
||||
<string name="panic_setting">Paramètres du bouton d\'urgence</string>
|
||||
<string name="panic_setting_title">Bouton d\'urgence</string>
|
||||
<string name="panic_setting_hint">Configurer la réaction de Briar lorsque vous utilisez une application de bouton d\'urgence</string>
|
||||
<string name="panic_app_setting_title">Application de bouton d\'urgence</string>
|
||||
<string name="unknown_app">une appli inconnue</string>
|
||||
<string name="panic_app_setting_summary">Aucune appli n\'a été définie</string>
|
||||
<string name="panic_app_setting_none">Aucune</string>
|
||||
<string name="dialog_title_connect_panic_app">Confirmer l\'application Panique</string>
|
||||
<string name="dialog_message_connect_panic_app">Êtes-vous sûr de vouloir autoriser %1$s à déclencher les actions destructives du bouton Panique ?</string>
|
||||
<string name="lock_setting_title">Se déconnecter</string>
|
||||
<string name="lock_setting_summary">Se déconnecter de Briar lorsque le bouton Panique est pressé</string>
|
||||
<string name="dialog_title_connect_panic_app">Confirmer l\'application d\'urgence</string>
|
||||
<string name="dialog_message_connect_panic_app">Voulez-vous vraiment autoriser %1$s à déclencher les actions destructrices du bouton d\'urgence ?</string>
|
||||
<string name="lock_setting_title">Déconnexion</string>
|
||||
<string name="lock_setting_summary">Se déconnecter de Briar lorsqu\'on appuie sur un bouton d\'urgence</string>
|
||||
<string name="purge_setting_title">Supprimer le compte</string>
|
||||
<string name="purge_setting_summary">Supprimer votre compte lorsqu\'un bouton Panique est pressé. Attention : ceci supprimera définitivement vos identités, contacts et messages.</string>
|
||||
<string name="purge_setting_summary">Supprimer votre compte Briar lorsqu\'on appuie sur un bouton d\'urgence. Attention : cela supprimera définitivement vos identités, contacts et messages</string>
|
||||
<string name="uninstall_setting_title">Désinstaller Briar</string>
|
||||
<string name="uninstall_setting_summary">Ceci nécessite une confirmation manuelle en cas d\'événement de panique</string>
|
||||
<string name="uninstall_setting_summary">Une confirmation manuelle est exigée en cas d\'événement d\'urgence</string>
|
||||
<!--Settings Notifications-->
|
||||
<string name="notification_settings_title">Notifications</string>
|
||||
<string name="notify_private_messages_setting_title">Messages privés</string>
|
||||
<string name="notify_private_messages_setting_summary">Afficher les notifications pour les messages privés</string>
|
||||
<string name="notify_private_messages_setting_summary">Afficher des alertes pour les messages privés</string>
|
||||
<string name="notify_group_messages_setting_title">Messages de groupe</string>
|
||||
<string name="notify_group_messages_setting_summary">Afficher les notifications pour les messages de groupe</string>
|
||||
<string name="notify_forum_posts_setting_title">Messages de forum</string>
|
||||
<string name="notify_forum_posts_setting_summary">Afficher les notifications des messages de forum</string>
|
||||
<string name="notify_blog_posts_setting_title">Messages de blog</string>
|
||||
<string name="notify_blog_posts_setting_summary">Afficher les notifications pour les messages de blog</string>
|
||||
<string name="notify_vibration_setting">Vibration</string>
|
||||
<string name="notify_lock_screen_setting_title">Verrouiller l\'écran</string>
|
||||
<string name="notify_lock_screen_setting_summary">Afficher les notifications sur l\'écran verrouillé</string>
|
||||
<string name="notify_sound_setting">Sonnerie</string>
|
||||
<string name="notify_group_messages_setting_summary">Afficher des alertes pour les messages de groupe</string>
|
||||
<string name="notify_forum_posts_setting_title">Articles de forum</string>
|
||||
<string name="notify_forum_posts_setting_summary">Afficher des alertes pour les articles de forum</string>
|
||||
<string name="notify_blog_posts_setting_title">Articles de blogue</string>
|
||||
<string name="notify_blog_posts_setting_summary">Afficher des alertes pour pour les articles de blogue</string>
|
||||
<string name="notify_vibration_setting">Vibrer</string>
|
||||
<string name="notify_lock_screen_setting_title">Écran de verrouillage</string>
|
||||
<string name="notify_lock_screen_setting_summary">Afficher les notifications sur l\'écran de verrouillage</string>
|
||||
<string name="notify_sound_setting">Son</string>
|
||||
<string name="notify_sound_setting_default">Sonnerie par défaut</string>
|
||||
<string name="notify_sound_setting_disabled">Aucune</string>
|
||||
<string name="choose_ringtone_title">Choisir la sonnerie</string>
|
||||
<string name="notify_sound_setting_disabled">Aucun</string>
|
||||
<string name="choose_ringtone_title">Choisir une sonnerie</string>
|
||||
<!--Settings Feedback-->
|
||||
<string name="feedback_settings_title">Commentaires</string>
|
||||
<string name="send_feedback">Envoyer vos commentaires</string>
|
||||
<string name="feedback_settings_title">Rétroaction</string>
|
||||
<string name="send_feedback">Envoyer une rétroaction</string>
|
||||
<!--Link Warning-->
|
||||
<string name="link_warning_title">Avertissement de lien</string>
|
||||
<string name="link_warning_intro">Vous êtes sur le point d\'ouvrir le lien suivant via une application externe.</string>
|
||||
<string name="link_warning_text">Ceci pourrait être utilisé pour vous identifier. Évaluez la confiance que vous avez en la personne qui vous a envoyé ce lien et envisagez de l\'ouvrir avec Orfox.</string>
|
||||
<string name="link_warning_intro">Vous êtes sur le point d\'ouvrir le lien suivant avec une appli externe.</string>
|
||||
<string name="link_warning_text">Cela pourrait être utilisé pour vous identifier. Décidez si vous faites confiance à la personne qui vous a envoyé ce lien et envisagez de l\'ouvrir avec Orfox.</string>
|
||||
<string name="link_warning_open_link">Ouvrir le lien</string>
|
||||
<!--Crash Reporter-->
|
||||
<string name="crash_report_title">Rapport de plantage Briar</string>
|
||||
<string name="crash_report_title">Rapport de plantage de Briar</string>
|
||||
<string name="briar_crashed">Désolé, Briar a planté.</string>
|
||||
<string name="not_your_fault">Vous n\'y êtes pour rien.</string>
|
||||
<string name="please_send_report">Aidez-nous à améliorer Briar en nous envoyant un rapport de plantage.</string>
|
||||
<string name="please_send_report">Veuillez nous aider à améliorer Briar en nous envoyant un rapport de plantage.</string>
|
||||
<string name="report_is_encrypted">Nous promettons que le rapport est chiffré et envoyé en toute sécurité. </string>
|
||||
<string name="feedback_title">Commentaires</string>
|
||||
<string name="describe_crash">Décrivez ce qui s\'est produit (optionnel)</string>
|
||||
<string name="enter_feedback">Saisir vos commentaires</string>
|
||||
<string name="optional_contact_email">Votre adresse émail (optionnel)</string>
|
||||
<string name="include_debug_report_crash">Ajouter des données anonymes concernant le plantage</string>
|
||||
<string name="include_debug_report_feedback">Ajouter des données anonymes concernant cet appareil</string>
|
||||
<string name="feedback_title">Rétroaction</string>
|
||||
<string name="describe_crash">Décrivez ce qui s\'est produit (facultatif)</string>
|
||||
<string name="enter_feedback">Saisir votre rétroaction</string>
|
||||
<string name="optional_contact_email">Votre adresse courriel (facultatif)</string>
|
||||
<string name="include_debug_report_crash">Inclure des données anonymes concernant le plantage</string>
|
||||
<string name="include_debug_report_feedback">Inclure des données anonymes concernant cet appareil</string>
|
||||
<string name="could_not_load_report_data">Impossible de charger les données du rapport.</string>
|
||||
<string name="send_report">Envoyer le rapport</string>
|
||||
<string name="close">Fermer</string>
|
||||
<string name="dev_report_saved">Rapport enregistré. Il sera envoyé lors de votre prochaine connexion à Briar.</string>
|
||||
<string name="dev_report_saved">Le rapport a été enregistré. Il sera envoyé lors de votre prochaine connexion à Briar.</string>
|
||||
<!--Sign Out-->
|
||||
<string name="progress_title_logout">Déconnexion de Briar ...</string>
|
||||
<string name="progress_title_logout">Déconnexion de Briar…</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">Superposition d\'écran détectée</string>
|
||||
<string name="screen_filter_body">Une autre app s\'affiche par dessus Briar. Pour protéger votre sécurité, Briar ne répond pas au toucher lorsqu\'une autre app s\'affiche par dessus.\n\nEssayer de fermer les applications suivantes lorsque vous utilisez Briar :\n\n%1$s</string>
|
||||
<string name="screen_filter_title">Une superposition d\'écran a été détectée</string>
|
||||
<string name="screen_filter_body">Une autre appli s\'affiche par dessus Briar. Pour protéger votre sécurité, Briar ne répondra pas au toucher si autre appli s\'affiche par dessus.\n\nEssayez de fermer les applis suivantes lorsque vous utilisez Briar :\n\n%1$s</string>
|
||||
</resources>
|
||||
|
||||
367
briar-android/src/main/res/values-nb/strings.xml
Normal file
367
briar-android/src/main/res/values-nb/strings.xml
Normal file
@@ -0,0 +1,367 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<!--Setup-->
|
||||
<string name="setup_title">Oppsett av Briar</string>
|
||||
<string name="setup_explanation">Din Briar-konto er lagret kryptert på din enhet, ikke i skyen. Hvis du avinstallerer Briar eller glemmer passordet ditt, går det ikke an å gjenopprette kontoen og dataen din.</string>
|
||||
<string name="choose_nickname">Velg kallenavn</string>
|
||||
<string name="choose_password">Velg passord</string>
|
||||
<string name="confirm_password">Bekreft passord</string>
|
||||
<string name="name_too_long">For langt navn</string>
|
||||
<string name="password_too_weak">Passordet er for svakt</string>
|
||||
<string name="passwords_do_not_match">Passordene samsvarer ikke</string>
|
||||
<string name="create_account_button">Opprett konto</string>
|
||||
<!--Login-->
|
||||
<string name="enter_password">Skriv inn passord:</string>
|
||||
<string name="try_again">Feil passord, prøv igjen</string>
|
||||
<string name="sign_in_button">Logg inn</string>
|
||||
<string name="forgotten_password">Jeg har glemt passordet mitt</string>
|
||||
<string name="dialog_title_lost_password">Tapt passord</string>
|
||||
<string name="dialog_message_lost_password">Din Briar-konto er lagret kryptert på din enhet, ikke i skyen, så du kan ikke tilbakestille passordet ditt. Ønsker du å slette kontoen og starte igjen?\n\nMerk: Identitetene dine, kontaktene og meldingene vil gå tapt for alltid.</string>
|
||||
<string name="startup_failed_notification_title">Briar kunne ikke starte</string>
|
||||
<string name="startup_failed_notification_text">Det kan hende du må reinstallere Briar.</string>
|
||||
<string name="startup_failed_activity_title">Oppstartsfeil med Briar</string>
|
||||
<string name="startup_failed_db_error">Av ukjent brunn, er din Briar-database skadet og kan ikke repareres. Kontoen din, alle data, og din kontakter har gått tapt for alltid. Beklageligvis må du reinstallere Briar og sette opp en ny konto.</string>
|
||||
<string name="startup_failed_service_error">Briar kunne ikke starte det nødvendige programtillegget. Reinstallasjon av Briar fikser vanligvis dette problemet. Merk deg dog at kontoen og all data tilknyttet den vil gå tapt for godt siden Briar ikke bruker sentrale tjenere å lagre dataen din på.</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="one">Dette er en betaversjon av Briar. Din konto vil utløpe om %d dag og kan ikke fornyes.</item>
|
||||
<item quantity="other">Dette er en betaversjon av Briar. Din konto vil utløpe om %d dager og kan ikke fornyes.</item>
|
||||
</plurals>
|
||||
<string name="expiry_date_reached">Denne programvaren har utløpt.\nTakk for at du testet den.</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">Åpne navigasjonsskuffen</string>
|
||||
<string name="nav_drawer_close_description">Lukk navigasjonsskuffen</string>
|
||||
<string name="contact_list_button">Kontakter</string>
|
||||
<string name="groups_button">Private grupper</string>
|
||||
<string name="forums_button">Forum</string>
|
||||
<string name="blogs_button">Blogger</string>
|
||||
<string name="settings_button">Innstillinger</string>
|
||||
<string name="sign_out_button">Logg ut</string>
|
||||
<!--Transports-->
|
||||
<string name="transport_tor">Internett</string>
|
||||
<string name="transport_bt">Blåtann</string>
|
||||
<string name="transport_lan">Wi-Fi</string>
|
||||
<!--Notifications-->
|
||||
<string name="ongoing_notification_title">Logget inn på Briar</string>
|
||||
<string name="ongoing_notification_text">Trykk for å åpne Briar.</string>
|
||||
<plurals name="private_message_notification_text">
|
||||
<item quantity="one">Ny privat melding.</item>
|
||||
<item quantity="other">%d nye private meldinger.</item>
|
||||
</plurals>
|
||||
<plurals name="group_message_notification_text">
|
||||
<item quantity="one">Ny gruppemelding.</item>
|
||||
<item quantity="other">%d nye gruppemelindger.</item>
|
||||
</plurals>
|
||||
<plurals name="forum_post_notification_text">
|
||||
<item quantity="one">Ny forumpost.</item>
|
||||
<item quantity="other">%d nye forumposter.</item>
|
||||
</plurals>
|
||||
<plurals name="blog_post_notification_text">
|
||||
<item quantity="one">Ny bloggpost.</item>
|
||||
<item quantity="other">%d nye bloggposter.</item>
|
||||
</plurals>
|
||||
<!--Misc-->
|
||||
<string name="now">nå</string>
|
||||
<string name="show">Vis</string>
|
||||
<string name="hide">Skjul</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="cancel">Avbryt</string>
|
||||
<string name="got_it">Skjønner</string>
|
||||
<string name="delete">Slett</string>
|
||||
<string name="accept">Godta</string>
|
||||
<string name="decline">Avslå</string>
|
||||
<string name="options">Valg</string>
|
||||
<string name="online">Pålogget</string>
|
||||
<string name="offline">Frakoblet</string>
|
||||
<string name="send">Send</string>
|
||||
<string name="allow">Tillat</string>
|
||||
<string name="open">Åpne</string>
|
||||
<string name="no_data">Ingen data</string>
|
||||
<string name="ellipsis">…</string>
|
||||
<string name="text_too_long">Innskrevet tekst er for lang</string>
|
||||
<string name="show_onboarding">Vis hjelpedialogvindu</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">Det later til at du er ny her og ikke har noen kontakter enda.\n\nTrykk på \"+\"-ikonet på toppen og følg instruksjonene for å legge til noen venner på listen din.\n\nHusk: Du kan bare legge til nye kontakter ansikt-til-ansikt for å forhindre noen fra å etterligne deg eller fra å lese meldingene dine i framtiden.</string>
|
||||
<string name="date_no_private_messages">Ingen meldinger.</string>
|
||||
<string name="no_private_messages">Dette er samtaleoversikten.\n\nDet ser ut til at det er mangel på samtaler.\n\nTrykk på inndatafeltet på bunnen for å starte en samtale.</string>
|
||||
<string name="message_hint">Skriv melding</string>
|
||||
<string name="delete_contact">Slett kontakt</string>
|
||||
<string name="dialog_title_delete_contact">Bekreft sletting av kontakt</string>
|
||||
<string name="dialog_message_delete_contact">Er du sikker på at du vil fjerne denne kontakten og alle meldinger utvekslet med denne kontakten?</string>
|
||||
<string name="contact_deleted_toast">Kontakt slettet</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Legg til en kontakt</string>
|
||||
<string name="your_nickname">Velg identiteten du ønsker å bruke:</string>
|
||||
<string name="face_to_face">Du må møte personen du ønsker å legge til som kontakt.\n\nDette vil forhindre folk fra å etterligne deg eller lese meldingene dine i fremtiden.</string>
|
||||
<string name="continue_button">Fortsett</string>
|
||||
<string name="your_invitation_code">Din invitasjonskode er</string>
|
||||
<string name="enter_invitation_code">Skriv inn invitasjonskoden fra din kontakt:</string>
|
||||
<string name="searching_format">Søker etter kontakt med invitasjonskode %06d\u2026</string>
|
||||
<string name="connection_failed">Tilkobling mislyktes</string>
|
||||
<string name="could_not_find_contact">Briar kunne ikke finne kontakten din nærheten</string>
|
||||
<string name="try_again_button">Prøv igjen</string>
|
||||
<string name="connected_to_contact">Tilkoblet kontakt</string>
|
||||
<string name="calculating_confirmation_code">Regner ut bekreftelseskode\u2026</string>
|
||||
<string name="your_confirmation_code">Din bekreftelseskode er</string>
|
||||
<string name="enter_confirmation_code">Skriv inn din kontakts bekreftelseskode:</string>
|
||||
<string name="waiting_for_contact">Venter på kontakt\u2026</string>
|
||||
<string name="waiting_for_contact_to_scan">Venter på at kontakten skal skanne og koble til\u2026</string>
|
||||
<string name="exchanging_contact_details">Utveksler kontaktdetaljer\u2026</string>
|
||||
<string name="codes_do_not_match">Kodene samsvarer ikke</string>
|
||||
<string name="interfering">Dette kan bety at noen prøver å tukle med tilkoblingen</string>
|
||||
<string name="contact_added_toast">Kontakt lagt til: %s</string>
|
||||
<string name="contact_already_exists">Kontakten %s finnes allerede</string>
|
||||
<string name="contact_exchange_failed">Kontaktutveksling mislyktes</string>
|
||||
<string name="qr_code_invalid">QR-koden er ugyldig</string>
|
||||
<string name="connecting_to_device">Kobler til enhet\u2026</string>
|
||||
<string name="authenticating_with_device">Autentiserer med enhet\u2026</string>
|
||||
<string name="connection_aborted_local">Tilkoblingen ble avbrutt på din side! Dette kan bety at noen prøver å tukle med tilkoblingen din</string>
|
||||
<string name="connection_aborted_remote">Tilkobling avbrutt av din kontakt! Dette kan bety at noen prøver å tukle med vedkommendes tilkobling</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">Introduser kontaktene dine</string>
|
||||
<string name="introduction_onboarding_text">Du kan introdusere dine kontakter til hverandre, slik at de ikke trenger å møtes personlig for å treffes på Briar.</string>
|
||||
<string name="introduction_activity_title">Velg kontakt</string>
|
||||
<string name="introduction_message_title">Introduser kontakter</string>
|
||||
<string name="introduction_message_hint">Legg til melding (valgfritt)</string>
|
||||
<string name="introduction_button">Introduser ovenfor hverandre</string>
|
||||
<string name="introduction_sent">Din introduksjon har blitt sendt.</string>
|
||||
<string name="introduction_error">Feil under utstedelse av introduksjon.</string>
|
||||
<string name="introduction_response_error">Feil under svar på introduksjon</string>
|
||||
<string name="introduction_request_sent">Du har spurt %1$s om å bli introdusert ovenfor %2$s.</string>
|
||||
<string name="introduction_request_received">%1$s har spurt om å introdusere deg ovenfor %2$s. Ønsker du å legge til %2$s på din kontaktliste?</string>
|
||||
<string name="introduction_request_exists_received">%1$s har spurt om å introdusere deg ovenfor %2$s, men %2$s er allerede på din kontaktliste. Siden %1$s kanskje ikke vet dette, kan du fremdeles svare:</string>
|
||||
<string name="introduction_request_answered_received">%1$s har spurt om å introdusere deg ovenfor %2$s.</string>
|
||||
<string name="introduction_response_accepted_sent">Du er nå bekjent %1$s.</string>
|
||||
<string name="introduction_response_declined_sent">Du avslo introduksjonen med %1$s.</string>
|
||||
<string name="introduction_response_accepted_received">%1$s er nå bekjent %2$s.</string>
|
||||
<string name="introduction_response_declined_received">%1$s avslo introduksjonen med %2$s.</string>
|
||||
<string name="introduction_response_declined_received_by_introducee">%1$s sier at %2$s avslo introduksjonen.</string>
|
||||
<plurals name="introduction_notification_text">
|
||||
<item quantity="one">Ny kontakt lagt til.</item>
|
||||
<item quantity="other">%d nye kontakter lagt til.</item>
|
||||
</plurals>
|
||||
<!--Private Groups-->
|
||||
<string name="groups_list_empty">Du deltar ikke i noen grupper.\n\nTrykk på \"+\"-ikonet på toppen for å opprette en gruppe selv, eller spørre noen av kontaktene dine om å bli invitert inn i en av deres grupper.</string>
|
||||
<string name="groups_created_by">Opprettet av %s</string>
|
||||
<plurals name="messages">
|
||||
<item quantity="one">%d melding</item>
|
||||
<item quantity="other">%d meldinger</item>
|
||||
</plurals>
|
||||
<string name="groups_group_is_empty">Denne gruppen er tom</string>
|
||||
<string name="groups_group_is_dissolved">Denne gruppen har blitt oppløst</string>
|
||||
<string name="groups_remove">Fjern</string>
|
||||
<string name="groups_create_group_title">Opprett privat gruppe</string>
|
||||
<string name="groups_create_group_button">Opprett gruppe</string>
|
||||
<string name="groups_create_group_invitation_button">Send invitasjon</string>
|
||||
<string name="groups_create_group_hint">Velg et navn for din private gruppe</string>
|
||||
<string name="groups_invitation_sent">Gruppeinvitasjon sendt</string>
|
||||
<string name="groups_message_sent">Melding sendt</string>
|
||||
<string name="groups_member_list">Medlemsliste</string>
|
||||
<string name="groups_invite_members">Inviter medlemmer</string>
|
||||
<string name="groups_member_created_you">Du opprettet gruppen</string>
|
||||
<string name="groups_member_created">%s opprettet gruppen</string>
|
||||
<string name="groups_member_joined_you">Du tok del i gruppen</string>
|
||||
<string name="groups_member_joined">%s tok del i gruppen</string>
|
||||
<string name="groups_leave">Forlat gruppe</string>
|
||||
<string name="groups_leave_dialog_title">Bekreft at du vil forlate gruppen</string>
|
||||
<string name="groups_leave_dialog_message">Er du sikker på at du vil forlate denne gruppen?</string>
|
||||
<string name="groups_dissolve">Oppløs gruppe</string>
|
||||
<string name="groups_dissolve_dialog_title">Bekreft oppløsning av gruppe</string>
|
||||
<string name="groups_dissolve_dialog_message">Er du sikker på at du vil oppløse denne gruppen?\n\nAlle andre medlemmer vil ikke kunne fortsette deres samtaler, og vil kanskje ikke motta de seneste meldingene.</string>
|
||||
<string name="groups_dissolve_button">Oppløs</string>
|
||||
<string name="groups_dissolved_dialog_title">Gruppen har blitt oppløst</string>
|
||||
<string name="groups_dissolved_dialog_message">Oppretteren av denne gruppen har oppløst den.\n\nDu kan ikke lenger skrive meldinger til gruppen, og vil kanskje ikke få alle poster som har blitt skrevet.</string>
|
||||
<!--Private Group Invitations-->
|
||||
<string name="groups_invitations_title">Gruppeinvitasjoner</string>
|
||||
<string name="groups_invitations_invitation_sent">Du har invitert %1$s til å ta del i gruppen \"%2$s\".</string>
|
||||
<string name="groups_invitations_invitation_received">%1$s har inviter deg til å ta del i gruppen \"%2$s\".</string>
|
||||
<string name="groups_invitations_joined">Tok del i gruppe</string>
|
||||
<string name="groups_invitations_declined">Gruppeinvitasjon avslått</string>
|
||||
<plurals name="groups_invitations_open">
|
||||
<item quantity="one">%d åpen gruppeinvitasjon</item>
|
||||
<item quantity="other">%d åpne gruppeinvitasjoner</item>
|
||||
</plurals>
|
||||
<string name="groups_invitations_response_accepted_sent">Du godtok gruppeinvitasjonen fra %s.</string>
|
||||
<string name="groups_invitations_response_declined_sent">Du avslo gruppeinvitasjoner fra %s.</string>
|
||||
<string name="groups_invitations_response_accepted_received">%s godtok gruppeinvitasjonen.</string>
|
||||
<string name="groups_invitations_response_declined_received">%s avslo gruppeinvitasjonen</string>
|
||||
<string name="sharing_status_groups">Bare grunnleggeren kan invitere nye medlemmer til gruppen. Nedenfor har du alle medlemmene i den.</string>
|
||||
<!--Private Groups Revealing Contacts-->
|
||||
<string name="groups_reveal_contacts">Avslør kontakter</string>
|
||||
<string name="groups_reveal_dialog_message">Du kan velge hvorvidt du vil tilkjennegi kontakter ovenfor alle nåværende og fremtidige medlemmer av denne gruppen.\n\nDette gjør tilkoblingen til gruppen raskere og mer pålitelig, fordi du kan kommunisere med tilkjennegitte kontakter selv om grunnleggeren av gruppen er frakoblet.</string>
|
||||
<string name="groups_reveal_visible">Kontaktforhold er synlige for gruppen</string>
|
||||
<string name="groups_reveal_visible_revealed_by_us">Kontaktforhold er synlige for gruppen (avslørt av deg)</string>
|
||||
<string name="groups_reveal_visible_revealed_by_contact">Kontaktforhold er synlige for gruppen (avslørt av %s)</string>
|
||||
<string name="groups_reveal_invisible">Kontaktforhold er ikke synlige for gruppen</string>
|
||||
<!--Forums-->
|
||||
<string name="no_forums">Du har ingen forum enda.\n\nHvorfor oppretter du ikke et selv ved å trukke på \"+\"-ikonet på toppen?\n\nDu kan også spørre dine kontakter om å dele forum med deg.</string>
|
||||
<string name="create_forum_title">Opprett forum</string>
|
||||
<string name="choose_forum_hint">Velg et navn for ditt forum</string>
|
||||
<string name="create_forum_button">Opprett forum</string>
|
||||
<string name="forum_created_toast">Forum opprettet</string>
|
||||
<string name="no_forum_posts">Dette forumet er tomt.\n\nBruk penneikonet på toppen for å føre den første posten i pennen.\n\nEr det ensomt her? Del dette forumet med flere av dine kontakter!</string>
|
||||
<string name="no_posts">Ingen poster</string>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">%d post</item>
|
||||
<item quantity="other">%d poster</item>
|
||||
</plurals>
|
||||
<string name="forum_new_entry_posted">Forumoppføring postet</string>
|
||||
<string name="forum_new_message_hint">Ny oppføring</string>
|
||||
<string name="forum_message_reply_hint">Nytt svar</string>
|
||||
<string name="btn_reply">Svar</string>
|
||||
<string name="forum_leave">Forlat forum</string>
|
||||
<string name="dialog_title_leave_forum">Bekreft forlating av forum</string>
|
||||
<string name="dialog_message_leave_forum">Er du sikker på at du vil forlate dette forumet? Kontakter du har delt dette forumet med kan bli forhindret fra å motta oppdateringer fra dette forumet.</string>
|
||||
<string name="dialog_button_leave">Forlat</string>
|
||||
<string name="forum_left_toast">Forlot forum</string>
|
||||
<!--Forum Sharing-->
|
||||
<string name="forum_share_button">Del forum</string>
|
||||
<string name="contacts_selected">Kontakter valgt</string>
|
||||
<string name="activity_share_toolbar_header">Velg kontakter</string>
|
||||
<string name="no_contacts_selector">Det later til at du er ny her og ikke har noen kontakter enda.\n\nKom tilbake hit etter at du har lagt til din første kontakt.</string>
|
||||
<string name="forum_shared_snackbar">Forum delt med valgte kontakter</string>
|
||||
<string name="forum_share_message">Legg til melding (valgfritt)</string>
|
||||
<string name="forum_share_error">Feil ved deling av dette forumet.</string>
|
||||
<string name="forum_invitation_received">%1$s har delt forumet \"%2$s\" med deg.</string>
|
||||
<string name="forum_invitation_sent">Du har det forumet \"%1$s\" med %2$s.</string>
|
||||
<string name="forum_invitations_title">Foruminvitasjoner</string>
|
||||
<string name="forum_invitation_exists">Du godtok invitasjonen til dette forumet allerede. Å godta flere kontakter vil bidra til og styrke kommunikasjonen på forumet.</string>
|
||||
<string name="forum_joined_toast">Tok del i forumet</string>
|
||||
<string name="forum_declined_toast">Foruminvitasjon avslått</string>
|
||||
<string name="shared_by_format">Delt av %s</string>
|
||||
<string name="forum_invitation_already_sharing">Deler allerede</string>
|
||||
<string name="forum_invitation_response_accepted_sent">Du godtok foruminvitasjonen fra %s.</string>
|
||||
<string name="forum_invitation_response_declined_sent">Du avslo foruminvitasjonen fra %s.</string>
|
||||
<string name="forum_invitation_response_accepted_received">%s godtok foruminvitasjonen.</string>
|
||||
<string name="forum_invitation_response_declined_received">%s avslo foruminvitasjonen.</string>
|
||||
<string name="sharing_status">Delingsstatus</string>
|
||||
<string name="sharing_status_forum">Ethvert medlem av et forum kan dele det med sine kontakter. Du deler dette forumet med følgende kontakter. Det kan også være andre medlemmer du ikke kan se.</string>
|
||||
<string name="shared_with">Delt med %1$d (%2$d pålogget)</string>
|
||||
<plurals name="forums_shared">
|
||||
<item quantity="one">%d forum delt av kontakter</item>
|
||||
<item quantity="other">%d forum delt av kontakter</item>
|
||||
</plurals>
|
||||
<string name="nobody">Ingen</string>
|
||||
<!--Blogs-->
|
||||
<string name="blogs_other_blog_empty_state">Denne bloggen er foreløpig tom.\n\nEnten har forfatteren ikke skrevet noe enda, eller så må personen som delte denne bloggen med deg logge på, slik at postene kan synkroniseres.</string>
|
||||
<string name="read_more">les mer</string>
|
||||
<string name="blogs_write_blog_post">Skriv bloggpost</string>
|
||||
<string name="blogs_write_blog_post_body_hint">Skriv din bloggpost her</string>
|
||||
<string name="blogs_publish_blog_post">Offentliggjør</string>
|
||||
<string name="blogs_blog_post_created">Bloggpost opprettet</string>
|
||||
<string name="blogs_blog_post_received">Ny bloggpost opprettet</string>
|
||||
<string name="blogs_blog_post_scroll_to">Rull til</string>
|
||||
<string name="blogs_feed_empty_state">Dette er en verdensomspennende bloggstrøm.\n\nDet ser ikke ut til at noen har blogget noe enda.\n\nVær den første til å trykke på pennesymbolet for å føre en blogg i pennen.</string>
|
||||
<string name="blogs_remove_blog">Fjern blogg</string>
|
||||
<string name="blogs_remove_blog_dialog_message">Er du sikker på at du ønsker å fjerne denne bloggen og alle bloggposter?\nMerk at dette ikke vil fjerne bloggen fra andre folks enheter.</string>
|
||||
<string name="blogs_remove_blog_ok">Fjern blogg</string>
|
||||
<string name="blogs_blog_removed">Blogg fjernet</string>
|
||||
<string name="blogs_reblog_comment_hint">Legg til en kommentar (valgfritt)</string>
|
||||
<string name="blogs_reblog_button">Blogg dette</string>
|
||||
<!--Blog Sharing-->
|
||||
<string name="blogs_sharing_share">Del blogg</string>
|
||||
<string name="blogs_sharing_error">Feil under deling av denne bloggen.</string>
|
||||
<string name="blogs_sharing_button">Del blogg</string>
|
||||
<string name="blogs_sharing_snackbar">Blogg delt med valgte kontakter</string>
|
||||
<string name="blogs_sharing_response_accepted_sent">Du godtok blogg-invitasjonen fra %s.</string>
|
||||
<string name="blogs_sharing_response_declined_sent">Du avslo blogg-invitasjonen fra %s.</string>
|
||||
<string name="blogs_sharing_response_accepted_received">%s godtok blogg-invitasjonen.</string>
|
||||
<string name="blogs_sharing_response_declined_received">%s avslo blogg-invitasjonen.</string>
|
||||
<string name="blogs_sharing_invitation_received">%1$s har delt bloggen \"%2$s\" med deg.</string>
|
||||
<string name="blogs_sharing_invitation_sent">Du har delt bloggen \"%1$s\" med %2$s.</string>
|
||||
<string name="blogs_sharing_invitations_title">Blogginvitasjoner</string>
|
||||
<string name="blogs_sharing_joined_toast">Abonnerte på blogg</string>
|
||||
<string name="blogs_sharing_declined_toast">Blogginvitasjon avslått</string>
|
||||
<string name="sharing_status_blog">Enhver som abonnerer på bloggen kan dele den med sine kontakter. Du deler denne bloggen med følgende kontakter. Det kan også være andre abonnementer du ikke kan se.</string>
|
||||
<!--RSS Feeds-->
|
||||
<string name="blogs_rss_feeds_import">Importer RSS-strøm</string>
|
||||
<string name="blogs_rss_feeds_import_button">Importer</string>
|
||||
<string name="blogs_rss_feeds_import_hint">Skriv inn nettadresse for RSS-strøm</string>
|
||||
<string name="blogs_rss_feeds_import_error">Vi beklager! Feil under importering av strøm.</string>
|
||||
<string name="blogs_rss_feeds_manage">Behandle RSS-strømmer</string>
|
||||
<string name="blogs_rss_feeds_manage_imported">Importert:</string>
|
||||
<string name="blogs_rss_feeds_manage_author">Forfatter:</string>
|
||||
<string name="blogs_rss_feeds_manage_updated">Sist oppdatert:</string>
|
||||
<string name="blogs_rss_remove_feed">Fjern strøm</string>
|
||||
<string name="blogs_rss_remove_feed_dialog_message">Er du sikker på at du vil fjerne denne strømmen og alle dens poster?\nAlle poster du har delt vil ikke fjernes fra andre folks enheter.</string>
|
||||
<string name="blogs_rss_remove_feed_ok">Fjern strøm</string>
|
||||
<string name="blogs_rss_feeds_manage_delete_error">Strømmen kunne ikke fjernes!</string>
|
||||
<string name="blogs_rss_feeds_manage_empty_state">Du har ikke importert noen RSS-strømmer enda.\n\nKlikk på \"+\"-tegnet øverst til høyre på skjermen for å legge til din først.</string>
|
||||
<string name="blogs_rss_feeds_manage_error">Feil ved lasting av dine strømmer. Prøv igjen senere.</string>
|
||||
<!--Settings Network-->
|
||||
<string name="network_settings_title">Nettverk</string>
|
||||
<string name="bluetooth_setting">Koble til via Blåtann</string>
|
||||
<string name="bluetooth_setting_enabled">Når kontakter er i nærheten</string>
|
||||
<string name="bluetooth_setting_disabled">Bare når kontakter legges til</string>
|
||||
<string name="tor_network_setting">Koble til via Tor</string>
|
||||
<string name="tor_network_setting_never">Aldri</string>
|
||||
<string name="tor_network_setting_wifi">Bare på Wi-Fi</string>
|
||||
<string name="tor_network_setting_always">Når Wi-Fi eller mobildata brukes</string>
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">Sikkerhet</string>
|
||||
<string name="change_password">Endre passord</string>
|
||||
<string name="current_password">Skriv inn nåværende passord:</string>
|
||||
<string name="choose_new_password">Velg ditt nye passord:</string>
|
||||
<string name="confirm_new_password">Bekreft ditt nye passord:</string>
|
||||
<string name="password_changed">Passordet har blitt endret.</string>
|
||||
<string name="panic_setting">Oppsett av panikk-knapp</string>
|
||||
<string name="panic_setting_title">Panikk-knapp</string>
|
||||
<string name="panic_setting_hint">Sett opp hvordan Briar vil reagere når du bruker panikk-knapp-programmet</string>
|
||||
<string name="panic_app_setting_title">Panikk-knapp-program</string>
|
||||
<string name="unknown_app">et ukjent program</string>
|
||||
<string name="panic_app_setting_summary">Inget program har blitt satt</string>
|
||||
<string name="panic_app_setting_none">Inget</string>
|
||||
<string name="dialog_title_connect_panic_app">Bekreft panikk-knapp-program</string>
|
||||
<string name="dialog_message_connect_panic_app">Er du sikker på at du vil tillate %1$s å utløse destruktive panikk-knapp-handlinger?</string>
|
||||
<string name="lock_setting_title">Logg ut</string>
|
||||
<string name="lock_setting_summary">Logg ut av Briar hvis panikk-knappen trykkes</string>
|
||||
<string name="purge_setting_title">Slett konto</string>
|
||||
<string name="purge_setting_summary">Slett Briar-kontoen din hvis panikk-knappen trykkes. Advarsel: Dette vil slette dine identiteter, kontakter og meldinger for godt.</string>
|
||||
<string name="uninstall_setting_title">Avinstaller Briar</string>
|
||||
<string name="uninstall_setting_summary">Dette krever manuell bekreftelse i panikkfall</string>
|
||||
<!--Settings Notifications-->
|
||||
<string name="notification_settings_title">Merknader</string>
|
||||
<string name="notify_private_messages_setting_title">Private meldinger</string>
|
||||
<string name="notify_private_messages_setting_summary">Vis varsler for private meldinger</string>
|
||||
<string name="notify_group_messages_setting_title">Gruppemeldinger</string>
|
||||
<string name="notify_group_messages_setting_summary">Vis varsler for gruppemeldinger</string>
|
||||
<string name="notify_forum_posts_setting_title">Forumposter</string>
|
||||
<string name="notify_forum_posts_setting_summary">Vis varsler for forumposter</string>
|
||||
<string name="notify_blog_posts_setting_title">Bloggposter</string>
|
||||
<string name="notify_blog_posts_setting_summary">Vis varsler for bloggposter</string>
|
||||
<string name="notify_vibration_setting">Vibrer</string>
|
||||
<string name="notify_lock_screen_setting_title">Låseskjerm</string>
|
||||
<string name="notify_lock_screen_setting_summary">Vis merknader på låseskjermen</string>
|
||||
<string name="notify_sound_setting">Lyd</string>
|
||||
<string name="notify_sound_setting_default">Forvalgt ringetone</string>
|
||||
<string name="notify_sound_setting_disabled">Ingen</string>
|
||||
<string name="choose_ringtone_title">Velg ringetone</string>
|
||||
<!--Settings Feedback-->
|
||||
<string name="feedback_settings_title">Tilbakemelding</string>
|
||||
<string name="send_feedback">Send tilbakemelding</string>
|
||||
<!--Link Warning-->
|
||||
<string name="link_warning_title">Lenkeadvarsel</string>
|
||||
<string name="link_warning_intro">Du er i ferd med å følge en lenke med et eksternt program.</string>
|
||||
<string name="link_warning_text">Dette kan brukes for å identifisere deg. Tenk deg om hvorvidt du stoler på personen som sendte denne lenken til deg, og overvei å åpne den i Orfox.</string>
|
||||
<string name="link_warning_open_link">Åpne lenke</string>
|
||||
<!--Crash Reporter-->
|
||||
<string name="crash_report_title">Kræsjrapport for Briar</string>
|
||||
<string name="briar_crashed">Beklager, Briar har kræsjet.</string>
|
||||
<string name="not_your_fault">Dette er ikke din feil.</string>
|
||||
<string name="please_send_report">Hjelp til med å forbedre Briar ved å sende en kræsjrapport.</string>
|
||||
<string name="report_is_encrypted">Kræsjrapporten vil sendes kryptert og sikkert.</string>
|
||||
<string name="feedback_title">Tilbakemelding</string>
|
||||
<string name="describe_crash">Beskriv hva som skjedde (valgfritt)</string>
|
||||
<string name="enter_feedback">Skriv inn din tilbakemelding</string>
|
||||
<string name="optional_contact_email">Din e-postadresse (valgfri)</string>
|
||||
<string name="include_debug_report_crash">Inkluder anonym data om kræsjet</string>
|
||||
<string name="include_debug_report_feedback">Inkluder anonym data om denne enheten</string>
|
||||
<string name="could_not_load_report_data">Kunne ikke laste inn kræsjrapportdata.</string>
|
||||
<string name="send_report">Send kræsjrapport</string>
|
||||
<string name="close">Lukk</string>
|
||||
<string name="dev_report_saved">Kræsjrapport lagret. Den vil bli sendt neste gang du logger inn i Briar.</string>
|
||||
<!--Sign Out-->
|
||||
<string name="progress_title_logout">Logger ut av Briar…</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">Skjermoverlag oppdaget</string>
|
||||
<string name="screen_filter_body">Et annet program tegner på toppen av Briar. For å beskytte din sikkerhet, vil Briar ikke reagere på trykk når et annet program tegner over det.\n\nPrøv å skru av følgende programmer når du bruker Briar.\n\n%1$s</string>
|
||||
</resources>
|
||||
369
briar-android/src/main/res/values-oc/strings.xml
Normal file
369
briar-android/src/main/res/values-oc/strings.xml
Normal file
@@ -0,0 +1,369 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<!--Setup-->
|
||||
<string name="setup_title">Configuracion de Briar</string>
|
||||
<string name="setup_explanation">Vòstre compte Briar es salvat e chifrat sus vòstre aparelh, non pas sus en un servidor alunhat \"cloud\". Se desinstalletz Briar o oblidetz vòstre senhal, vòstre compte e vòstras donadas seràn irrecuperables.</string>
|
||||
<string name="choose_nickname">Causir un escais-nom</string>
|
||||
<string name="choose_password">Causir un senhal</string>
|
||||
<string name="confirm_password">Confirmar lo senhal</string>
|
||||
<string name="name_too_long">L’escais es tròp long</string>
|
||||
<string name="password_too_weak">Lo senhal es tròp feble</string>
|
||||
<string name="passwords_do_not_match">Los senhals correspondon pas</string>
|
||||
<string name="create_account_button">Crear un compte</string>
|
||||
<!--Login-->
|
||||
<string name="enter_password">Picatz vòstre senhal :</string>
|
||||
<string name="try_again">Senhal incorècte, tornatz ensajar</string>
|
||||
<string name="sign_in_button">Se connectar</string>
|
||||
<string name="forgotten_password">Senhal oblidat</string>
|
||||
<string name="dialog_title_lost_password">Senhal oblidat</string>
|
||||
<string name="dialog_message_lost_password">Vòstre compte Briar es salvat e chifrat sus vòstre aparelh, non pas sus en un servidor alunhat \"cloud\". De fècte podèm pas reïnicializar vòstre senhal.
|
||||
Volètz suprimir vòstre compte e ne crear un nòu ?\n
|
||||
\nMèfi : vòstra identitat, vòstres contactes e messatges seràn perduts per totjorn.</string>
|
||||
<string name="startup_failed_notification_title">Briar a pas pogut aviar</string>
|
||||
<string name="startup_failed_notification_text">Benlèu que vos cal tornar installar Briar.</string>
|
||||
<string name="startup_failed_activity_title">Fracàs de l’aviada de Briar.</string>
|
||||
<string name="startup_failed_db_error">Per una rason desconeguda vòstra basa de donadas Briar es pas utilizabla e irrecuperabla. Vòstres comptes, donadas e contactes son perduts. Per malastre vos cal tornar installar Briar e configurar un compte nòu.</string>
|
||||
<string name="startup_failed_service_error">Briar a pas pogut aviar un modul necessari. Tornar installar Briar pòt resolver aquò. Que aquò siá dich : perdretz vòstre compte e totas las donadas ligadas a aqueste. Briar utiliza pas de servidor centralizat per salvar sas donadas.</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="one">Aquò es una version Beta de Briar. Vòstre compte s’acabarà dins %d jorn e poirà pas èsser renovat.</item>
|
||||
<item quantity="other">Aquò es una version Beta de Briar. Vòstre compte s’acabarà dins %d jorns e poirà pas èsser renovat.</item>
|
||||
</plurals>
|
||||
<string name="expiry_date_reached">Vòstre logicial s’acabèt.\nMercés d’aver ensajat !</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">Dobrir lo panèl de navigacion</string>
|
||||
<string name="nav_drawer_close_description">Tampar lo panèl de navigacion</string>
|
||||
<string name="contact_list_button">Contactes</string>
|
||||
<string name="groups_button">Grops privats</string>
|
||||
<string name="forums_button">Fòrums</string>
|
||||
<string name="blogs_button">Blòges</string>
|
||||
<string name="settings_button">Paramètres</string>
|
||||
<string name="sign_out_button">Se desconnectar</string>
|
||||
<!--Transports-->
|
||||
<string name="transport_tor">Internet</string>
|
||||
<string name="transport_bt">Bluetooth</string>
|
||||
<string name="transport_lan">Wifi</string>
|
||||
<!--Notifications-->
|
||||
<string name="ongoing_notification_title">Connectat a Briar</string>
|
||||
<string name="ongoing_notification_text">Tocatz per dobrir</string>
|
||||
<plurals name="private_message_notification_text">
|
||||
<item quantity="one">Novèl messatge privat.</item>
|
||||
<item quantity="other">%d novèls messatges privats</item>
|
||||
</plurals>
|
||||
<plurals name="group_message_notification_text">
|
||||
<item quantity="one">Novèl messatge gropat</item>
|
||||
<item quantity="other">%d novèls messatges gropats</item>
|
||||
</plurals>
|
||||
<plurals name="forum_post_notification_text">
|
||||
<item quantity="one">Novèla publicacion al fòrum</item>
|
||||
<item quantity="other">%d novèlas publicacions al fòrum</item>
|
||||
</plurals>
|
||||
<plurals name="blog_post_notification_text">
|
||||
<item quantity="one">Novèl article sul blòg</item>
|
||||
<item quantity="other">%d novèls article sul blòg</item>
|
||||
</plurals>
|
||||
<!--Misc-->
|
||||
<string name="now">Ara</string>
|
||||
<string name="show">Mostrar</string>
|
||||
<string name="hide">Amagar</string>
|
||||
<string name="ok">D’acòrdi</string>
|
||||
<string name="cancel">Anullar</string>
|
||||
<string name="got_it">Comprés</string>
|
||||
<string name="delete">Suprimir</string>
|
||||
<string name="accept">Acceptar</string>
|
||||
<string name="decline">Refusar</string>
|
||||
<string name="options">Opcions</string>
|
||||
<string name="online">En linha</string>
|
||||
<string name="offline">Fòra linha</string>
|
||||
<string name="send">Mandar</string>
|
||||
<string name="allow">Autorizar</string>
|
||||
<string name="open">Dobrir</string>
|
||||
<string name="no_data">Pas donada</string>
|
||||
<string name="ellipsis">...</string>
|
||||
<string name="text_too_long">Lo tèxte picat es tròp long</string>
|
||||
<string name="show_onboarding">Mostrar l’ajuda</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">Sembla que sètz novèl aquí e qu’avètz pas cap de contacte.\n\nQuichatz l’icòna ennaut e seguètz las instruccions per n’apondre. Informacion : podètz pas qu’apondre lo monde se se tròban en fàcia de vos, aquò per evitar qu’òm vos raube l’identitat e que vòstres messatges sián legits per d’autres.</string>
|
||||
<string name="date_no_private_messages">Cap messatge</string>
|
||||
<string name="no_private_messages">Aquò es la vista de las conversacions.\n\nSembla que n’i a pas fòrça.\n\nQuichatz lo camp tèxte per ne començar una.</string>
|
||||
<string name="message_hint">Picatz lo messatge</string>
|
||||
<string name="delete_contact">Suprimir lo contacte</string>
|
||||
<string name="dialog_title_delete_contact">Confirmatz la supression del contacte</string>
|
||||
<string name="dialog_message_delete_contact">Sètz segur de voler suprimir lo contacte e los messatges ligats a el ?</string>
|
||||
<string name="contact_deleted_toast">Contacte suprimit</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Ajustar un contacte</string>
|
||||
<string name="your_nickname">Triatz l’identitat que volètz utilizar</string>
|
||||
<string name="face_to_face">Vos cal vos amassar amb la persona que volètz apondre als contactes.\n\n Aquò es fach per evitar qu’òm vos raube l’identitat e que vòstres messatges sián legits per d’autres.</string>
|
||||
<string name="continue_button">Contunhar</string>
|
||||
<string name="your_invitation_code">Vòstre còdi de convidacion es</string>
|
||||
<string name="enter_invitation_code">Mercés de marcar lo còdi de convidacion de vòstre contacte :</string>
|
||||
<string name="searching_format">Recercar de contactes amb lo còdi de convidacion %06d\u2026</string>
|
||||
<string name="connection_failed">Fracàs de la connexion</string>
|
||||
<string name="could_not_find_contact">Briar a pas trobar degun a proximitat</string>
|
||||
<string name="try_again_button">Tornar ensajar</string>
|
||||
<string name="connected_to_contact">Connectat al contacte</string>
|
||||
<string name="calculating_confirmation_code">Calcul del còdi de confirmacion\u2026</string>
|
||||
<string name="your_confirmation_code">Vòstre còdi de confirmacion es </string>
|
||||
<string name="enter_confirmation_code">Mercés de marcar lo còdi de confirmacion de vòstre contacte :</string>
|
||||
<string name="waiting_for_contact">En espèra del contacte\u2026</string>
|
||||
<string name="waiting_for_contact_to_scan">En espèra de la numerizacion e de la connexion al contacte\u2026</string>
|
||||
<string name="exchanging_contact_details">Escambi dels detalhs del contacte\u2026</string>
|
||||
<string name="codes_do_not_match">Los còdis correspondon pas</string>
|
||||
<string name="interfering">Poriá arribar que qualqu’un ensajar d’interferir dins la connexion</string>
|
||||
<string name="contact_added_toast">Contacte apondut : %s</string>
|
||||
<string name="contact_already_exists">Lo contacte %s existís ja</string>
|
||||
<string name="contact_exchange_failed">L’escambi de contacte a fracassat</string>
|
||||
<string name="qr_code_invalid">Lo QR còdi es invalid</string>
|
||||
<string name="connecting_to_device">Connexion a l’aparelh\u2026</string>
|
||||
<string name="authenticating_with_device">Autentificacion amb l’aparelh\u2026</string>
|
||||
<string name="connection_aborted_local">Avèm copat la connexion ! Poiriá arribar que qualqu’un ensage d’interferir amb aquela</string>
|
||||
<string name="connection_aborted_remote">Vòstre contacte a copat la connexion ! Poiriá arribar que qualqu’un ensage d’interferir amb aquela</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">Presentatz vòstres contactes</string>
|
||||
<string name="introduction_onboarding_text">Podètz presentar vòstres contactes, lor caldrà pas se veire per s’apondre e se connectar a Briar.</string>
|
||||
<string name="introduction_activity_title">Seleccionar contacte</string>
|
||||
<string name="introduction_message_title">Presentar de contactes</string>
|
||||
<string name="introduction_message_hint">Apondre un messatge (opcional)</string>
|
||||
<string name="introduction_button">Escafar l’introduccion</string>
|
||||
<string name="introduction_sent">Vòstra admission es enviada.</string>
|
||||
<string name="introduction_error">Una error s’es produsida pendent l’admission</string>
|
||||
<string name="introduction_response_error">Error pendent la responsa d’admission.</string>
|
||||
<string name="introduction_request_sent">Avètz demandat a %2$s d’apondre %1$s</string>
|
||||
<string name="introduction_request_received">%1$s vos prepausa d’apondre %2$s. Volètz apondre %2$s a vòstres contactes ?</string>
|
||||
<string name="introduction_request_exists_received">%1$s vos demandèt d’apondre %2$s mas %2$s es ja dins vòstra tièra de contactes. Del moment que %1$s o sap pas, podètz çaquelà respondre :</string>
|
||||
<string name="introduction_request_answered_received">%1$s a demandat d’apondre %2$s.</string>
|
||||
<string name="introduction_response_accepted_sent">Avètz acceptat d’apondre %1$s.</string>
|
||||
<string name="introduction_response_declined_sent">Avètz refusat d’apondre %1$s.</string>
|
||||
<string name="introduction_response_accepted_received">%1$s a acceptat d’apondre %2$s.</string>
|
||||
<string name="introduction_response_declined_received">%1$s a refusat d’apondre %2$s.</string>
|
||||
<string name="introduction_response_declined_received_by_introducee">%1$s anóncia que %2$s a refusat la convidacion.</string>
|
||||
<plurals name="introduction_notification_text">
|
||||
<item quantity="one">Nòu contacte ajustat.</item>
|
||||
<item quantity="other">%d nòus contactes ajustats.</item>
|
||||
</plurals>
|
||||
<!--Private Groups-->
|
||||
<string name="groups_list_empty">Sètz pas sòci de cap grop.\n\nTocatz l’icòna + ennaut per ne crear un o demandatz a vòstres contactes de vos convidar a un de lors grops.</string>
|
||||
<string name="groups_created_by">Fondat per %s</string>
|
||||
<plurals name="messages">
|
||||
<item quantity="one">1 messatge</item>
|
||||
<item quantity="other">%d messatges</item>
|
||||
</plurals>
|
||||
<string name="groups_group_is_empty">Aqueste grop es void</string>
|
||||
<string name="groups_group_is_dissolved">Aqueste grop es estat suprimit</string>
|
||||
<string name="groups_remove">Suprimir</string>
|
||||
<string name="groups_create_group_title">Crear un grop privat</string>
|
||||
<string name="groups_create_group_button">Crear un grop</string>
|
||||
<string name="groups_create_group_invitation_button">Enviar de convidacions</string>
|
||||
<string name="groups_create_group_hint">Donatz un nom al grop privat</string>
|
||||
<string name="groups_invitation_sent">Convidacion enviada al grop</string>
|
||||
<string name="groups_message_sent">Messatge mandat</string>
|
||||
<string name="groups_member_list">Tièra de membres</string>
|
||||
<string name="groups_invite_members">Convidar mai de monde</string>
|
||||
<string name="groups_member_created_you">Avètz creat lo grop</string>
|
||||
<string name="groups_member_created">%s a fondat lo grop</string>
|
||||
<string name="groups_member_joined_you">Sètz marcat dins lo grop</string>
|
||||
<string name="groups_member_joined">%s es marcat dins lo grop</string>
|
||||
<string name="groups_leave">Quitar lo grop</string>
|
||||
<string name="groups_leave_dialog_title">Confirmar que volètz quitar lo grop</string>
|
||||
<string name="groups_leave_dialog_message">Sètz segur de voler quitar lo grop ?</string>
|
||||
<string name="groups_dissolve">Suprimir lo grop</string>
|
||||
<string name="groups_dissolve_dialog_title">Confirmar la supression del grop</string>
|
||||
<string name="groups_dissolve_dialog_message">Sètz segur de voler suprimir lo grop ? \n\nLa rèsta dels participants poiran pas tenir de charrat e poiriá arribar qu’ajan pas los darrièrs messatges.</string>
|
||||
<string name="groups_dissolve_button">Suprimir</string>
|
||||
<string name="groups_dissolved_dialog_title">Lo grop es estat suprimit</string>
|
||||
<string name="groups_dissolved_dialog_message">Lo fondador d’aqueste grop l’a suprimit.\n\nPodètz pas mai escriure de messatges e benlèu qu’avètz pas recebuts totes los messatges.</string>
|
||||
<!--Private Group Invitations-->
|
||||
<string name="groups_invitations_title">Convidacion de grop</string>
|
||||
<string name="groups_invitations_invitation_sent">Avètz convidat %1$s a venir al grop « %2$vs»</string>
|
||||
<string name="groups_invitations_invitation_received">%1$s vos a convidat a anar al grop « %2$s »</string>
|
||||
<string name="groups_invitations_joined">Ajustat al grop</string>
|
||||
<string name="groups_invitations_declined">Convidacion al grop refusada</string>
|
||||
<plurals name="groups_invitations_open">
|
||||
<item quantity="one">%d convidacion de grop en espèra</item>
|
||||
<item quantity="other">%d convidacions de grop en espèra</item>
|
||||
</plurals>
|
||||
<string name="groups_invitations_response_accepted_sent">Avètz acceptat la convidacion del grop a %s.</string>
|
||||
<string name="groups_invitations_response_declined_sent">Avètz refusat la convidacion del grop a %s.</string>
|
||||
<string name="groups_invitations_response_accepted_received">%s a acceptat la convidacion del grop.</string>
|
||||
<string name="groups_invitations_response_declined_received">%s a refusat la convidacion del grop.</string>
|
||||
<string name="sharing_status_groups">Pas que lo fondador del grop pòt convidar mai de monde. Vaquí la tièra dels membres actuals.</string>
|
||||
<!--Private Groups Revealing Contacts-->
|
||||
<string name="groups_reveal_contacts">Revelar los contactes</string>
|
||||
<string name="groups_reveal_dialog_message">Podètz causir de revelar los contactes a totes los membres actuals e venents d’aqueste grop.\n\nEn revelar los contactes auretz una melhora connexion al grop e mai fiabla perque poiretz comunicar amb los contactes revelats quand lo fondator es fòra linha.</string>
|
||||
<string name="groups_reveal_visible">Vòstre ligam amb lo contacte es visible pel grop</string>
|
||||
<string name="groups_reveal_visible_revealed_by_us">Vòstre ligam – revelat per vos – amb lo contacte es visible pel grop </string>
|
||||
<string name="groups_reveal_visible_revealed_by_contact">Vòstre ligam – revelat per %s – amb lo contacte es invisible pel grop</string>
|
||||
<string name="groups_reveal_invisible">Vòstre ligam amb lo contacte es invisible pel grop</string>
|
||||
<!--Forums-->
|
||||
<string name="no_forums">Avètz pas de fòrum\n\n Perque pas ne crear un en tocar l’icòna + ennaut ?\n\nPodètz tanben demandar a vòstres contactes de partejar de fòrums amb vos.</string>
|
||||
<string name="create_forum_title">Crear un fòrum</string>
|
||||
<string name="choose_forum_hint">Donar un nom al fòrum</string>
|
||||
<string name="create_forum_button">Crear un fòrum</string>
|
||||
<string name="forum_created_toast">Fòrum creat</string>
|
||||
<string name="no_forum_posts">Aqueste fòrum es void.\n\nUtilizatz l’icòna del gredon ennaut per escriure la primièra publicacion.\n\n Vos sentez soleton aquí ? Partejatz aqueste fòrum amb d’autres contactes !</string>
|
||||
<string name="no_posts">Cap publicacion</string>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">%d publicacion</item>
|
||||
<item quantity="other">%d publicacions</item>
|
||||
</plurals>
|
||||
<string name="forum_new_entry_posted">Publicacions enviadas</string>
|
||||
<string name="forum_new_message_hint">Nòva publicacion</string>
|
||||
<string name="forum_message_reply_hint">Nòva responsa</string>
|
||||
<string name="btn_reply">Respondre</string>
|
||||
<string name="forum_leave">Quitar lo fòrum</string>
|
||||
<string name="dialog_title_leave_forum">Confirmar la sortida del fòrum</string>
|
||||
<string name="dialog_message_leave_forum">Sètz segur de voler quitar lo fòrum ? Los contactes qu’avètz convidats aquí auràn pas mai de mesa a jorn.</string>
|
||||
<string name="dialog_button_leave">Quitar</string>
|
||||
<string name="forum_left_toast">Fòrum daissat</string>
|
||||
<!--Forum Sharing-->
|
||||
<string name="forum_share_button">Partejar lo fòrum</string>
|
||||
<string name="contacts_selected">Contactes seleccionats</string>
|
||||
<string name="activity_share_toolbar_header">Causissètz de contactes</string>
|
||||
<string name="no_contacts_selector">Semblatz novèl aquí, avètz pas encara de contactes.\n\nTornatz aquí quand auretz apondut vòstre primièr contacte.</string>
|
||||
<string name="forum_shared_snackbar">Fòrom partejat amb los contactes causits</string>
|
||||
<string name="forum_share_message">Apondre un messatge (opcional)</string>
|
||||
<string name="forum_share_error">Error en partejar lo fòrum</string>
|
||||
<string name="forum_invitation_received">%1$s a partejat lo fòrom « %2$s » amb vos.</string>
|
||||
<string name="forum_invitation_sent">Avètz partejat lo fòrom « %1$s » amb %2$s.</string>
|
||||
<string name="forum_invitations_title">Convidacion al fòrum</string>
|
||||
<string name="forum_invitation_exists">Avètz acceptat una convidacion a aqueste fòrum. En acceptar mai convidacion, fasètz mai fòrta la comunicacion e visibilitat del fòrum.</string>
|
||||
<string name="forum_joined_toast">Fòrums que participi</string>
|
||||
<string name="forum_declined_toast">Convidacions refusadas</string>
|
||||
<string name="shared_by_format">Partejat per %s</string>
|
||||
<string name="forum_invitation_already_sharing">Ja a partejar</string>
|
||||
<string name="forum_invitation_response_accepted_sent">Avètz acceptat la convidacion al fòrum de %s.</string>
|
||||
<string name="forum_invitation_response_declined_sent">Avètz refusat la convidacion al fòrum de %s.</string>
|
||||
<string name="forum_invitation_response_accepted_received">%s a acceptat la convidacion al fòrum.</string>
|
||||
<string name="forum_invitation_response_declined_received">%s a refusat la convidacion al fòrum.</string>
|
||||
<string name="sharing_status">Estat del partatge</string>
|
||||
<string name="sharing_status_forum">Totes los participants d’un fòrum pòdon lo partejar amb lors contactes. Partejatz aqueste fòrum amb los contactes seguents. I a benlèu mai de monde que podètz pas veire.</string>
|
||||
<string name="shared_with">Partejat amb %1$d (%2$d en linha)</string>
|
||||
<plurals name="forums_shared">
|
||||
<item quantity="one">%d fòrum partejat amb de contactes</item>
|
||||
<item quantity="other">%d fòrums partejats amb de contactes</item>
|
||||
</plurals>
|
||||
<string name="nobody">Degun</string>
|
||||
<!--Blogs-->
|
||||
<string name="blogs_other_blog_empty_state">Aqueste blòg es void pel moment. Doas possibilitats : l’autor a pas encara escrich quicòm o alara la persona que vos a marcat al fòrum es pas en linha e la sincronizacion es pas encara realizada.</string>
|
||||
<string name="read_more">Legir la seguida</string>
|
||||
<string name="blogs_write_blog_post">Escriure un article de blòg</string>
|
||||
<string name="blogs_write_blog_post_body_hint">Marcatz aquí vòstre messatge pel blòg</string>
|
||||
<string name="blogs_publish_blog_post">Publicar</string>
|
||||
<string name="blogs_blog_post_created">Article salvat</string>
|
||||
<string name="blogs_blog_post_received">Nòu article recebut</string>
|
||||
<string name="blogs_blog_post_scroll_to">Anar a</string>
|
||||
<string name="blogs_feed_empty_state">Aquò es lo flux global dels blògs.\n\n Sembla que degun aja escrich aquí.\n\n Siatz lo primièr, tocatz l’icòna de l’estilò per escriure un article al blòg.</string>
|
||||
<string name="blogs_remove_blog">Suprimir lo blòg</string>
|
||||
<string name="blogs_remove_blog_dialog_message">Sètz segur de voler suprimir aqueste blòg e totes sos messatges ?\n Aquò suprimirà pas lo blòg suls aparelhs dels autres.</string>
|
||||
<string name="blogs_remove_blog_ok">Suprimir lo blòg</string>
|
||||
<string name="blogs_blog_removed">Blòg mandat al diable</string>
|
||||
<string name="blogs_reblog_comment_hint">Apondre un comentari (opcional)</string>
|
||||
<string name="blogs_reblog_button">Partejar</string>
|
||||
<!--Blog Sharing-->
|
||||
<string name="blogs_sharing_share">Partejar lo blòg</string>
|
||||
<string name="blogs_sharing_error">Error en partejar lo blòg</string>
|
||||
<string name="blogs_sharing_button">Fai virar</string>
|
||||
<string name="blogs_sharing_snackbar">Blòg partejat amb los contactes seleccionats</string>
|
||||
<string name="blogs_sharing_response_accepted_sent">Avètz acceptat la convidacion al blòg a %s.</string>
|
||||
<string name="blogs_sharing_response_declined_sent">Avètz refusat la convidacion al blòg a %s.</string>
|
||||
<string name="blogs_sharing_response_accepted_received">%s a acceptat la convidacion al blòg.</string>
|
||||
<string name="blogs_sharing_response_declined_received">%s a refusat la convidacion al blòg.</string>
|
||||
<string name="blogs_sharing_invitation_received">%1$s a partejat lo blòg « %2$s » amb vos.</string>
|
||||
<string name="blogs_sharing_invitation_sent">Avètz partejat lo blòg « %1$s » amb %2$s.</string>
|
||||
<string name="blogs_sharing_invitations_title">Convidacion al blòg</string>
|
||||
<string name="blogs_sharing_joined_toast">Seguidors del blòg</string>
|
||||
<string name="blogs_sharing_declined_toast">Convidacions al blòg refusadas</string>
|
||||
<string name="sharing_status_blog">Qual que siá seguidor d’un blòg pòt lo partejar amb sos contactes. Partejatz lo blòg amb los contactes seguents. I a benlèu mai de monde qu’o seguen que vesètz pas.</string>
|
||||
<!--RSS Feeds-->
|
||||
<string name="blogs_rss_feeds_import">Importar de fluxes RSS</string>
|
||||
<string name="blogs_rss_feeds_import_button">Importar</string>
|
||||
<string name="blogs_rss_feeds_import_hint">Marcar l’URL del flux RSS</string>
|
||||
<string name="blogs_rss_feeds_import_error">Una error s’es produisida en importar lo flux</string>
|
||||
<string name="blogs_rss_feeds_manage">Gerir lo flux RSS</string>
|
||||
<string name="blogs_rss_feeds_manage_imported">Importat :</string>
|
||||
<string name="blogs_rss_feeds_manage_author">Autor :</string>
|
||||
<string name="blogs_rss_feeds_manage_updated">Darrièra mesa a jorn :</string>
|
||||
<string name="blogs_rss_remove_feed">Suprimir lo flux</string>
|
||||
<string name="blogs_rss_remove_feed_dialog_message">Sètz segur de voler suprimir aqueste flux e sos messatges ?\nLos messatges qu’avètz partejat seràn pas suprimits dels aparelhs dels autres.</string>
|
||||
<string name="blogs_rss_remove_feed_ok">Suprimir lo flux</string>
|
||||
<string name="blogs_rss_feeds_manage_delete_error">Impossible de suprimir lo flux !</string>
|
||||
<string name="blogs_rss_feeds_manage_empty_state">Avètz pas importat cap flux RSS.\n\nPerque pas n’apondre un en clicar sul pichon + a man dreita ennaut ?</string>
|
||||
<string name="blogs_rss_feeds_manage_error">Error en cargar vòstres fluxes. Ensajatz mai tard.</string>
|
||||
<!--Settings Network-->
|
||||
<string name="network_settings_title">Ret</string>
|
||||
<string name="bluetooth_setting">Se connectar per Bluetooth</string>
|
||||
<string name="bluetooth_setting_enabled">Quand de contactes son prèp</string>
|
||||
<string name="bluetooth_setting_disabled">Solament en apondre de contctes</string>
|
||||
<string name="tor_network_setting">Se connectar via Tor</string>
|
||||
<string name="tor_network_setting_never">Pas jamai</string>
|
||||
<string name="tor_network_setting_wifi">Solament per wifi</string>
|
||||
<string name="tor_network_setting_always">Per wifi o donadas mobil</string>
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">Seguretat</string>
|
||||
<string name="change_password">Modificar lo senhal</string>
|
||||
<string name="current_password">Marcatz lo senhal actual :</string>
|
||||
<string name="choose_new_password">Causissètz lo novèl senhal :</string>
|
||||
<string name="confirm_new_password">Confirmatz lo novèl senhal :</string>
|
||||
<string name="password_changed">Lo senhal a cambiat</string>
|
||||
<string name="panic_setting">Paramètres del boton urgéncia</string>
|
||||
<string name="panic_setting_title">Boton urgéncia</string>
|
||||
<string name="panic_setting_hint">Configurar Briat per l’utilizacion de l’aplicacion del boton d’urgéncia</string>
|
||||
<string name="panic_app_setting_title">Aplicacion boton urgéncia</string>
|
||||
<string name="unknown_app">Aplicacion desconeguda</string>
|
||||
<string name="panic_app_setting_summary">Cap aplicacion causida</string>
|
||||
<string name="panic_app_setting_none">Cap</string>
|
||||
<string name="dialog_title_connect_panic_app">Confirmar l’aplicacion Urgéncia</string>
|
||||
<string name="dialog_message_connect_panic_app">Sètz segur de voler autorizar %1$s a aviar las accions de destruccion del boton Urgéncia ?</string>
|
||||
<string name="lock_setting_title">Se desconnectar</string>
|
||||
<string name="lock_setting_summary">Se desconnectar de Briar en apiejant lo boton Urgéncia ?</string>
|
||||
<string name="purge_setting_title">Suprimir lo compte</string>
|
||||
<string name="purge_setting_summary">Suprimir vòstre compte en apiejant lo boton Urgéncia. Mèfi, aquò escafarà per totjorn vòstra identitat, vòstres contactes e messatges.</string>
|
||||
<string name="uninstall_setting_title">Desinstallar Briar</string>
|
||||
<string name="uninstall_setting_summary">Aquò demanda una confirmacion manuala.</string>
|
||||
<!--Settings Notifications-->
|
||||
<string name="notification_settings_title">Notificacions</string>
|
||||
<string name="notify_private_messages_setting_title">Messatges privats</string>
|
||||
<string name="notify_private_messages_setting_summary">Notificar los messatges privats</string>
|
||||
<string name="notify_group_messages_setting_title">Messatges gropats</string>
|
||||
<string name="notify_group_messages_setting_summary">Notificar los messatges gropats</string>
|
||||
<string name="notify_forum_posts_setting_title">Publicacions al fòrum</string>
|
||||
<string name="notify_forum_posts_setting_summary">Notificar las publicacions al fòrum</string>
|
||||
<string name="notify_blog_posts_setting_title">Articles de blòg</string>
|
||||
<string name="notify_blog_posts_setting_summary">Notificar los articles de blòg</string>
|
||||
<string name="notify_vibration_setting">Vibracion</string>
|
||||
<string name="notify_lock_screen_setting_title">Verrolhar l’ecran</string>
|
||||
<string name="notify_lock_screen_setting_summary">Mostrar las notificacions sus l’ecran verrolhat</string>
|
||||
<string name="notify_sound_setting">Sonariá</string>
|
||||
<string name="notify_sound_setting_default">Sonariá per defaut</string>
|
||||
<string name="notify_sound_setting_disabled">Cap</string>
|
||||
<string name="choose_ringtone_title">Causir la sonariá</string>
|
||||
<!--Settings Feedback-->
|
||||
<string name="feedback_settings_title">Comentaris</string>
|
||||
<string name="send_feedback">Enviar vòstres comentaris</string>
|
||||
<!--Link Warning-->
|
||||
<string name="link_warning_title">Avertiment de ligam</string>
|
||||
<string name="link_warning_intro">Sètz a man de dobrir lo ligam seguent amb una aplicacion extèrna.</string>
|
||||
<string name="link_warning_text">Aquò poiriá èsser utilizat per vos identificar. Estimatz la fisança qu’avètz de la persona que vos a enviat lo ligam e pensatz lo dobrir amb Orfox.</string>
|
||||
<string name="link_warning_open_link">Dobrir lo ligam</string>
|
||||
<!--Crash Reporter-->
|
||||
<string name="crash_report_title">Senhalar lo fracàs de Briar</string>
|
||||
<string name="briar_crashed">Malastre, Briar a arrestat de foncionar.</string>
|
||||
<string name="not_your_fault">Es pas vòstra fauta.</string>
|
||||
<string name="please_send_report">Ajudatz-nos a melhorar Briar en nos enviant un rapòrt de fracàs.</string>
|
||||
<string name="report_is_encrypted">Asseguram que lo rapòrt es chifrat e enviat en tota seguretat.</string>
|
||||
<string name="feedback_title">Comentaris</string>
|
||||
<string name="describe_crash">Describètz çò que s’es passat</string>
|
||||
<string name="enter_feedback">Marcatz vòstres comentaris</string>
|
||||
<string name="optional_contact_email">Vòstre corrièl (opcional)</string>
|
||||
<string name="include_debug_report_crash">Ajustar de donas anonimas tocant lo fracàs</string>
|
||||
<string name="include_debug_report_feedback">Ajustar de donas anonimas tocant aqueste periferic</string>
|
||||
<string name="could_not_load_report_data">Impossible de cargar las donadas del rapòrt.</string>
|
||||
<string name="send_report">Enviar lo rapòrt</string>
|
||||
<string name="close">Tampar</string>
|
||||
<string name="dev_report_saved">Rapòrt salvat. Serà enviat a vòstra connexion venenta a Briar.</string>
|
||||
<!--Sign Out-->
|
||||
<string name="progress_title_logout">Desconnecion de Briar...</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">Superposicion detectada</string>
|
||||
<string name="screen_filter_body">Una autra aplicacion s’aficha per dessús Briar. Per protegir vòstre seguretat Briar respond pas quand una autra aplicacion s’aficha par dessús.\n\nEnsajatz de tampar las aplicacions seguentas quand utilizatz Briar : \n\n%1$s</string>
|
||||
</resources>
|
||||
@@ -2,31 +2,37 @@
|
||||
<resources>
|
||||
<!--Setup-->
|
||||
<string name="setup_title">Настройка Briar</string>
|
||||
<string name="setup_explanation">Ваш аккаунт Briar хранится зашифрованным в вашем устройстве, а не в облачном сервисе. Если вы удалите Briar или удалите ваш пароль, нельзя будет восстановить ваш аккаунт или ваши данные.</string>
|
||||
<string name="choose_nickname">Выберите ваше имя пользователя</string>
|
||||
<string name="choose_password">Выберите ваш пароль</string>
|
||||
<string name="confirm_password">Подтвердите ваш пароль</string>
|
||||
<string name="name_too_long">Имя слишком длинное</string>
|
||||
<string name="setup_explanation">Ваша учетная запись Briar хранится в зашифрованном виде только на устройстве. Если вы удалите Briar или забудете пароль, то не сможете восстановить свою учетную запись и данные.</string>
|
||||
<string name="choose_nickname">Выберите псевдоним</string>
|
||||
<string name="choose_password">Выберите пароль</string>
|
||||
<string name="confirm_password">Подтвердите пароль</string>
|
||||
<string name="name_too_long">Слишком длинное имя</string>
|
||||
<string name="password_too_weak">Пароль слишком слабый</string>
|
||||
<string name="passwords_do_not_match">Пароли не совпадают</string>
|
||||
<string name="create_account_button">Создать аккаунт</string>
|
||||
<string name="create_account_button">Создать учетную запись</string>
|
||||
<!--Login-->
|
||||
<string name="enter_password">Введите ваш пароль:</string>
|
||||
<string name="enter_password">Введите пароль:</string>
|
||||
<string name="try_again">Неверный пароль, повторите попытку</string>
|
||||
<string name="sign_in_button">Войти</string>
|
||||
<string name="forgotten_password">Я забыл свой пароль</string>
|
||||
<string name="dialog_title_lost_password">Потерялся пароль</string>
|
||||
<string name="dialog_message_lost_password">Ваш аккаунт Briar хранится зашифрованным в вашем устройстве, а не в облачном сервисе, поэтому мы не можем восстановить ваш пароль. Хотите ли вы удалить ваш аккаунт и начать заново? Осторожно: Ваши личности, контакты и сообщения будут навсегда потеряны.</string>
|
||||
<string name="startup_failed_notification_title">Briar не может запуститься</string>
|
||||
<string name="startup_failed_notification_text">Возможно, вам нужно переустановить Briar.</string>
|
||||
<string name="startup_failed_activity_title">Ошибка запуска Briar</string>
|
||||
<string name="startup_failed_db_error">По какой-то причине, ваша база данных Briar повреждена без возможности восстановления. Ваш аккаунт, ваши данные и все ваши связи с контактами потеряны.
|
||||
К сожалению, вам нужно переустановить Briar и создать новый аккаунт.</string>
|
||||
<string name="startup_failed_service_error">Briar не смог запустить требуемый плагин. Переустановка Briar обычно решает эту проблему. Однако, пожалуйста имейте в виду, что тогда вы потеряете ваш аккаунт и все связанные с ним данные, так как Briar не использует центральных серверов для хранения ваших данных.</string>
|
||||
<string name="expiry_date_reached">Это программное обеспечение истекло.\n Спасибо за тестирование!</string>
|
||||
<string name="dialog_title_lost_password">Потерянный пароль</string>
|
||||
<string name="dialog_message_lost_password">Ваша учетная запись Briar хранится в зашифрованном виде только на устройстве, поэтому мы не можем сбросить пароль. Удалить учетную запись и начать заново?\n\nВнимание: ваши идентификаторы, контакты и сообщения будут потеряны навсегда.</string>
|
||||
<string name="startup_failed_notification_title">Не удалось запустить Briar</string>
|
||||
<string name="startup_failed_notification_text">Возможно, потребуется переустановить Briar.</string>
|
||||
<string name="startup_failed_activity_title">Сбой при запуске Briar</string>
|
||||
<string name="startup_failed_db_error">По какой-то причине, ваша база данных Briar повреждена без возможности восстановления. Ваша учетная запись, ваши данные и все ваши связи с контактами потеряны.
|
||||
К сожалению, необходимо переустановить Briar и настроить новую учетную запись.</string>
|
||||
<string name="startup_failed_service_error">Briar не смог запустить требуемый подключаемый модуль. Переустановка Briar обычно решает эту проблему. Однако обратите внимание, что после этого вы потеряете свою учетную запись и все связанные с ней данные, поскольку Briar не использует центральных серверов для хранения данных.</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="one">Это бета-версия Briar. Срок действия вашей учетной записи истекает через %d день и не может быть продлен.</item>
|
||||
<item quantity="few">Это бета-версия Briar. Срок действия вашей учетной записи истекает через %d дней и не может быть продлен.</item>
|
||||
<item quantity="many">Это бета-версия Briar. Срок действия вашей учетной записи истекает через %d дней и не может быть продлен.</item>
|
||||
<item quantity="other">Это бета-версия Briar. Срок действия вашей учетной записи истекает через %d дней и не может быть продлен.</item>
|
||||
</plurals>
|
||||
<string name="expiry_date_reached">Срок действия этого программного обеспечения истек.\nСпасибо за тестирование!</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">Открыть навигационную панель</string>
|
||||
<string name="nav_drawer_close_description">Закрыть навигационную панель</string>
|
||||
<string name="nav_drawer_open_description">Открыть навигационное меню</string>
|
||||
<string name="nav_drawer_close_description">Закрыть навигационное меню</string>
|
||||
<string name="contact_list_button">Контакты</string>
|
||||
<string name="groups_button">Приватные группы</string>
|
||||
<string name="forums_button">Форумы</string>
|
||||
@@ -40,80 +46,129 @@
|
||||
<!--Notifications-->
|
||||
<string name="ongoing_notification_title">Выполнен вход в Briar</string>
|
||||
<string name="ongoing_notification_text">Коснитесь, чтобы открыть Briar</string>
|
||||
<plurals name="private_message_notification_text">
|
||||
<item quantity="one">Новое личное сообщение.</item>
|
||||
<item quantity="few">%d новых личных сообщений.</item>
|
||||
<item quantity="many">%d новых личных сообщений.</item>
|
||||
<item quantity="other">%d новых личных сообщений.</item>
|
||||
</plurals>
|
||||
<plurals name="group_message_notification_text">
|
||||
<item quantity="one">Новое групповое сообщение</item>
|
||||
<item quantity="few">%d новых групповых сообщений.</item>
|
||||
<item quantity="many">%d новых групповых сообщений.</item>
|
||||
<item quantity="other">%d новых групповых сообщений.</item>
|
||||
</plurals>
|
||||
<plurals name="forum_post_notification_text">
|
||||
<item quantity="one">Новый пост на форуме</item>
|
||||
<item quantity="few">%d новых постов на форуме.</item>
|
||||
<item quantity="many">%d новых постов на форуме.</item>
|
||||
<item quantity="other">%d новых постов на форуме.</item>
|
||||
</plurals>
|
||||
<plurals name="blog_post_notification_text">
|
||||
<item quantity="one">Новый пост в блоге.</item>
|
||||
<item quantity="few">%d новых постов в блоге.</item>
|
||||
<item quantity="many">%d новых постов в блоге.</item>
|
||||
<item quantity="other">%d новых постов в блоге.</item>
|
||||
</plurals>
|
||||
<!--Misc-->
|
||||
<string name="now">сейчас</string>
|
||||
<string name="show">Показать</string>
|
||||
<string name="hide">Скрыть</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="cancel">Отменить</string>
|
||||
<string name="cancel">Отмена</string>
|
||||
<string name="got_it">Получено</string>
|
||||
<string name="delete">Удалить</string>
|
||||
<string name="accept">Принять</string>
|
||||
<string name="decline">Отклонить</string>
|
||||
<string name="options">Опции</string>
|
||||
<string name="online">В сети</string>
|
||||
<string name="offline">Вне сети</string>
|
||||
<string name="send">Послать</string>
|
||||
<string name="offline">Не в сети</string>
|
||||
<string name="send">Отправить</string>
|
||||
<string name="allow">Разрешить</string>
|
||||
<string name="open">Открыть</string>
|
||||
<string name="no_data">Нет данных</string>
|
||||
<string name="ellipsis">...</string>
|
||||
<string name="ellipsis">…</string>
|
||||
<string name="text_too_long">Введенный текст слишком длинный</string>
|
||||
<string name="show_onboarding">Показать справку</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">Видимо, вы впервые здесь и пока что не имеете контактов.
|
||||
Нажмите на значок + наверху и следуйте инструкциям, чтобы добавить несколько друзей в ваш список.
|
||||
Пожалуйста, помните: вы можете добавлять новые контакты только лично, лицо к лицу, чтобы в будущем не позволить никому выдать себя за вас или читать ваши сообщения.</string>
|
||||
<string name="date_no_private_messages">Сообщений нет.</string>
|
||||
<string name="no_contacts">Видимо, вы здесь новенький и пока что не имеете контактов.\n\nКоснитесь значка + вверху и следуйте инструкциям, чтобы добавить друзей в список.\n\nПомните: вы можете добавлять новые контакты только лично, чтобы никто не выдал себя за вас и не мог читать ваши сообщения.</string>
|
||||
<string name="date_no_private_messages">Нет сообщений.</string>
|
||||
<string name="no_private_messages">Это представление беседы.\n\nКажется, у нас нет беседы.\n\nПросто коснитесь поля ввода внизу, чтобы начать беседу.</string>
|
||||
<string name="message_hint">Печатает сообщение</string>
|
||||
<string name="delete_contact">Удалить контакт</string>
|
||||
<string name="dialog_title_delete_contact">Подтвердите удаление контакта</string>
|
||||
<string name="dialog_message_delete_contact">Вы уверены, что хотите удалить этот контакт и все связанные с ним сообщения?</string>
|
||||
<string name="dialog_message_delete_contact">Вы действительно хотите удалить этот контакт и все связанные с ним сообщения?</string>
|
||||
<string name="contact_deleted_toast">Контакт удален</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Добавить контакт</string>
|
||||
<string name="your_nickname">Выберите идентификатор, который вы хотите использовать:</string>
|
||||
<string name="face_to_face">Вы должны встретиться с человеком, которого хотите добавить в качестве контакта.\n\nЭто не позволит кому-либо выдать себя за вас или читать ваши сообщения в будущем.</string>
|
||||
<string name="your_nickname">Выберите идентификатор, который хотите использовать:</string>
|
||||
<string name="face_to_face">Необходимо встретиться с человеком, который требуется добавить в качестве контакта.\n\nЭто не позволит кому-либо выдать себя за вас или читать ваши сообщения.</string>
|
||||
<string name="continue_button">Продолжить</string>
|
||||
<string name="your_invitation_code">Ваш код приглашения</string>
|
||||
<string name="enter_invitation_code">Введите код приглашения вашего контакта:</string>
|
||||
<string name="searching_format">Поиск контакта с кодом приглашения %06d \u2026</string>
|
||||
<string name="searching_format">Поиск контакта с кодом приглашения %06d\u2026</string>
|
||||
<string name="connection_failed">Ошибка подключения</string>
|
||||
<string name="could_not_find_contact">Briar не смог найти ваш контакт поблизости</string>
|
||||
<string name="try_again_button">Попробовать снова</string>
|
||||
<string name="connected_to_contact">Подключено к контакту</string>
|
||||
<string name="calculating_confirmation_code">Вычисление кода подтверждения\u2026</string>
|
||||
<string name="your_confirmation_code">Ваш код подтвержения</string>
|
||||
<string name="enter_confirmation_code">Пожалуйста, введите код подтверждения вашего контакта:</string>
|
||||
<string name="enter_confirmation_code">Введите код подтверждения контакта:</string>
|
||||
<string name="waiting_for_contact">Ожидание контакта\u2026</string>
|
||||
<string name="waiting_for_contact_to_scan">Ожидание контакта для сканирования и подключения\u2026</string>
|
||||
<string name="exchanging_contact_details">Обмен контактными данными\u2026</string>
|
||||
<string name="codes_do_not_match">Коды не совпадают</string>
|
||||
<string name="interfering">Это может означать, что кто-то пытается помешать вашему соединению</string>
|
||||
<string name="interfering">Это может означать, что кто-то пытается вмешаться в ваше подключение</string>
|
||||
<string name="contact_added_toast">Контакт добавлен: %s</string>
|
||||
<string name="contact_already_exists">Контакт %s уже существует</string>
|
||||
<string name="contact_exchange_failed">Обмен контактами не удался</string>
|
||||
<string name="qr_code_invalid">Неверный QR-код</string>
|
||||
<string name="connecting_to_device">Подключение к устройству\u2026</string>
|
||||
<string name="authenticating_with_device">Аутентификация с устройством\u2026</string>
|
||||
<string name="connection_aborted_local">Соединение прервано нами! Это может означать, что кто-то пытается помешать вашему соединению</string>
|
||||
<string name="connection_aborted_remote">Соединение прервано вашим контактом! Это может означать, что кто-то пытается помешать вашему соединению</string>
|
||||
<string name="connection_aborted_local">Соединение прервано нами! Это может означать, что кто-то пытается вмешаться в ваше подключение</string>
|
||||
<string name="connection_aborted_remote">Соединение прервано вашим контактом! Это может означать, что кто-то пытается вмешаться в ваше подключение</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">Укажите ваши контакты</string>
|
||||
<string name="introduction_onboarding_text">Вы можете предоставить свои контакты друг другу, поэтому им не нужно встречаться лично, чтобы подключиться к Briar.</string>
|
||||
<string name="introduction_onboarding_title">Знакомство с контактами</string>
|
||||
<string name="introduction_onboarding_text">Вы можете представить свои контакты друг другу, поэтому им не нужно встречаться лично, чтобы подключиться к Briar. </string>
|
||||
<string name="introduction_activity_title">Выберите контакт</string>
|
||||
<string name="introduction_message_title">Ввести контакты</string>
|
||||
<string name="introduction_message_hint">Добавить сообщение (опционально)</string>
|
||||
<string name="introduction_button">Сделать введение</string>
|
||||
<string name="introduction_message_title">Познакомить контакты</string>
|
||||
<string name="introduction_message_hint">Добавить сообщение (необязательно)</string>
|
||||
<string name="introduction_button">Выполнить знакомство</string>
|
||||
<string name="introduction_sent">Ваше представление было отправлено.</string>
|
||||
<string name="introduction_error">Произошла ошибка при выполнении знакомства.</string>
|
||||
<string name="introduction_response_error">Ошибка при ответе на знакомство</string>
|
||||
<string name="introduction_request_sent">Вы хотели познакомить %1$s с %2$s.</string>
|
||||
<string name="introduction_request_received">%1$s попросил вас представить %2$s. Вы хотите добавить %2$s в ваш список контактов?</string>
|
||||
<string name="introduction_request_exists_received">%1$s попросил вас представить %2$s, но %2$s уже находится в вашем списке контактов. Поскольку %1$s может не знать об этом, вы все равно можете ответить:</string>
|
||||
<string name="introduction_request_answered_received">%1$s попросил вас представить %2$s.</string>
|
||||
<string name="introduction_response_accepted_sent">Вы приняли знакомство с %1$s.</string>
|
||||
<string name="introduction_response_declined_sent">Вы отказались от знакомства с %1$s.</string>
|
||||
<string name="introduction_response_accepted_received">%1$s принял знакомство с %2$s.</string>
|
||||
<string name="introduction_response_declined_received">%1$s отказался от знакомства с %2$s.</string>
|
||||
<string name="introduction_response_declined_received_by_introducee">%1$s говорит, что %2$s отказался от знакомства.</string>
|
||||
<plurals name="introduction_notification_text">
|
||||
<item quantity="one">Новый контакт добавлен.</item>
|
||||
<item quantity="few">%d новых контактов добавлено.</item>
|
||||
<item quantity="many">%d новых контактов добавлено.</item>
|
||||
<item quantity="other">%d новых контактов добавлено.</item>
|
||||
</plurals>
|
||||
<!--Private Groups-->
|
||||
<string name="groups_list_empty">Вы не состоите в каких-либо группах.\n\nКоснитесь значка + вверху, чтобы создать группу самостоятельно или попросить ваши контакты пригласить их в одну из своих групп.</string>
|
||||
<string name="groups_created_by">Создано %s</string>
|
||||
<plurals name="messages">
|
||||
<item quantity="one">%d сообщение</item>
|
||||
<item quantity="few">%d сообщений</item>
|
||||
<item quantity="many">%d сообщений</item>
|
||||
<item quantity="other">%d сообщений</item>
|
||||
</plurals>
|
||||
<string name="groups_group_is_empty">Эта группа пуста</string>
|
||||
<string name="groups_group_is_dissolved">Эта группа была аннулирована</string>
|
||||
<string name="groups_group_is_dissolved">Эта группа была распущена</string>
|
||||
<string name="groups_remove">Удалить</string>
|
||||
<string name="groups_create_group_title">Создать приватную группу</string>
|
||||
<string name="groups_create_group_button">Создать группу</string>
|
||||
<string name="groups_create_group_invitation_button">Отправить приглашение</string>
|
||||
<string name="groups_create_group_hint">Выберите имя для своей приватной группы</string>
|
||||
<string name="groups_invitation_sent">Групповое приглашение было отправлено</string>
|
||||
<string name="groups_invitation_sent">Приглашение на вступление в группу было отправлено</string>
|
||||
<string name="groups_message_sent">Сообщение отправлено</string>
|
||||
<string name="groups_member_list">Список участников</string>
|
||||
<string name="groups_invite_members">Пригласить участников</string>
|
||||
@@ -124,30 +179,210 @@
|
||||
<string name="groups_leave">Покинуть группу</string>
|
||||
<string name="groups_leave_dialog_title">Подтверждение</string>
|
||||
<string name="groups_leave_dialog_message">Вы уверены, что хотите покинуть эту группу?</string>
|
||||
<string name="groups_dissolve">Аннулировать группу</string>
|
||||
<string name="groups_dissolve">Распустить группу</string>
|
||||
<string name="groups_dissolve_dialog_title">Подтверждение</string>
|
||||
<string name="groups_dissolve_dialog_message">Вы уверены, что хотите аннулировать эту группу?\n\nДругие участники не смогут продолжить разговор и могут не получить последние сообщения.</string>
|
||||
<string name="groups_dissolve_button">Аннулировать</string>
|
||||
<string name="groups_dissolved_dialog_title">Группа была аннулирована</string>
|
||||
<string name="groups_dissolved_dialog_message">Создатель этой группы аннулировал ее.\n\nВы больше не можете писать сообщения в группу и можете получить не все сообщения, которые были написаны.</string>
|
||||
<string name="groups_dissolve_dialog_message">Вы уверены, что хотите распустить эту группу?\n\nДругие участники не смогут продолжить разговор и могут не получить последние сообщения.</string>
|
||||
<string name="groups_dissolve_button">Распустить</string>
|
||||
<string name="groups_dissolved_dialog_title">Группа была распущена</string>
|
||||
<string name="groups_dissolved_dialog_message">Создатель этой группы распустил ее.\n\nВы больше не можете писать сообщения в группу и можете получить не все сообщения, которые были написаны.</string>
|
||||
<!--Private Group Invitations-->
|
||||
<string name="groups_invitations_title">Групповые приглашения</string>
|
||||
<string name="groups_invitations_title">Приглашения в группу</string>
|
||||
<string name="groups_invitations_invitation_sent">Вы предложили %1$s присоединиться к группе \"%2$s\".</string>
|
||||
<string name="groups_invitations_invitation_received">%1$s приглашает вас присоединиться к группе \"%2$s\".</string>
|
||||
<string name="groups_invitations_joined">Присоединился к группе</string>
|
||||
<string name="groups_invitations_declined">Групповое приглашение отклонено</string>
|
||||
<string name="groups_invitations_declined">Приглашение в группу отклонено</string>
|
||||
<plurals name="groups_invitations_open">
|
||||
<item quantity="one">%d открытое приглашение в группу</item>
|
||||
<item quantity="few">%d открытых приглашений в группу</item>
|
||||
<item quantity="many">%d открытых приглашений в группу</item>
|
||||
<item quantity="other">%d открытых приглашений в группу</item>
|
||||
</plurals>
|
||||
<string name="groups_invitations_response_accepted_sent">Вы приняли приглашение в группу от %s.</string>
|
||||
<string name="groups_invitations_response_declined_sent">Вы отклонили приглашение в группу от %s.</string>
|
||||
<string name="groups_invitations_response_accepted_received">%s принял приглашение в группу.</string>
|
||||
<string name="groups_invitations_response_declined_received">%s отклонил приглашение в группу.</string>
|
||||
<string name="sharing_status_groups">Только создатель может пригласить новых членов в группу. Ниже перечислены все текущие члены группы.</string>
|
||||
<!--Private Groups Revealing Contacts-->
|
||||
<string name="groups_reveal_contacts">Показать контакты</string>
|
||||
<string name="groups_reveal_dialog_message">Вы можете выбрать, раскрывать ли контакты всем текущим и будущим членам этой группы.\n\nПри раскрытии контактов связь с группой становится более быстрой и надежной, поскольку вы можете общаться с раскрытыми контактами, даже если создатель группы находится в автономном режиме.</string>
|
||||
<string name="groups_reveal_visible">Связь между контактами видна группе</string>
|
||||
<string name="groups_reveal_visible_revealed_by_us">Связь между контактами видна группе (раскрывается вами)</string>
|
||||
<string name="groups_reveal_visible_revealed_by_contact">Связь между контактами видна группе (раскрывается %s)</string>
|
||||
<string name="groups_reveal_invisible">Связь между контактами не видна группе</string>
|
||||
<!--Forums-->
|
||||
<string name="no_forums">У вас еще нет форумов.\n\nПочему бы вам не создать новый, коснувшись значка + вверху?\n\nВы также можете попросить свои контакты поделиться с вами форумами.</string>
|
||||
<string name="create_forum_title">Создать форум</string>
|
||||
<string name="choose_forum_hint">Выберите имя для вашего форума</string>
|
||||
<string name="create_forum_button">Создать форум</string>
|
||||
<string name="forum_created_toast">Форум создан</string>
|
||||
<string name="no_forum_posts">Этот форум пуст.\n\nИспользуйте значок пера вверху, чтобы создать первый пост.\n\nЧувствуете одиночество? Поделитесь этим форумом с вашими контактами!</string>
|
||||
<string name="no_posts">Нет постов</string>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">%d пост</item>
|
||||
<item quantity="few">%d постов</item>
|
||||
<item quantity="many">%d постов</item>
|
||||
<item quantity="other">%d постов</item>
|
||||
</plurals>
|
||||
<string name="forum_new_entry_posted">Опубликована запись форума</string>
|
||||
<string name="forum_new_message_hint">Новая запись</string>
|
||||
<string name="forum_message_reply_hint">Новый ответ</string>
|
||||
<string name="btn_reply">Ответ</string>
|
||||
<string name="forum_leave">Покинуть форум</string>
|
||||
<string name="dialog_title_leave_forum">Подтвердить</string>
|
||||
<string name="dialog_message_leave_forum">Вы уверены, что хотите покинуть этот форум? Контакты, с которыми вы поделились этим форумом, могут быть перестать получать обновления этого форума.</string>
|
||||
<string name="dialog_button_leave">Покинуть</string>
|
||||
<string name="forum_left_toast">Покинул форум</string>
|
||||
<!--Forum Sharing-->
|
||||
<string name="forum_share_button">Поделиться форумом</string>
|
||||
<string name="contacts_selected">Выбранные контакты</string>
|
||||
<string name="activity_share_toolbar_header">Выбор контактов</string>
|
||||
<string name="no_contacts_selector">Кажется, вы здесь новенький и не имеете контактов.\n\nПожалуйста, вернитесь сюда после того, как добавите свой первый контакт.</string>
|
||||
<string name="forum_shared_snackbar">Поделиться форумом совместно с выбранными контактами</string>
|
||||
<string name="forum_share_message">Добавить сообщение (необязательно)</string>
|
||||
<string name="forum_share_error">Произошла ошибка при при попытке поделиться этим форумом.</string>
|
||||
<string name="forum_invitation_received">%1$s поделился форумом \"%2$s\" с вами.</string>
|
||||
<string name="forum_invitation_sent">Вы поделились форумом \"%1$s\" с %2$s.</string>
|
||||
<string name="forum_invitations_title">Приглашения на форум</string>
|
||||
<string name="forum_invitation_exists">Вы уже приняли приглашение на этот форум. Принятие большего числа приглашений приведет к росту и укреплению общения на форуме.</string>
|
||||
<string name="forum_joined_toast">Присоединился к форуму</string>
|
||||
<string name="forum_declined_toast">Приглашение на форум отклонено</string>
|
||||
<string name="shared_by_format">Поделился %s</string>
|
||||
<string name="forum_invitation_already_sharing">Уже поделился</string>
|
||||
<string name="forum_invitation_response_accepted_sent">Вы приняли приглашение на форум от %s.</string>
|
||||
<string name="forum_invitation_response_declined_sent">Вы отклонили приглашение на форум от %s.</string>
|
||||
<string name="forum_invitation_response_accepted_received">%sпринял приглашение на форум.</string>
|
||||
<string name="forum_invitation_response_declined_received">%s отклонил приглашение на форум.</string>
|
||||
<string name="sharing_status">Статус общего доступа</string>
|
||||
<string name="sharing_status_forum">Любой член форума может поделиться своими контактами. Вы делитесь этим форумом со следующими контактами. Могут быть и другие члены, которых вы не видите.</string>
|
||||
<string name="shared_with">Совместно с %1$d (%2$d в сети)</string>
|
||||
<plurals name="forums_shared">
|
||||
<item quantity="one">%d форум, общий с контактами</item>
|
||||
<item quantity="few">%d форумов, общих с контактами</item>
|
||||
<item quantity="many">%d форумов, общих с контактами</item>
|
||||
<item quantity="other">%d форумов, общих с контактами</item>
|
||||
</plurals>
|
||||
<string name="nobody">Никто</string>
|
||||
<!--Blogs-->
|
||||
<string name="blogs_other_blog_empty_state">Этот блог пуст.\n\nЛибо автор еще ничего не написал, либо человек, который поделился с вами этим блогом, должен выйти в Интернет, чтобы сообщения могли быть синхронизированы.</string>
|
||||
<string name="read_more">подробнее</string>
|
||||
<string name="blogs_write_blog_post">Написать в блоге</string>
|
||||
<string name="blogs_write_blog_post_body_hint">Введите ваше сообщение здесь</string>
|
||||
<string name="blogs_publish_blog_post">Опубликовать</string>
|
||||
<string name="blogs_blog_post_created">Запись блога создана</string>
|
||||
<string name="blogs_blog_post_received">Появилась новая запись блога</string>
|
||||
<string name="blogs_blog_post_scroll_to">Перейти к</string>
|
||||
<string name="blogs_feed_empty_state">Это глобальный блог.\n\nПохоже, что никто ничего не писал.\n\nБудьте первым - коснитесь значка пера, чтобы написать новую запись блога.</string>
|
||||
<string name="blogs_remove_blog">Удалить блог</string>
|
||||
<string name="blogs_remove_blog_dialog_message">Вы действительно хотите удалить этот блог со всеми записями?\nОбратите внимание, что это не приведет к удалению блога на устройствах других пользователей.</string>
|
||||
<string name="blogs_remove_blog_ok">Удалить блог</string>
|
||||
<string name="blogs_blog_removed">Блог удален</string>
|
||||
<string name="blogs_reblog_comment_hint">Добавить комментарий (необязательно)</string>
|
||||
<string name="blogs_reblog_button">Перепостить</string>
|
||||
<!--Blog Sharing-->
|
||||
<string name="blogs_sharing_share">Поделиться блогом</string>
|
||||
<string name="blogs_sharing_error">Произошла ошибка при при попытке поделиться этим блогом.</string>
|
||||
<string name="blogs_sharing_button">Поделиться блогом</string>
|
||||
<string name="blogs_sharing_snackbar">Поделиться блогом совместно с выбранными контактами</string>
|
||||
<string name="blogs_sharing_response_accepted_sent">Вы приняли приглашение в блог от %s.</string>
|
||||
<string name="blogs_sharing_response_declined_sent">Вы отклонили приглашение в блог от %s.</string>
|
||||
<string name="blogs_sharing_response_accepted_received">%s принял приглашение в блог.</string>
|
||||
<string name="blogs_sharing_response_declined_received">%s отклонил приглашение в блог.</string>
|
||||
<string name="blogs_sharing_invitation_received">%1$s поделился блогом \"%2$s\" с вами.</string>
|
||||
<string name="blogs_sharing_invitation_sent">Вы поделились блогом \"%1$s\" с %2$s.</string>
|
||||
<string name="blogs_sharing_invitations_title">Приглашения в блог</string>
|
||||
<string name="blogs_sharing_joined_toast">Подписался на блог</string>
|
||||
<string name="blogs_sharing_declined_toast">Приглашение в блог отклонено</string>
|
||||
<string name="sharing_status_blog">Любой, кто подписывается на блог, может поделиться им со своими контактами. Вы делитесь этим блогом со следующими контактами. Могут также быть и другие подписчики, которых вы не видите.</string>
|
||||
<!--RSS Feeds-->
|
||||
<string name="blogs_rss_feeds_import">Импорт RSS-канала</string>
|
||||
<string name="blogs_rss_feeds_import_button">Импорт</string>
|
||||
<string name="blogs_rss_feeds_import_hint">Введите URL-адрес RSS-канала</string>
|
||||
<string name="blogs_rss_feeds_import_error">Мы сожалеем! Не удалось импортировать этот RSS-канал.</string>
|
||||
<string name="blogs_rss_feeds_manage">Управление RSS-каналами</string>
|
||||
<string name="blogs_rss_feeds_manage_imported">Импортирован:</string>
|
||||
<string name="blogs_rss_feeds_manage_author">Автор:</string>
|
||||
<string name="blogs_rss_feeds_manage_updated">Последнее обновление:</string>
|
||||
<string name="blogs_rss_remove_feed">Удалить RSS-канал</string>
|
||||
<string name="blogs_rss_remove_feed_dialog_message">Вы действительно хотите удалить этот RSS-канал и все его сообщения?\nЛюбые сообщения, которыми вы поделились, не будут удалены с устройств других пользователей.</string>
|
||||
<string name="blogs_rss_remove_feed_ok">Удалить RSS-канал</string>
|
||||
<string name="blogs_rss_feeds_manage_delete_error">Не удалось удалить RSS-канал!</string>
|
||||
<string name="blogs_rss_feeds_manage_empty_state">Вы еще не импортировали RSS-каналы.\n\nПочему бы вам не нажать кнопку + в правом верхнем углу экрана, чтобы добавить первый?</string>
|
||||
<string name="blogs_rss_feeds_manage_error">Ошибка при загрузке каналов. Повторите попытку позже.</string>
|
||||
<!--Settings Network-->
|
||||
<string name="network_settings_title">Сети</string>
|
||||
<string name="bluetooth_setting">Подключение через Bluetooth</string>
|
||||
<string name="bluetooth_setting_enabled">Когда контакты находятся поблизости</string>
|
||||
<string name="bluetooth_setting_disabled">Только при добавлении контактов</string>
|
||||
<string name="tor_network_setting">Подключение через Tor</string>
|
||||
<string name="tor_network_setting_never">Никогда</string>
|
||||
<string name="tor_network_setting_wifi">Только при использовании Wi-Fi</string>
|
||||
<string name="tor_network_setting_always">При использовании Wi-Fi или мобильных данных</string>
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">Безопасность</string>
|
||||
<string name="change_password">Изменить пароль</string>
|
||||
<string name="current_password">Введите текущий пароль:</string>
|
||||
<string name="choose_new_password">Выберите новый пароль:</string>
|
||||
<string name="confirm_new_password">Подтвердите новый пароль:</string>
|
||||
<string name="password_changed">Пароль был изменен.</string>
|
||||
<string name="panic_setting">Настройка кнопки паники</string>
|
||||
<string name="panic_setting_title">Кнопка паники</string>
|
||||
<string name="panic_setting_hint">Настройте поведение Briar, когда вы используете приложение Кнопка паники</string>
|
||||
<string name="panic_app_setting_title">Приложение Кнопка паники</string>
|
||||
<string name="unknown_app">неизвестное приложение</string>
|
||||
<string name="panic_app_setting_summary">Приложение не установлено</string>
|
||||
<string name="panic_app_setting_none">Никто</string>
|
||||
<string name="dialog_title_connect_panic_app">Подтвердить приложение Паника</string>
|
||||
<string name="dialog_message_connect_panic_app">Вы уверены, что хотите разрешить %1$s запускать действия разрушительной Кнопкой паники?</string>
|
||||
<string name="lock_setting_title">Выйти</string>
|
||||
<string name="lock_setting_summary">Выйти из Briar, если нажата Кнопка паники</string>
|
||||
<string name="purge_setting_title">Удалить аккаунт</string>
|
||||
<string name="purge_setting_summary">Удалите свою учетную запись Briar, если нажата Кнопка паники. Предупреждение: это необратимо удалит ваши идентификаторы, контакты и сообщения</string>
|
||||
<string name="uninstall_setting_title">Удалить Briar</string>
|
||||
<string name="uninstall_setting_summary">Это требует подтверждения вручную в случае возникновения паники</string>
|
||||
<!--Settings Notifications-->
|
||||
<string name="notification_settings_title">Уведомления</string>
|
||||
<string name="notify_private_messages_setting_title">Приватные сообщения</string>
|
||||
<string name="notify_private_messages_setting_summary">Показывать оповещения для приватных сообщений</string>
|
||||
<string name="notify_group_messages_setting_title">Групповые сообщения</string>
|
||||
<string name="notify_group_messages_setting_summary">Показывать оповещения для групповых сообщений</string>
|
||||
<string name="notify_forum_posts_setting_title">Записи форума</string>
|
||||
<string name="notify_forum_posts_setting_summary">Показывать оповещения для записей форума</string>
|
||||
<string name="notify_blog_posts_setting_title">Записи блога</string>
|
||||
<string name="notify_blog_posts_setting_summary">Показывать оповещения для записей блога</string>
|
||||
<string name="notify_vibration_setting">Вибрация</string>
|
||||
<string name="notify_lock_screen_setting_title">Экран блокировки</string>
|
||||
<string name="notify_lock_screen_setting_summary">Показывать уведомления на экране блокировки</string>
|
||||
<string name="notify_sound_setting">Звук</string>
|
||||
<string name="notify_sound_setting_default">Мелодия по умолчанию</string>
|
||||
<string name="notify_sound_setting_disabled">Нет</string>
|
||||
<string name="choose_ringtone_title">Выберите мелодию звонка</string>
|
||||
<!--Settings Feedback-->
|
||||
<string name="feedback_settings_title">Обратная связь</string>
|
||||
<string name="send_feedback">Отправить отзыв</string>
|
||||
<!--Link Warning-->
|
||||
<string name="link_warning_title">Ссылка-предупреждение</string>
|
||||
<string name="link_warning_intro">Вы собираетесь открыть следующую ссылку с внешним приложением.</string>
|
||||
<string name="link_warning_text">Это может быть использовано для идентификации вас. Подумайте о том, доверяете ли вы человеку, который прислал вам эту ссылку, и рассмотрите возможность его открытия в Orfox.</string>
|
||||
<string name="link_warning_open_link">Открыть ссылку</string>
|
||||
<!--Crash Reporter-->
|
||||
<string name="crash_report_title">Отчет о сбое Briar</string>
|
||||
<string name="briar_crashed">К сожалению, Briar неожиданно завершил работу.</string>
|
||||
<string name="not_your_fault">Это не ваша вина.</string>
|
||||
<string name="please_send_report">Пожалуйста, помогите сделать Briar еще лучше, отправив нам отчет о сбоях.</string>
|
||||
<string name="report_is_encrypted">Мы обещаем, что отчет зашифрован и будет отправлен безопасно.</string>
|
||||
<string name="feedback_title">Обратная связь</string>
|
||||
<string name="describe_crash">Опишите, что произошло (необязательно)</string>
|
||||
<string name="enter_feedback">Введите свой отзыв</string>
|
||||
<string name="optional_contact_email">Ваш адрес электронной почты (необязательно)</string>
|
||||
<string name="include_debug_report_crash">Включить анонимные данные о сбое</string>
|
||||
<string name="include_debug_report_feedback">Включить анонимные данные об этом устройстве</string>
|
||||
<string name="could_not_load_report_data">Не удалось загрузить данные отчета.</string>
|
||||
<string name="send_report">Отправить отчет</string>
|
||||
<string name="close">Закрыть</string>
|
||||
<string name="dev_report_saved">Отчет сохранен. Он будет отправлен при следующем входе в Briar.</string>
|
||||
<!--Sign Out-->
|
||||
<string name="progress_title_logout">Выход из Briar…</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">Обнаружено наложение экрана</string>
|
||||
<string name="screen_filter_body">Другое приложение рисуется поверх Briar. Чтобы защитить вашу безопасность, Briar не будет реагировать на прикосновения, когда другое приложение рисует сверху.\n\nПопробуйте отключить следующие приложения при использовании Briar:\n\n%1$s</string>
|
||||
</resources>
|
||||
|
||||
327
briar-android/src/main/res/values-sr/strings.xml
Normal file
327
briar-android/src/main/res/values-sr/strings.xml
Normal file
@@ -0,0 +1,327 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<!--Setup-->
|
||||
<string name="setup_title">Briar Podešavanje</string>
|
||||
<string name="setup_explanation">Vaš Briar račun je kriptovan i sačuvan na vašem uređaju, ne u server-oblaku. Ako deinstalirate Briar ili zaboravite šifru, nema načina da povratite vaš račun ili vaše podatke.</string>
|
||||
<string name="choose_nickname">Izaberite vaš nadimak</string>
|
||||
<string name="choose_password">Izaberite vašu šifru</string>
|
||||
<string name="confirm_password">Potvrdite vašu šifru</string>
|
||||
<string name="name_too_long">Ime je predugo</string>
|
||||
<string name="password_too_weak">Šifra je slaba</string>
|
||||
<string name="passwords_do_not_match">Šifre se ne poklapaju</string>
|
||||
<string name="create_account_button">Kreirajte račun</string>
|
||||
<!--Login-->
|
||||
<string name="enter_password">Unesite vašu šifru:</string>
|
||||
<string name="try_again">Pogrešna šifra, probajte opet</string>
|
||||
<string name="sign_in_button">Prijavite se</string>
|
||||
<string name="forgotten_password">Zaboravio sam šifru</string>
|
||||
<string name="dialog_title_lost_password">Izgubljena šifra</string>
|
||||
<string name="dialog_message_lost_password">Vaš Briar račun je kriptovan i sačuvan na vašem uređaju, ne u server-oblaku, pa nemožemo resetovati vašu šifru. Želite li da izbrišete račun i počnete iz početka?\n\nPozor: Vaši identiteti, kontakti i poruke će biti premanentno izgubljeni.</string>
|
||||
<string name="startup_failed_notification_title">Brirar nije mogao da startuje</string>
|
||||
<string name="startup_failed_notification_text">Možda ćete morati da reinstalirate Briar</string>
|
||||
<string name="startup_failed_activity_title">Briar neuspješno startovanje</string>
|
||||
<string name="startup_failed_db_error">Iz nekog razloga, Briar baza podataka je nepovratno korumpirana. Vaš račun, vaši podaci i svi kontakti su izgubljeni. Nažalost, morate reinstalirati Briar i podesiti novi račun.</string>
|
||||
<string name="startup_failed_service_error">Briar nije mogao da startuje potreban plugin. Reinstaliranje Briara obično riješi problem. Međutim, imajte na umu da ćete izgubiti vaš račun i sve podatke vezane za njega pošto Briar ne koristi centralne servere da sačuva vaše podatke.</string>
|
||||
<string name="expiry_date_reached">Ovaj softver je istekao.\nHvala na testiranju!</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">Otvorite navigacionu fioku</string>
|
||||
<string name="nav_drawer_close_description">Zatvorite navigacionu fioku</string>
|
||||
<string name="contact_list_button">Kontakti</string>
|
||||
<string name="groups_button">Privatne Grupe</string>
|
||||
<string name="forums_button">Forumi</string>
|
||||
<string name="blogs_button">Blogovi</string>
|
||||
<string name="settings_button">Podešavanja</string>
|
||||
<string name="sign_out_button">Odjava</string>
|
||||
<!--Transports-->
|
||||
<string name="transport_tor">Internet</string>
|
||||
<string name="transport_bt">Bluetooth</string>
|
||||
<string name="transport_lan">Wi-Fi</string>
|
||||
<!--Notifications-->
|
||||
<string name="ongoing_notification_title">Prijavljeni ste u Briar</string>
|
||||
<string name="ongoing_notification_text">Dodirnite da otvorite Briar.</string>
|
||||
<!--Misc-->
|
||||
<string name="now">sada</string>
|
||||
<string name="show">Pokazi</string>
|
||||
<string name="hide">Sakrij</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="cancel">Odustani</string>
|
||||
<string name="got_it">Jasno</string>
|
||||
<string name="delete">Briši</string>
|
||||
<string name="accept">Prihvati</string>
|
||||
<string name="decline">Odbij</string>
|
||||
<string name="options">Opcije</string>
|
||||
<string name="online">Na vezi</string>
|
||||
<string name="offline">Iskljucen</string>
|
||||
<string name="send">Šalji</string>
|
||||
<string name="allow">Dozvoli</string>
|
||||
<string name="open">Otvori</string>
|
||||
<string name="no_data">Nema podataka</string>
|
||||
<string name="ellipsis">...</string>
|
||||
<string name="text_too_long">Uneti tekst je predugačak</string>
|
||||
<string name="show_onboarding">Pokazi Pomoc dijalog </string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">Čini se da ste ovdje novi i da nemate još kontakata.\n\nTaknite + ikonu na vrhu i pratite instrukcije da bi dodali neke prijatelje na listu.\n\nMolimo da upamtite: Mozete samo dodati nove kontakte postupkom lice-u-lice kako bi se spriječili pokušaji lažnog predstavljanja ili da bi pročitali poruke upućene vama.</string>
|
||||
<string name="date_no_private_messages">Nema poruka.</string>
|
||||
<string name="no_private_messages">Ovo je pregled konverzacija.\n\nČini se da imamo nedostatak konverzacija.\n\nSamo taknite polje ya unos na dnu kako bi počeli konverzaciju.</string>
|
||||
<string name="message_hint">Ukucajte poruku</string>
|
||||
<string name="delete_contact">Izbrišite kontakt</string>
|
||||
<string name="dialog_title_delete_contact">Potvrdite brisanje kontakta</string>
|
||||
<string name="dialog_message_delete_contact">Jeste li sigurni da želite da uklonite ovaj kontakt i sve poruke koje ste razmijenili?</string>
|
||||
<string name="contact_deleted_toast">Kontakt izbrisan</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Dodaj kontakt</string>
|
||||
<string name="your_nickname">Izaberite identitet koji želite da koristite</string>
|
||||
<string name="face_to_face">Morate se sresti sa osobom koju želite da dodate kao kontakt.\n\nOvo će spriječiti bilo koga da se predstavi kao vi ili da ubuduće čita poruke.</string>
|
||||
<string name="continue_button">Dalje</string>
|
||||
<string name="your_invitation_code">Vaš kod za poziv je</string>
|
||||
<string name="enter_invitation_code">Unesite kod pozivnice vašeg kontakta:</string>
|
||||
<string name="searching_format">Tražim kontakt sa kodom pozivnice %06d\u2026</string>
|
||||
<string name="connection_failed">Veza je pukla</string>
|
||||
<string name="could_not_find_contact">Briar nije mogao naći vaš kontakt u blizini</string>
|
||||
<string name="try_again_button">Probajte opet</string>
|
||||
<string name="connected_to_contact">Povezite se sa kontaktom</string>
|
||||
<string name="calculating_confirmation_code">Kalkulisanje koda konfirmacije\u2026</string>
|
||||
<string name="your_confirmation_code">Vaš konfirmacioni kod je</string>
|
||||
<string name="enter_confirmation_code">Unesite konfirmacioni kod vašeg kontakta:</string>
|
||||
<string name="waiting_for_contact">Čekam kontakt\u2026</string>
|
||||
<string name="waiting_for_contact_to_scan">Čekam da kontakt skenira i poveže se\u2026</string>
|
||||
<string name="exchanging_contact_details">Razmjenjujem detalje kontakta\u2026</string>
|
||||
<string name="codes_do_not_match">Kodovi se nijesu poklopili</string>
|
||||
<string name="interfering">Ovo može značiti da neko pokušava da se umiješa u vašu vezu</string>
|
||||
<string name="contact_added_toast">Kontakt dodat: %s</string>
|
||||
<string name="contact_already_exists">Kontakt %s već postoji</string>
|
||||
<string name="contact_exchange_failed">Razmjena kontakata nije uspjela</string>
|
||||
<string name="qr_code_invalid">QR kod nije validan</string>
|
||||
<string name="connecting_to_device">Povezujem se sa uređajem\u2026</string>
|
||||
<string name="authenticating_with_device">Autentikacija sa uređajem\u2026</string>
|
||||
<string name="connection_aborted_local">Konekcija prekinuta sa naše strane! Ovo može značiti da neko pokušava da se umiješa u vašu vezu</string>
|
||||
<string name="connection_aborted_remote">Konekcija prekinuta sa od strane kontakta! Ovo može značiti da neko pokušava da se umiješa u vašu vezu</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">Upoznajte vaše kontakte</string>
|
||||
<string name="introduction_onboarding_text">Vi možete da upoznate vaše kontakte međusobno, da nebi morali da srijeću lice u liceda bi se povezali na Briar.</string>
|
||||
<string name="introduction_activity_title">Izaberite kontakt</string>
|
||||
<string name="introduction_message_title">Upoznajte kontakte</string>
|
||||
<string name="introduction_message_hint">Dodajte poruku (opciono)</string>
|
||||
<string name="introduction_button">Izvršite upoznavanje</string>
|
||||
<string name="introduction_sent">Vaše poziv na upoznavanje je poslat</string>
|
||||
<string name="introduction_error">Došlo je do greške pri izvršenju upoznavanja.</string>
|
||||
<string name="introduction_response_error">Greška pri odgovoru na upoznavanje</string>
|
||||
<string name="introduction_request_sent">Tražili ste da se %1$s i %2$s upoznaju.</string>
|
||||
<string name="introduction_request_received">%1$s je tražio da vas upozna sa %2$s. Da li želite da dodate %2$s u vašu listu kontakata?</string>
|
||||
<string name="introduction_request_exists_received">%1$s je tražio da vas upozna sa %2$s, ali %2$s je već u vašoj listi kontakata. Pošto %1$s to možda nezna, vi i dalje možete da odgovorite: </string>
|
||||
<string name="introduction_request_answered_received">%1$s je tražio da vas upozna sa %2$s.</string>
|
||||
<string name="introduction_response_accepted_sent">Prihvatili ste upoznavanje sa kontaktom %1$s.</string>
|
||||
<string name="introduction_response_declined_sent">Odbili ste upoznavanje sa kontaktom %1$s.</string>
|
||||
<string name="introduction_response_accepted_received">%1$s prihvata upoznavanje sa %2$s.</string>
|
||||
<string name="introduction_response_declined_received">%1$s je prihvatio-la upoznavanje sa kontaktom %2$s.</string>
|
||||
<string name="introduction_response_declined_received_by_introducee">%1$s kaže da je %2$s odbio-la upoznavanje.</string>
|
||||
<!--Private Groups-->
|
||||
<string name="groups_list_empty">Ne učestvujete ni u jednoj grupi.\n\nTaknite + ikonu na vrhu da bi sami kreirali grupu ili tražite vašim kontaktima da vas pozovu u neku od njihovih grupa.</string>
|
||||
<string name="groups_created_by">Kreator je %s</string>
|
||||
<string name="groups_group_is_empty">Grupa je prazna</string>
|
||||
<string name="groups_group_is_dissolved">Ova grupa je rasformirana</string>
|
||||
<string name="groups_remove">Ukloni</string>
|
||||
<string name="groups_create_group_title">Kreiraj privatnu grupu</string>
|
||||
<string name="groups_create_group_button">Kreiraj grupu</string>
|
||||
<string name="groups_create_group_invitation_button">Pošalji poziv</string>
|
||||
<string name="groups_create_group_hint">Izaberite ime za vašu privatnu grupu</string>
|
||||
<string name="groups_invitation_sent">Grupna pozivnica je poslata</string>
|
||||
<string name="groups_message_sent">Poruka poslata</string>
|
||||
<string name="groups_member_list">Lista članova</string>
|
||||
<string name="groups_invite_members">Pošaljite poziv članovima</string>
|
||||
<string name="groups_member_created_you">Vi ste kreirali grupu</string>
|
||||
<string name="groups_member_created">%s je kreator grupe</string>
|
||||
<string name="groups_member_joined_you">Pristupili ste grupi</string>
|
||||
<string name="groups_member_joined">1%s je pristupio-la grupi</string>
|
||||
<string name="groups_leave">Napustite grupu</string>
|
||||
<string name="groups_leave_dialog_title">Potvrdite napuštanje grupe</string>
|
||||
<string name="groups_leave_dialog_message">Jeste li sigurni da želite da napustite grupu?</string>
|
||||
<string name="groups_dissolve">Raspustite grupu</string>
|
||||
<string name="groups_dissolve_dialog_title">Potvrdite raspuštanje grupe</string>
|
||||
<string name="groups_dissolve_dialog_message">Jeste li sigurni da želite da raspustite ovu grupu?\n\nOstali članovi neće više moći da nastave njihovu konverzaciju i možda neće dobiti poslednje poruke.</string>
|
||||
<string name="groups_dissolve_button">Raspusti</string>
|
||||
<string name="groups_dissolved_dialog_title">Grupa je raspuštena</string>
|
||||
<string name="groups_dissolved_dialog_message">Kreator je raspustio ovu grupu.\n\nNemožete više pisati poruke u grupi i možda nećete primiti poljednje poruke koje su napisane.</string>
|
||||
<!--Private Group Invitations-->
|
||||
<string name="groups_invitations_title">Grupni pozivi</string>
|
||||
<string name="groups_invitations_invitation_sent">Pozvali ste kontakt %1$s da se pridruzi grupi \"%2$s\".</string>
|
||||
<string name="groups_invitations_invitation_received">%1$s je poslao-la poziv da pristupite grupi \"%2$s\".</string>
|
||||
<string name="groups_invitations_joined">Pristupili ste grupi</string>
|
||||
<string name="groups_invitations_declined">Grupni poziv je odbijen</string>
|
||||
<string name="groups_invitations_response_accepted_sent">Prihvatili ste grupni poziv od kontakta %s.</string>
|
||||
<string name="groups_invitations_response_declined_sent">Odbili ste grupni poziv od kontakta %s.</string>
|
||||
<string name="groups_invitations_response_accepted_received">%s je prihvatio-la grupni poziv.</string>
|
||||
<string name="groups_invitations_response_declined_received">1%s je odbio-la grupni poziv.</string>
|
||||
<string name="sharing_status_groups">Samo kreator moze da pozove nove članove u grupu. Ispod su trenutni članovi grupe.</string>
|
||||
<!--Private Groups Revealing Contacts-->
|
||||
<string name="groups_reveal_contacts">Otkrijte kontakte</string>
|
||||
<string name="groups_reveal_dialog_message">Možete odlučiti da otkrijete kontakte svim trenutnim i budućim članovima ove grupe.\n\nOtkrivanje kontakata čini konekciju ka članovima grupe bržom i pouzdanijom, jer možete da komunicirate sa otkrivenim kontaktima čak i kad kreator grupe nije na vezi.</string>
|
||||
<string name="groups_reveal_visible">Veze kontakata su vidljive grupi</string>
|
||||
<string name="groups_reveal_visible_revealed_by_us">Veze kontakata su vidljive grupi (vi ste je otkrili)</string>
|
||||
<string name="groups_reveal_visible_revealed_by_contact">Veze kontakata su vidljive grupi (otkriveno od strane %s)</string>
|
||||
<string name="groups_reveal_invisible">Veze kontakata nisu vidljive grupi</string>
|
||||
<!--Forums-->
|
||||
<string name="no_forums">Još nemate nijedan forum.\n\nZašto ne kreirate jedan sami dodirom na + ikonu pri vrhu?\n\nMožete takođe da pitate kontakte da podijele neki forum sa vama.</string>
|
||||
<string name="create_forum_title">Kreirajte forum</string>
|
||||
<string name="choose_forum_hint">Izaberite ime za vaš forum</string>
|
||||
<string name="create_forum_button">Kreirajte forum</string>
|
||||
<string name="forum_created_toast">Forum je kreiran</string>
|
||||
<string name="no_forum_posts">Ovaj forum je prazan.\n\nKoristite ikonu olovke pri vrhu da napišete prvi post.\n\nOsjećate se usamljeno ovdje? Podijelite ovaj forum sa više vaših kontakata!</string>
|
||||
<string name="no_posts">Nema postova</string>
|
||||
<string name="forum_new_entry_posted">Forumski zapis postovan</string>
|
||||
<string name="forum_new_message_hint">Novi zapis</string>
|
||||
<string name="forum_message_reply_hint">Novi odgovor</string>
|
||||
<string name="btn_reply">Odgovor</string>
|
||||
<string name="forum_leave">Napustite forum</string>
|
||||
<string name="dialog_title_leave_forum">Potvrdite napuštanje foruma</string>
|
||||
<string name="dialog_message_leave_forum">jeste li sigurni da želite da napustite forum? Kontakti kojima ste podijelili ovaj forum mogu prestati da dobijaju nove postove foruma. </string>
|
||||
<string name="dialog_button_leave">Napusti</string>
|
||||
<string name="forum_left_toast">Napustili ste forum</string>
|
||||
<!--Forum Sharing-->
|
||||
<string name="forum_share_button">Dijelite forum</string>
|
||||
<string name="contacts_selected">Kontakti selektovani</string>
|
||||
<string name="activity_share_toolbar_header">Izaberite kontakte</string>
|
||||
<string name="no_contacts_selector">Izgleda da ste ovdje novi i da nemate još kontakata.\n\nVratite se nazad ovdje kad dodate vaš prvi kontakt.</string>
|
||||
<string name="forum_shared_snackbar">Forum je podijeljen sa izabranim kontaktima</string>
|
||||
<string name="forum_share_message">Dodajte poruku (opciono)</string>
|
||||
<string name="forum_share_error">Došlo je do greške prilikom slanja ovog foruma.</string>
|
||||
<string name="forum_invitation_received">%1$s je podijelio-la forum \"%2$s\" sa vama.</string>
|
||||
<string name="forum_invitation_sent">Podijelili ste forum \"%1$s\" sa %2$s.</string>
|
||||
<string name="forum_invitations_title">Pozivnice za forum</string>
|
||||
<string name="forum_invitation_exists">Vi ste već prihvatili poziv u ovaj forum. Prihvatanje dodatnih pozivnica će povećati i ojačati komunikaciju u forumu.</string>
|
||||
<string name="forum_joined_toast">Pristupili ste forumu</string>
|
||||
<string name="forum_declined_toast">Pozivnica za forum je odbijena</string>
|
||||
<string name="shared_by_format">Podijelio-la %s</string>
|
||||
<string name="forum_invitation_already_sharing">Već se dijeli</string>
|
||||
<string name="forum_invitation_response_accepted_sent">Prihvatili ste poziv u forum od %s.</string>
|
||||
<string name="forum_invitation_response_declined_sent">Odbili ste poziv u forum od %s.</string>
|
||||
<string name="forum_invitation_response_accepted_received">1%s prihvata vaš poziv u forum.</string>
|
||||
<string name="forum_invitation_response_declined_received">%s odbija vaš poziv u forum.</string>
|
||||
<string name="sharing_status">Status dijeljenja</string>
|
||||
<string name="sharing_status_forum">Bilo koji član foruma može ga podijeliti sa svojim kontaktima. Vi dijelite forum sa slijedećim kontaktima. Moguće je da ima drugih članova koje ne vidite.</string>
|
||||
<string name="shared_with">Pdijeljeno sa %1$d (%2$d na vezi)</string>
|
||||
<string name="nobody">Niko</string>
|
||||
<!--Blogs-->
|
||||
<string name="blogs_other_blog_empty_state">Ovaj blog je trenutno prazan.\n\nIli autor nije još ništa napisao, ili osoba koja je podijelila ovaj blog sa vama treba da bude na vezi, kako bi postovi bili sinhronizovani.</string>
|
||||
<string name="read_more">pročitaj ostalo</string>
|
||||
<string name="blogs_write_blog_post">Napiši Blog Post</string>
|
||||
<string name="blogs_write_blog_post_body_hint">Ukucajte vaš blog post ovdje</string>
|
||||
<string name="blogs_publish_blog_post">Objavi</string>
|
||||
<string name="blogs_blog_post_created">Blog post je kreiran</string>
|
||||
<string name="blogs_blog_post_received">Primljen je novi blog post</string>
|
||||
<string name="blogs_blog_post_scroll_to">Skroluj do</string>
|
||||
<string name="blogs_feed_empty_state">Ovo je globalni blog kanal.\n\nIzgleda da niko još uvjek nije ništa blogovao.\n\nBudi prvi i dotakni ikonu olovke da napišeš novi blog post.</string>
|
||||
<string name="blogs_remove_blog">Ukloni blog</string>
|
||||
<string name="blogs_remove_blog_dialog_message">Jeste li sigurni da želite da uklonite ovaj blog i sve postove?\nUzmite u obzir da ovo neće ukloniti blog sa uređaja drugih osoba.</string>
|
||||
<string name="blogs_remove_blog_ok">Uklonite blog</string>
|
||||
<string name="blogs_blog_removed">Blog uklonjen</string>
|
||||
<string name="blogs_reblog_comment_hint">Dodajte komentar (opciono)</string>
|
||||
<string name="blogs_reblog_button">Reblogujte</string>
|
||||
<!--Blog Sharing-->
|
||||
<string name="blogs_sharing_share">Podijelite blog</string>
|
||||
<string name="blogs_sharing_error">Došlo je do greške pri podjeli bloga.</string>
|
||||
<string name="blogs_sharing_button">Podijeli blog</string>
|
||||
<string name="blogs_sharing_snackbar">Blog je podijeljen sa izabranim kontaktima</string>
|
||||
<string name="blogs_sharing_response_accepted_sent">Prihvatili ste blog pozivnicu od %s.</string>
|
||||
<string name="blogs_sharing_response_declined_sent">Odbili ste blog pozivnicu od %s.</string>
|
||||
<string name="blogs_sharing_response_accepted_received">%s je prihvatio-la blog pozivnicu.</string>
|
||||
<string name="blogs_sharing_response_declined_received">%s je odbio-la blog pozivnicu.</string>
|
||||
<string name="blogs_sharing_invitation_received">%1$s je podijelio-la blog \"%2$s\" sa vama.</string>
|
||||
<string name="blogs_sharing_invitation_sent">Podijelili ste blog \"%1$s\" sa %2$s.</string>
|
||||
<string name="blogs_sharing_invitations_title">Blog pozivnice</string>
|
||||
<string name="blogs_sharing_joined_toast">Potpisani ste na blog</string>
|
||||
<string name="blogs_sharing_declined_toast">Blog poziv je Odbijen</string>
|
||||
<string name="sharing_status_blog">Ko god se potpiše za ovaj blog može da ga podijeli sa svojim kontaktima. Vi dijelite blog sa slijedećim kontaktima. Moguće je da ima još potpisnika koje nemožete da vidite.</string>
|
||||
<!--RSS Feeds-->
|
||||
<string name="blogs_rss_feeds_import">Uvezi RSS kanal</string>
|
||||
<string name="blogs_rss_feeds_import_button">Uvezi</string>
|
||||
<string name="blogs_rss_feeds_import_hint">Unesi URL od RSS kanala</string>
|
||||
<string name="blogs_rss_feeds_import_error">Žao nam je! Došlo je do greške pri unosu vašeg kanala.</string>
|
||||
<string name="blogs_rss_feeds_manage">Rukujte RSS kanalima</string>
|
||||
<string name="blogs_rss_feeds_manage_imported">Uvezeno:</string>
|
||||
<string name="blogs_rss_feeds_manage_author">Autor:</string>
|
||||
<string name="blogs_rss_feeds_manage_updated">Zadnje ažuriranje:</string>
|
||||
<string name="blogs_rss_remove_feed">Uklonite kanal</string>
|
||||
<string name="blogs_rss_remove_feed_dialog_message">Jeste li sigurni da želite da uklonite ovaj kanal i sve njegove postove?\nBilo koji post koji ste podijelili se neće ukloniti sa uređaja drugih osoba.</string>
|
||||
<string name="blogs_rss_remove_feed_ok">Ukloni kanal</string>
|
||||
<string name="blogs_rss_feeds_manage_delete_error">Kanal nije bilo moguće ukloniti!</string>
|
||||
<string name="blogs_rss_feeds_manage_empty_state">Nijeste uvezli nijedan RSS kanal.\n\nYZašto nebi dotakli plus znak na vrhu desnog dijela ekrana da dodate prvi?</string>
|
||||
<string name="blogs_rss_feeds_manage_error">Došlo je do problema pri učitavanju vaših kanala. Probajte opet kasnije.</string>
|
||||
<!--Settings Network-->
|
||||
<string name="network_settings_title">Mreže</string>
|
||||
<string name="bluetooth_setting">Povežite se preko Bluetooth-a</string>
|
||||
<string name="bluetooth_setting_enabled">Kad god su kontakti blizu</string>
|
||||
<string name="bluetooth_setting_disabled">Samo pri dodavanju kontakata</string>
|
||||
<string name="tor_network_setting">Povežite se preko Tor-a</string>
|
||||
<string name="tor_network_setting_never">Nikad</string>
|
||||
<string name="tor_network_setting_wifi">Samo kad koristim Wi-Fi</string>
|
||||
<string name="tor_network_setting_always">Kad koristim Wi-Fi ili mobile data</string>
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">Sigurnost</string>
|
||||
<string name="change_password">Promijeni sifru</string>
|
||||
<string name="current_password">Unesite vašu trenutnu šifru:</string>
|
||||
<string name="choose_new_password">Izaberite vašu novu šifru:</string>
|
||||
<string name="confirm_new_password">Potvrdite vašu novu šifru:</string>
|
||||
<string name="password_changed">Šifra je promijenjena</string>
|
||||
<string name="panic_setting">Podešavanje panik dugmeta</string>
|
||||
<string name="panic_setting_title">Panik dugme</string>
|
||||
<string name="panic_setting_hint">Podesite kako Briar reaguje kad koristite panik dugme app</string>
|
||||
<string name="panic_app_setting_title">Panik Dugne App</string>
|
||||
<string name="unknown_app">nepoznata aplikacija</string>
|
||||
<string name="panic_app_setting_summary">Nema postavljene aplikacije</string>
|
||||
<string name="panic_app_setting_none">Ništa</string>
|
||||
<string name="dialog_title_connect_panic_app">Potvrdi Panic App</string>
|
||||
<string name="dialog_message_connect_panic_app">Jeste li sigurni da želite da dozvolite %1$s da pokrene destruktivne akcije panik dugmeta?</string>
|
||||
<string name="lock_setting_title">Izlogujte se</string>
|
||||
<string name="lock_setting_summary">Izloguj se iz Briar-a ako je pritisnuto panik dugme</string>
|
||||
<string name="purge_setting_title">Izbriši račun</string>
|
||||
<string name="purge_setting_summary">Izbriši Briar račun ako je panik dugme pritisnuto. Pažnja: Ovo će permanentno izbrisati vaše identitete, kontakte i poruke</string>
|
||||
<string name="uninstall_setting_title">Deinstaliraj Briar</string>
|
||||
<string name="uninstall_setting_summary">Ovo zahtijeva ručnu potvrdu u slučaju panike</string>
|
||||
<!--Settings Notifications-->
|
||||
<string name="notification_settings_title">Obavještenja</string>
|
||||
<string name="notify_private_messages_setting_title">Privatne poruke</string>
|
||||
<string name="notify_private_messages_setting_summary">Prikaži upozorenja na privatne poruke</string>
|
||||
<string name="notify_group_messages_setting_title">Grupne poruke</string>
|
||||
<string name="notify_group_messages_setting_summary">Prikaži upozorenja za grupne poruke</string>
|
||||
<string name="notify_forum_posts_setting_title">Forum postovi</string>
|
||||
<string name="notify_forum_posts_setting_summary">Prikaži upozorenja za forum postove</string>
|
||||
<string name="notify_blog_posts_setting_title">Blog postovi</string>
|
||||
<string name="notify_blog_posts_setting_summary">Prikaži upozorenja na blog postove</string>
|
||||
<string name="notify_vibration_setting">Vibriranje</string>
|
||||
<string name="notify_lock_screen_setting_title">Zaključaj ekran</string>
|
||||
<string name="notify_lock_screen_setting_summary">Prikaži obavještenja na zaključanom ekranu</string>
|
||||
<string name="notify_sound_setting">Zvuk</string>
|
||||
<string name="notify_sound_setting_default">Podrazumijevanio zvono</string>
|
||||
<string name="notify_sound_setting_disabled">Ništa</string>
|
||||
<string name="choose_ringtone_title">Izaberi zvuk zvona</string>
|
||||
<!--Settings Feedback-->
|
||||
<string name="feedback_settings_title">Povratne informacije</string>
|
||||
<string name="send_feedback">Šalji povratne informacije</string>
|
||||
<!--Link Warning-->
|
||||
<string name="link_warning_title">Link upozorenje</string>
|
||||
<string name="link_warning_intro">Upravo ćete otvoriti slijedeći link sa eksternom aplikacijom</string>
|
||||
<string name="link_warning_text">Ovo može biti upotrijebljeno da vas identifikuju. Razmislite da li vjerujete osobi koja vam je poslala ovaj link i razmotrite da ga možda otvorite u Orfox-u.</string>
|
||||
<string name="link_warning_open_link">Otvori link</string>
|
||||
<!--Crash Reporter-->
|
||||
<string name="crash_report_title">Briar izvještaj po krahu</string>
|
||||
<string name="briar_crashed">Izvinite, Briar je krahirao.</string>
|
||||
<string name="not_your_fault">Nijeste vi krivi.</string>
|
||||
<string name="please_send_report">Molimo pomozite nam da napravimo bolji Briar tako što ćete nam poslati izvještaj o krahu.</string>
|
||||
<string name="report_is_encrypted">Obećavamo da je izvještaj kriptovan i sigurno poslat.</string>
|
||||
<string name="feedback_title">Povratna informacija</string>
|
||||
<string name="describe_crash">Opišite šta se desilo (opciono)</string>
|
||||
<string name="enter_feedback">Unesite vašu povratnu informaciju</string>
|
||||
<string name="optional_contact_email">Vaša email adres (opciono)</string>
|
||||
<string name="include_debug_report_crash">Uključite anonimne podatke o krahu</string>
|
||||
<string name="include_debug_report_feedback">Uključite anonimne podatke o ovom uređaju</string>
|
||||
<string name="could_not_load_report_data">Nije bilo moguće učitati podatke izvještaja</string>
|
||||
<string name="send_report">Pošalji izvještaj</string>
|
||||
<string name="close">Zatvori</string>
|
||||
<string name="dev_report_saved">Izvještaj sačuvan. Biće poslat slijedeći put kada se ulogujete u Briar.</string>
|
||||
<!--Sign Out-->
|
||||
<string name="progress_title_logout">Isključujete se iz Briara...</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">Detektovano je prekrivanje ekrana</string>
|
||||
<string name="screen_filter_body">Neka druga aplikacija crta interfejs preko Briara. Kako bi vas zaštitili, Briar neće reagovati na dodir kada druga aplikacija ispisuje preko njega.\n\nProbajte da ugasite slijedeće aplikacije kad koristite Briar:\n\n%1$s</string>
|
||||
</resources>
|
||||
@@ -98,24 +98,12 @@
|
||||
|
||||
<!-- Adding Contacts -->
|
||||
<string name="add_contact_title">Add a Contact</string>
|
||||
<string name="your_nickname">Choose the identity you want to use:</string>
|
||||
<string name="face_to_face">You must meet up with the person you want to add as a contact.\n\nThis will prevent anyone from impersonating you or reading your messages in future.</string>
|
||||
<string name="continue_button">Continue</string>
|
||||
<string name="your_invitation_code">Your invitation code is</string>
|
||||
<string name="enter_invitation_code">Please enter your contact\'s invitation code:</string>
|
||||
<string name="searching_format">Searching for contact with invitation code %06d\u2026</string>
|
||||
<string name="connection_failed">Connection failed</string>
|
||||
<string name="could_not_find_contact">Briar could not find your contact nearby</string>
|
||||
<string name="try_again_button">Try Again</string>
|
||||
<string name="connected_to_contact">Connected to contact</string>
|
||||
<string name="calculating_confirmation_code">Calculating confirmation code\u2026</string>
|
||||
<string name="your_confirmation_code">Your confirmation code is</string>
|
||||
<string name="enter_confirmation_code">Please enter your contact\'s confirmation code:</string>
|
||||
<string name="waiting_for_contact">Waiting for contact\u2026</string>
|
||||
<string name="waiting_for_contact_to_scan">Waiting for contact to scan and connect\u2026</string>
|
||||
<string name="exchanging_contact_details">Exchanging contact details\u2026</string>
|
||||
<string name="codes_do_not_match">Codes do not match</string>
|
||||
<string name="interfering">This could mean that someone is trying to interfere with your connection</string>
|
||||
<string name="contact_added_toast">Contact added: %s</string>
|
||||
<string name="contact_already_exists">Contact %s already exists</string>
|
||||
<string name="contact_exchange_failed">Contact exchange failed</string>
|
||||
@@ -357,6 +345,7 @@
|
||||
<string name="notify_sound_setting_default">Default ringtone</string>
|
||||
<string name="notify_sound_setting_disabled">None</string>
|
||||
<string name="choose_ringtone_title">Choose ringtone</string>
|
||||
<string name="cannot_load_ringtone">Cannot load ringtone</string>
|
||||
|
||||
<!-- Settings Feedback -->
|
||||
<string name="feedback_settings_title">Feedback</string>
|
||||
|
||||
@@ -21,6 +21,8 @@ public interface MessageTree<T extends MessageTree.MessageNode> {
|
||||
|
||||
Collection<T> depthFirstOrder();
|
||||
|
||||
boolean contains(MessageId m);
|
||||
|
||||
@NotNullByDefault
|
||||
interface MessageNode {
|
||||
|
||||
|
||||
@@ -14,21 +14,26 @@ import javax.annotation.concurrent.Immutable;
|
||||
@NotNullByDefault
|
||||
public class ForumPostReceivedEvent extends Event {
|
||||
|
||||
private final ForumPostHeader forumPostHeader;
|
||||
private final GroupId groupId;
|
||||
private final ForumPostHeader header;
|
||||
private final String body;
|
||||
|
||||
public ForumPostReceivedEvent(ForumPostHeader forumPostHeader,
|
||||
GroupId groupId) {
|
||||
|
||||
this.forumPostHeader = forumPostHeader;
|
||||
public ForumPostReceivedEvent(GroupId groupId, ForumPostHeader header,
|
||||
String body) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public ForumPostHeader getForumPostHeader() {
|
||||
return forumPostHeader;
|
||||
this.header = header;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public GroupId getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public ForumPostHeader getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,14 @@ public class GroupMessageAddedEvent extends Event {
|
||||
|
||||
private final GroupId groupId;
|
||||
private final GroupMessageHeader header;
|
||||
private final String body;
|
||||
private final boolean local;
|
||||
|
||||
public GroupMessageAddedEvent(GroupId groupId, GroupMessageHeader header,
|
||||
boolean local) {
|
||||
|
||||
String body, boolean local) {
|
||||
this.groupId = groupId;
|
||||
this.header = header;
|
||||
this.body = body;
|
||||
this.local = local;
|
||||
}
|
||||
|
||||
@@ -35,6 +36,10 @@ public class GroupMessageAddedEvent extends Event {
|
||||
return header;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public boolean isLocal() {
|
||||
return local;
|
||||
}
|
||||
|
||||
@@ -107,4 +107,8 @@ public class MessageTreeImpl<T extends MessageTree.MessageNode>
|
||||
return orderedList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(MessageId m) {
|
||||
return nodeMap.containsKey(m);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,12 +161,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener,
|
||||
@Override
|
||||
public void addFeed(String url) throws DbException, IOException {
|
||||
// fetch syndication feed to get its metadata
|
||||
SyndFeed f;
|
||||
try {
|
||||
f = fetchSyndFeed(url);
|
||||
} catch (FeedException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
SyndFeed f = fetchSyndFeed(url);
|
||||
|
||||
Feed feed = feedFactory.createFeed(url, f);
|
||||
|
||||
@@ -183,12 +178,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener,
|
||||
}
|
||||
|
||||
// fetch feed again and post entries
|
||||
Feed updatedFeed;
|
||||
try {
|
||||
updatedFeed = fetchFeed(feed);
|
||||
} catch (FeedException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
Feed updatedFeed = fetchFeed(feed);
|
||||
|
||||
// store feed again to also store last added entry
|
||||
txn = db.startTransaction(false);
|
||||
@@ -314,10 +304,6 @@ class FeedManagerImpl implements FeedManager, Client, EventListener,
|
||||
for (Feed feed : feeds) {
|
||||
try {
|
||||
newFeeds.add(fetchFeed(feed));
|
||||
} catch (FeedException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
newFeeds.add(feed);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
@@ -339,15 +325,14 @@ class FeedManagerImpl implements FeedManager, Client, EventListener,
|
||||
LOG.info("Done updating RSS feeds");
|
||||
}
|
||||
|
||||
private SyndFeed fetchSyndFeed(String url)
|
||||
throws FeedException, IOException {
|
||||
private SyndFeed fetchSyndFeed(String url) throws IOException {
|
||||
// fetch feed
|
||||
InputStream stream = getFeedInputStream(url);
|
||||
SyndFeed f = getSyndFeed(stream);
|
||||
stream.close();
|
||||
|
||||
if (f.getEntries().size() == 0)
|
||||
throw new FeedException("Feed has no entries");
|
||||
throw new IOException("Feed has no entries");
|
||||
|
||||
// clean title
|
||||
String title =
|
||||
@@ -371,8 +356,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener,
|
||||
return f;
|
||||
}
|
||||
|
||||
private Feed fetchFeed(Feed feed)
|
||||
throws FeedException, IOException, DbException {
|
||||
private Feed fetchFeed(Feed feed) throws IOException, DbException {
|
||||
// fetch and clean feed
|
||||
SyndFeed f = fetchSyndFeed(feed.getUrl());
|
||||
|
||||
@@ -402,14 +386,19 @@ class FeedManagerImpl implements FeedManager, Client, EventListener,
|
||||
throw new IOException("Empty response body");
|
||||
}
|
||||
|
||||
private SyndFeed getSyndFeed(InputStream stream)
|
||||
throws IOException, FeedException {
|
||||
private SyndFeed getSyndFeed(InputStream stream) throws IOException {
|
||||
|
||||
SyndFeedInput input = new SyndFeedInput();
|
||||
return input.build(new XmlReader(stream));
|
||||
try {
|
||||
return input.build(new XmlReader(stream));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IOException(e);
|
||||
} catch (FeedException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private long postFeedEntries(Feed feed, List<SyndEntry> entries)
|
||||
long postFeedEntries(Feed feed, List<SyndEntry> entries)
|
||||
throws DbException {
|
||||
|
||||
long lastEntryTime = feed.getLastEntryTime();
|
||||
@@ -512,25 +501,20 @@ class FeedManagerImpl implements FeedManager, Client, EventListener,
|
||||
return StringUtils.truncateUtf8(text, MAX_BLOG_POST_BODY_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* This Comparator assumes that SyndEntry returns a valid Date either for
|
||||
* getPublishedDate() or getUpdatedDate().
|
||||
*/
|
||||
private Comparator<SyndEntry> getEntryComparator() {
|
||||
return new Comparator<SyndEntry>() {
|
||||
@Override
|
||||
public int compare(SyndEntry e1, SyndEntry e2) {
|
||||
if (e1.getPublishedDate() == null &&
|
||||
e1.getUpdatedDate() == null) {
|
||||
// we will be ignoring such entries anyway
|
||||
return 0;
|
||||
}
|
||||
Date d1 =
|
||||
e1.getPublishedDate() != null ? e1.getPublishedDate() :
|
||||
e1.getUpdatedDate();
|
||||
Date d2 =
|
||||
e2.getPublishedDate() != null ? e2.getPublishedDate() :
|
||||
e2.getUpdatedDate();
|
||||
if (d1 == null && d2 == null) return 0;
|
||||
if (d1 == null) return -1;
|
||||
if (d2 == null) return 1;
|
||||
|
||||
if (d1.after(d2)) return 1;
|
||||
if (d1.before(d2)) return -1;
|
||||
return 0;
|
||||
|
||||
@@ -45,7 +45,6 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.identity.Author.Status.ANONYMOUS;
|
||||
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
|
||||
import static org.briarproject.briar.api.forum.ForumConstants.KEY_AUTHOR;
|
||||
import static org.briarproject.briar.api.forum.ForumConstants.KEY_ID;
|
||||
@@ -85,9 +84,10 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
||||
|
||||
messageTracker.trackIncomingMessage(txn, m);
|
||||
|
||||
ForumPostHeader post = getForumPostHeader(txn, m.getId(), meta);
|
||||
ForumPostHeader header = getForumPostHeader(txn, m.getId(), meta);
|
||||
String postBody = getPostBody(body);
|
||||
ForumPostReceivedEvent event =
|
||||
new ForumPostReceivedEvent(post, m.getGroupId());
|
||||
new ForumPostReceivedEvent(m.getGroupId(), header, postBody);
|
||||
txn.attach(event);
|
||||
|
||||
// share message
|
||||
@@ -215,14 +215,19 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
||||
public String getPostBody(MessageId m) throws DbException {
|
||||
try {
|
||||
// Parent ID, author, forum post body, signature
|
||||
BdfList message = clientHelper.getMessageAsList(m);
|
||||
if (message == null) throw new DbException();
|
||||
return message.getString(2);
|
||||
BdfList body = clientHelper.getMessageAsList(m);
|
||||
if (body == null) throw new DbException();
|
||||
return getPostBody(body);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getPostBody(BdfList body) throws FormatException {
|
||||
// Parent ID, author, forum post body, signature
|
||||
return body.getString(2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ForumPostHeader> getPostHeaders(GroupId g)
|
||||
throws DbException {
|
||||
@@ -294,24 +299,17 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
||||
throws DbException, FormatException {
|
||||
|
||||
long timestamp = meta.getLong(KEY_TIMESTAMP);
|
||||
Author author = null;
|
||||
Status status = ANONYMOUS;
|
||||
MessageId parentId = null;
|
||||
if (meta.containsKey(KEY_PARENT))
|
||||
parentId = new MessageId(meta.getRaw(KEY_PARENT));
|
||||
// TODO: Remove support for anonymous forum posts
|
||||
BdfDictionary d1 = meta.getDictionary(KEY_AUTHOR, null);
|
||||
if (d1 != null) {
|
||||
AuthorId authorId = new AuthorId(d1.getRaw(KEY_ID));
|
||||
String name = d1.getString(KEY_NAME);
|
||||
byte[] publicKey = d1.getRaw(KEY_PUBLIC_NAME);
|
||||
author = new Author(authorId, name, publicKey);
|
||||
if (statuses.containsKey(authorId)) {
|
||||
status = statuses.get(authorId);
|
||||
} else {
|
||||
status = identityManager.getAuthorStatus(txn, author.getId());
|
||||
}
|
||||
}
|
||||
BdfDictionary authorDict = meta.getDictionary(KEY_AUTHOR);
|
||||
AuthorId authorId = new AuthorId(authorDict.getRaw(KEY_ID));
|
||||
String name = authorDict.getString(KEY_NAME);
|
||||
byte[] publicKey = authorDict.getRaw(KEY_PUBLIC_NAME);
|
||||
Author author = new Author(authorId, name, publicKey);
|
||||
Status status = statuses.get(authorId);
|
||||
if (status == null)
|
||||
status = identityManager.getAuthorStatus(txn, author.getId());
|
||||
boolean read = meta.getBoolean(MSG_KEY_READ);
|
||||
|
||||
return new ForumPostHeader(id, parentId, timestamp, author, status,
|
||||
|
||||
@@ -307,16 +307,20 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
||||
@Override
|
||||
public String getMessageBody(MessageId m) throws DbException {
|
||||
try {
|
||||
// type(0), member_name(1), member_public_key(2), parent_id(3),
|
||||
// previous_message_id(4), content(5), signature(6)
|
||||
BdfList body = clientHelper.getMessageAsList(m);
|
||||
if (body == null) throw new DbException();
|
||||
return body.getString(5);
|
||||
return getMessageBody(body);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getMessageBody(BdfList body) throws FormatException {
|
||||
// type(0), member_name(1), member_public_key(2), parent_id(3),
|
||||
// previous_message_id(4), content(5), signature(6)
|
||||
return body.getString(5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<GroupMessageHeader> getHeaders(GroupId g)
|
||||
throws DbException {
|
||||
@@ -579,21 +583,20 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
||||
private void attachGroupMessageAddedEvent(Transaction txn, Message m,
|
||||
BdfDictionary meta, boolean local)
|
||||
throws DbException, FormatException {
|
||||
GroupMessageHeader h =
|
||||
getGroupMessageHeader(txn, m.getGroupId(), m.getId(), meta,
|
||||
Collections.<AuthorId, Status>emptyMap());
|
||||
Event e = new GroupMessageAddedEvent(m.getGroupId(), h, local);
|
||||
txn.attach(e);
|
||||
GroupMessageHeader header = getGroupMessageHeader(txn, m.getGroupId(),
|
||||
m.getId(), meta, Collections.<AuthorId, Status>emptyMap());
|
||||
String body = getMessageBody(clientHelper.toList(m));
|
||||
txn.attach(new GroupMessageAddedEvent(m.getGroupId(), header, body,
|
||||
local));
|
||||
}
|
||||
|
||||
private void attachJoinMessageAddedEvent(Transaction txn, Message m,
|
||||
BdfDictionary meta, boolean local, Visibility v)
|
||||
throws DbException, FormatException {
|
||||
JoinMessageHeader h =
|
||||
getJoinMessageHeader(txn, m.getGroupId(), m.getId(), meta,
|
||||
Collections.<AuthorId, Status>emptyMap(), v);
|
||||
Event e = new GroupMessageAddedEvent(m.getGroupId(), h, local);
|
||||
txn.attach(e);
|
||||
JoinMessageHeader header = getJoinMessageHeader(txn, m.getGroupId(),
|
||||
m.getId(), meta, Collections.<AuthorId, Status>emptyMap(), v);
|
||||
txn.attach(new GroupMessageAddedEvent(m.getGroupId(), header, "",
|
||||
local));
|
||||
}
|
||||
|
||||
private void addMember(Transaction txn, GroupId g, Author a, Visibility v)
|
||||
|
||||
@@ -238,7 +238,8 @@ abstract class SharingManagerImpl<S extends Shareable>
|
||||
try {
|
||||
Contact contact = db.getContact(txn, contactId);
|
||||
if (!canBeShared(txn, shareableId, contact))
|
||||
throw new IllegalArgumentException();
|
||||
// we might have received an invitation in the meantime
|
||||
return;
|
||||
// Look up the session, if there is one
|
||||
GroupId contactGroupId = getContactGroup(contact).getId();
|
||||
StoredSession ss = getSession(txn, contactGroupId, sessionId);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package org.briarproject.briar.feed;
|
||||
|
||||
import com.rometools.rome.feed.synd.SyndEntry;
|
||||
import com.rometools.rome.feed.synd.SyndEntryImpl;
|
||||
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
@@ -11,17 +14,23 @@ import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||
import org.briarproject.briar.api.blog.Blog;
|
||||
import org.briarproject.briar.api.blog.BlogManager;
|
||||
import org.briarproject.briar.api.blog.BlogPost;
|
||||
import org.briarproject.briar.api.blog.BlogPostFactory;
|
||||
import org.briarproject.briar.api.feed.Feed;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
@@ -93,6 +102,36 @@ public class FeedManagerImplTest extends BrambleMockTestCase {
|
||||
feedManager.fetchFeeds();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPostFeedEntriesEmptyDate() throws Exception {
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
List<SyndEntry> entries = new ArrayList<SyndEntry>();
|
||||
entries.add(new SyndEntryImpl());
|
||||
final SyndEntry entry = new SyndEntryImpl();
|
||||
entry.setUpdatedDate(new Date());
|
||||
entries.add(entry);
|
||||
final String body =
|
||||
"<p> (" + entry.getUpdatedDate().toString() + ")</p>";
|
||||
Message msg = new Message(new MessageId(getRandomId()), blogGroupId, 0,
|
||||
getRandomBytes(42));
|
||||
final BlogPost post = new BlogPost(msg, null, localAuthor);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(42L));
|
||||
oneOf(blogPostFactory)
|
||||
.createBlogPost(feed.getBlogId(), 42L, null, localAuthor,
|
||||
body);
|
||||
will(returnValue(post));
|
||||
oneOf(blogManager).addLocalPost(txn, post);
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
feedManager.postFeedEntries(feed, entries);
|
||||
}
|
||||
|
||||
private void expectGetLocalGroup() {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID);
|
||||
|
||||
@@ -96,7 +96,7 @@ public class BlogSharingIntegrationTest
|
||||
injectEagerSingletons(c2);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test
|
||||
public void testPersonalBlogCannotBeSharedWithOwner() throws Exception {
|
||||
listenToEvents(true);
|
||||
|
||||
@@ -108,11 +108,6 @@ public class BlogSharingIntegrationTest
|
||||
contact0From1));
|
||||
assertFalse(blogSharingManager2.canBeShared(blog0.getId(),
|
||||
contact0From2));
|
||||
|
||||
// create invitation
|
||||
blogSharingManager0
|
||||
.sendInvitation(blog1.getId(), contactId1From0, "Hi!",
|
||||
clock.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.test.TestDatabaseModule;
|
||||
@@ -379,7 +380,7 @@ public class ForumSharingIntegrationTest
|
||||
assertEquals(1, forumManager1.getForums().size());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test
|
||||
public void testSharingSameForumWithEachOther() throws Exception {
|
||||
// initialize and let invitee accept all requests
|
||||
listenToEvents(true);
|
||||
@@ -399,6 +400,13 @@ public class ForumSharingIntegrationTest
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.responseReceived);
|
||||
|
||||
// response and invitation got tracked
|
||||
Group group = contactGroupFactory
|
||||
.createContactGroup(ForumSharingManager.CLIENT_ID,
|
||||
contact0From1);
|
||||
assertEquals(2, c1.getMessageTracker().getGroupCount(group.getId())
|
||||
.getMsgCount());
|
||||
|
||||
// forum was added successfully
|
||||
assertEquals(1, forumManager1.getForums().size());
|
||||
|
||||
@@ -407,6 +415,40 @@ public class ForumSharingIntegrationTest
|
||||
contactId0From1,
|
||||
"I am re-sharing this forum with you.",
|
||||
clock.currentTimeMillis());
|
||||
|
||||
// assert that the last invitation wasn't send
|
||||
assertEquals(2, c1.getMessageTracker().getGroupCount(group.getId())
|
||||
.getMsgCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharingSameForumWithEachOtherBeforeAccept()
|
||||
throws Exception {
|
||||
// send invitation
|
||||
forumSharingManager0
|
||||
.sendInvitation(forum0.getId(), contactId1From0, "Hi!",
|
||||
clock.currentTimeMillis());
|
||||
sync0To1(1, true);
|
||||
|
||||
// ensure that invitee has received the invitations
|
||||
assertEquals(1, forumSharingManager1.getInvitations().size());
|
||||
|
||||
// assert that the invitation arrived
|
||||
Group group = contactGroupFactory
|
||||
.createContactGroup(ForumSharingManager.CLIENT_ID,
|
||||
contact0From1);
|
||||
assertEquals(1, c1.getMessageTracker().getGroupCount(group.getId())
|
||||
.getMsgCount());
|
||||
|
||||
// invitee now shares same forum back
|
||||
forumSharingManager1.sendInvitation(forum0.getId(),
|
||||
contactId0From1,
|
||||
"I am re-sharing this forum with you.",
|
||||
clock.currentTimeMillis());
|
||||
|
||||
// assert that the last invitation wasn't send
|
||||
assertEquals(1, c1.getMessageTracker().getGroupCount(group.getId())
|
||||
.getMsgCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user