mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
2 Commits
beta-1.2.4
...
debugging-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
464dcf8742 | ||
|
|
09af494d5c |
80
.idea/codeStyles/Project.xml
generated
80
.idea/codeStyles/Project.xml
generated
@@ -1,7 +1,16 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="RIGHT_MARGIN" value="100" />
|
||||
<AndroidXmlCodeStyleSettings>
|
||||
<option name="USE_CUSTOM_SETTINGS" value="true" />
|
||||
</AndroidXmlCodeStyleSettings>
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="ANNOTATION_PARAMETER_WRAP" value="1" />
|
||||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
||||
<value />
|
||||
</option>
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="android" withSubpackages="true" static="false" />
|
||||
@@ -68,6 +77,7 @@
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
@@ -80,8 +90,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
<XML_NAMESPACE>Namespace:</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
@@ -91,8 +100,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
<XML_NAMESPACE>Namespace:</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
@@ -103,7 +111,6 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@@ -114,7 +121,6 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@@ -125,7 +131,6 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@@ -136,7 +141,6 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@@ -147,7 +151,6 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@@ -158,12 +161,64 @@
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<NAME>.*:layout_width</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_height</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_.*</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:width</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:height</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
@@ -171,7 +226,6 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
|
||||
@@ -9,10 +9,10 @@ android {
|
||||
buildToolsVersion '28.0.3'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
versionCode 10204
|
||||
versionName "1.2.4"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 26
|
||||
versionCode 10107
|
||||
versionName "1.1.7"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
@@ -30,8 +30,8 @@ configurations {
|
||||
|
||||
dependencies {
|
||||
implementation project(path: ':bramble-core', configuration: 'default')
|
||||
tor 'org.briarproject:tor-android:0.3.5.8-64@zip'
|
||||
tor 'org.briarproject:obfs4proxy-android:0.0.11-2@zip'
|
||||
tor 'org.briarproject:tor-android:0.3.5.8@zip'
|
||||
tor 'org.briarproject:obfs4proxy-android:0.0.9@zip'
|
||||
|
||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'
|
||||
|
||||
@@ -59,8 +59,6 @@ task unpackTorBinaries {
|
||||
copy {
|
||||
from configurations.tor.collect { zipTree(it) }
|
||||
into torBinariesDir
|
||||
// TODO: Remove after next Tor upgrade, which won't include non-PIE binaries
|
||||
include 'geoip.zip', '*_pie.zip'
|
||||
}
|
||||
}
|
||||
dependsOn cleanTorBinaries
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
@@ -88,15 +89,9 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
|
||||
// Check that we have a Tor binary for this architecture
|
||||
String architecture = null;
|
||||
for (String abi : AndroidUtils.getSupportedArchitectures()) {
|
||||
if (abi.startsWith("x86_64")) {
|
||||
architecture = "x86_64";
|
||||
break;
|
||||
} else if (abi.startsWith("x86")) {
|
||||
if (abi.startsWith("x86")) {
|
||||
architecture = "x86";
|
||||
break;
|
||||
} else if (abi.startsWith("arm64")) {
|
||||
architecture = "arm64";
|
||||
break;
|
||||
} else if (abi.startsWith("armeabi")) {
|
||||
architecture = "arm";
|
||||
break;
|
||||
@@ -106,8 +101,8 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
|
||||
LOG.info("Tor is not supported on this architecture");
|
||||
return null;
|
||||
}
|
||||
// Use position-independent executable
|
||||
architecture += "_pie";
|
||||
// Use position-independent executable for SDK >= 16
|
||||
if (Build.VERSION.SDK_INT >= 16) architecture += "_pie";
|
||||
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
|
||||
@@ -23,7 +23,6 @@ import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.content.Context.WIFI_SERVICE;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.provider.Settings.Secure.ANDROID_ID;
|
||||
|
||||
@Immutable
|
||||
@@ -75,7 +74,8 @@ class AndroidSecureRandomProvider extends UnixSecureRandomProvider {
|
||||
// Silence strict mode
|
||||
StrictMode.ThreadPolicy tp = StrictMode.allowThreadDiskWrites();
|
||||
super.writeSeed();
|
||||
if (SDK_INT <= 18) applyOpenSslFix();
|
||||
if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT <= 18)
|
||||
applyOpenSslFix();
|
||||
StrictMode.setThreadPolicy(tp);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,8 +66,8 @@ dependencyVerification {
|
||||
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
|
||||
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
|
||||
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
|
||||
'org.briarproject:obfs4proxy-android:0.0.11-2:obfs4proxy-android-0.0.11-2.zip:57e55cbe87aa2aac210fdbb6cd8cdeafe15f825406a08ebf77a8b787aa2c6a8a',
|
||||
'org.briarproject:tor-android:0.3.5.8-64:tor-android-0.3.5.8-64.zip:9f144088c0fe845d1cf3232cdc2b51c68e6f9a22660592009f43a5633fca8824',
|
||||
'org.briarproject:obfs4proxy-android:0.0.9:obfs4proxy-android-0.0.9.zip:9b7e9181535ea8d8bbe8ae6338e08cf4c5fc1e357a779393e0ce49586d459ae0',
|
||||
'org.briarproject:tor-android:0.3.5.8:tor-android-0.3.5.8.zip:42a13a6f185be1a62f42e3f30ce66a3c099ac5ec890a65e7593111b65b44a54a',
|
||||
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
||||
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
||||
'org.codehaus.groovy:groovy-all:2.4.15:groovy-all-2.4.15.jar:51d6c4e71782e85674239189499854359d380fb75e1a703756e3aaa5b98a5af0',
|
||||
|
||||
@@ -7,5 +7,5 @@ public interface FeatureFlags {
|
||||
|
||||
boolean shouldEnableImageAttachments();
|
||||
|
||||
boolean shouldEnablePrivateMessageDeletion();
|
||||
boolean shouldEnableRemoteContacts();
|
||||
}
|
||||
|
||||
@@ -4,10 +4,8 @@ import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.Pair;
|
||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.ContactExistsException;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||
import org.briarproject.bramble.api.db.PendingContactExistsException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
@@ -119,14 +117,9 @@ public interface ContactManager {
|
||||
* @throws FormatException If the link is invalid
|
||||
* @throws GeneralSecurityException If the pending contact's handshake
|
||||
* public key is invalid
|
||||
* @throws ContactExistsException If a contact with the same handshake
|
||||
* public key already exists
|
||||
* @throws PendingContactExistsException If a pending contact with the same
|
||||
* handshake public key already exists
|
||||
*/
|
||||
PendingContact addPendingContact(String link, String alias)
|
||||
throws DbException, FormatException, GeneralSecurityException,
|
||||
ContactExistsException, PendingContactExistsException;
|
||||
throws DbException, FormatException, GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Returns the pending contact with the given ID.
|
||||
|
||||
@@ -83,7 +83,7 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
/**
|
||||
* Stores a pending contact.
|
||||
*/
|
||||
void addPendingContact(Transaction txn, PendingContact p, AuthorId local)
|
||||
void addPendingContact(Transaction txn, PendingContact p)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
package org.briarproject.bramble.api.db;
|
||||
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
|
||||
/**
|
||||
* Thrown when a duplicate pending contact is added to the database. This
|
||||
* exception may occur due to concurrent updates and does not indicate a
|
||||
* database error.
|
||||
*/
|
||||
public class PendingContactExistsException extends DbException {
|
||||
|
||||
private final PendingContact pendingContact;
|
||||
|
||||
public PendingContactExistsException(PendingContact pendingContact) {
|
||||
this.pendingContact = pendingContact;
|
||||
}
|
||||
|
||||
public PendingContact getPendingContact() {
|
||||
return pendingContact;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,8 +139,7 @@ class ContactManagerImpl implements ContactManager, EventListener {
|
||||
pendingContactFactory.createPendingContact(link, alias);
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
AuthorId local = identityManager.getLocalAuthor(txn).getId();
|
||||
db.addPendingContact(txn, p, local);
|
||||
db.addPendingContact(txn, p);
|
||||
KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn);
|
||||
keyManager.addPendingContact(txn, p.getId(), p.getPublicKey(),
|
||||
ourKeyPair);
|
||||
|
||||
@@ -267,16 +267,6 @@ interface Database<T> {
|
||||
*/
|
||||
Collection<ContactId> getContacts(T txn, AuthorId local) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the contact with the given {@code handshakePublicKey}
|
||||
* for the given local pseudonym or {@code null} if none exists.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@Nullable
|
||||
Contact getContact(T txn, PublicKey handshakePublicKey, AuthorId local)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the group with the given ID.
|
||||
* <p/>
|
||||
|
||||
@@ -291,17 +291,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPendingContact(Transaction transaction, PendingContact p,
|
||||
AuthorId local) throws DbException {
|
||||
public void addPendingContact(Transaction transaction, PendingContact p)
|
||||
throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
Contact contact = db.getContact(txn, p.getPublicKey(), local);
|
||||
if (contact != null)
|
||||
throw new ContactExistsException(local, contact.getAuthor());
|
||||
if (db.containsPendingContact(txn, p.getId())) {
|
||||
PendingContact existing = db.getPendingContact(txn, p.getId());
|
||||
throw new PendingContactExistsException(existing);
|
||||
}
|
||||
if (db.containsPendingContact(txn, p.getId()))
|
||||
throw new PendingContactExistsException();
|
||||
db.addPendingContact(txn, p);
|
||||
transaction.attach(new PendingContactAddedEvent(p));
|
||||
}
|
||||
|
||||
@@ -1465,47 +1465,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Contact getContact(Connection txn, PublicKey handshakePublicKey,
|
||||
AuthorId localAuthorId) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT contactId, authorId, formatVersion, name,"
|
||||
+ " alias, publicKey, verified"
|
||||
+ " FROM contacts"
|
||||
+ " WHERE handshakePublicKey = ? AND localAuthorId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, handshakePublicKey.getEncoded());
|
||||
ps.setBytes(2, localAuthorId.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) {
|
||||
rs.close();
|
||||
ps.close();
|
||||
return null;
|
||||
}
|
||||
ContactId contactId = new ContactId(rs.getInt(1));
|
||||
AuthorId authorId = new AuthorId(rs.getBytes(2));
|
||||
int formatVersion = rs.getInt(3);
|
||||
String name = rs.getString(4);
|
||||
String alias = rs.getString(5);
|
||||
PublicKey publicKey = new SignaturePublicKey(rs.getBytes(6));
|
||||
boolean verified = rs.getBoolean(7);
|
||||
if (rs.next()) throw new DbStateException();
|
||||
rs.close();
|
||||
ps.close();
|
||||
Author author =
|
||||
new Author(authorId, formatVersion, name, publicKey);
|
||||
return new Contact(contactId, author, localAuthorId, alias,
|
||||
handshakePublicKey, verified);
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs, LOG, WARNING);
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group getGroup(Connection txn, GroupId g) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
|
||||
@@ -195,6 +195,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
if (!assetsAreUpToDate()) installAssets();
|
||||
if (cookieFile.exists() && !cookieFile.delete())
|
||||
LOG.warning("Old auth cookie not deleted");
|
||||
// Migrate old settings before having a chance to stop
|
||||
migrateSettings();
|
||||
// Start a new Tor process
|
||||
LOG.info("Starting Tor");
|
||||
String torPath = torFile.getAbsolutePath();
|
||||
@@ -814,6 +816,21 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
|
||||
}
|
||||
|
||||
// TODO remove when sufficient time has passed. Added 2018-08-15
|
||||
private void migrateSettings() {
|
||||
Settings sOld = callback.getSettings();
|
||||
int oldNetwork = sOld.getInt("network", -1);
|
||||
if (oldNetwork == -1) return;
|
||||
Settings s = new Settings();
|
||||
if (oldNetwork == 0) {
|
||||
s.putInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_NEVER);
|
||||
} else if (oldNetwork == 1) {
|
||||
s.putBoolean(PREF_TOR_MOBILE, false);
|
||||
}
|
||||
s.putInt("network", -1);
|
||||
callback.mergeSettings(s);
|
||||
}
|
||||
|
||||
private static class ConnectionStatus {
|
||||
|
||||
// All of the following are locking: this
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.SettableClock;
|
||||
import org.briarproject.bramble.test.TestDatabaseConfig;
|
||||
import org.briarproject.bramble.test.TestMessageFactory;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -1150,43 +1149,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetContactsByHandshakePublicKey() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add an identity for a local author - no contacts should be
|
||||
// associated
|
||||
db.addIdentity(txn, identity);
|
||||
PublicKey handshakePublicKey = TestUtils.getSignaturePublicKey();
|
||||
Contact contact =
|
||||
db.getContact(txn, handshakePublicKey, localAuthor.getId());
|
||||
assertNull(contact);
|
||||
|
||||
// Add a contact associated with the local author
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
handshakePublicKey, true));
|
||||
contact = db.getContact(txn, handshakePublicKey, localAuthor.getId());
|
||||
assertNotNull(contact);
|
||||
assertEquals(contactId, contact.getId());
|
||||
assertEquals(author, contact.getAuthor());
|
||||
assertNull(contact.getAlias());
|
||||
assertEquals(handshakePublicKey, contact.getHandshakePublicKey());
|
||||
assertTrue(contact.isVerified());
|
||||
assertEquals(author.getName(), contact.getAuthor().getName());
|
||||
assertEquals(author.getPublicKey(), contact.getAuthor().getPublicKey());
|
||||
assertEquals(author.getFormatVersion(),
|
||||
contact.getAuthor().getFormatVersion());
|
||||
|
||||
// Ensure no contacts are returned after contact was deleted
|
||||
db.removeContact(txn, contactId);
|
||||
contact = db.getContact(txn, handshakePublicKey, localAuthor.getId());
|
||||
assertNull(contact);
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOfferedMessages() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
|
||||
@@ -26,7 +26,7 @@ public class BrambleCoreIntegrationTestModule {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnablePrivateMessageDeletion() {
|
||||
public boolean shouldEnableRemoteContacts() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = pt_BR: pt-rBR, nb_NO: nb, zh-Hans: zh-rCN, zh-Hant: zh-rTW
|
||||
lang_map = pt_BR: pt-rBR, nb_NO: nb, zh-Hans: zh-rCN
|
||||
|
||||
[briar.stringsxml-5]
|
||||
file_filter = src/main/res/values-<lang>/strings.xml
|
||||
|
||||
@@ -20,10 +20,10 @@ android {
|
||||
buildToolsVersion '28.0.3'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
versionCode 10204
|
||||
versionName "1.2.4"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 26
|
||||
versionCode 10107
|
||||
versionName "1.1.7"
|
||||
applicationId "org.briarproject.briar.android"
|
||||
buildConfigField "String", "GitHash",
|
||||
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
|
||||
@@ -117,7 +117,7 @@ dependencies {
|
||||
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
||||
implementation 'com.google.zxing:core:3.3.3'
|
||||
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.14.0'
|
||||
implementation 'com.vanniktech:emoji-google:0.6.0' // later versions already use androidx
|
||||
implementation 'com.vanniktech:emoji-google:0.5.1'
|
||||
implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.1' // later versions already use androidx
|
||||
def glideVersion = '4.9.0'
|
||||
implementation("com.github.bumptech.glide:glide:$glideVersion") {
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
|
||||
# QR codes
|
||||
-keep class com.google.zxing.Result
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
|
||||
# RSS libraries
|
||||
-keep,includedescriptorclasses class com.rometools.rome.feed.synd.impl.** { *; }
|
||||
|
||||
@@ -4,7 +4,6 @@ import org.briarproject.bramble.BrambleAndroidModule;
|
||||
import org.briarproject.bramble.BrambleCoreModule;
|
||||
import org.briarproject.bramble.account.BriarAccountModule;
|
||||
import org.briarproject.briar.BriarCoreModule;
|
||||
import org.briarproject.briar.android.attachment.AttachmentModule;
|
||||
import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
@@ -14,7 +13,6 @@ import dagger.Component;
|
||||
@Singleton
|
||||
@Component(modules = {
|
||||
AppModule.class,
|
||||
AttachmentModule.class,
|
||||
BriarCoreModule.class,
|
||||
BrambleAndroidModule.class,
|
||||
BriarAccountModule.class,
|
||||
|
||||
@@ -47,10 +47,8 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
);
|
||||
private final MessageId msgId = new MessageId(getRandomId());
|
||||
|
||||
private final ImageHelper imageHelper = new ImageHelperImpl();
|
||||
private final AttachmentRetriever retriever =
|
||||
new AttachmentRetrieverImpl(null, dimensions, imageHelper,
|
||||
new ImageSizeCalculator(imageHelper));
|
||||
new AttachmentRetriever(null, dimensions);
|
||||
|
||||
@Test
|
||||
public void testSmallJpegImage() throws Exception {
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
android:label="@string/crash_report_title"
|
||||
android:launchMode="singleInstance"
|
||||
android:theme="@style/BriarTheme.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden">
|
||||
android:windowSoftInputMode="stateHidden">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
@@ -89,7 +89,7 @@
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.account.SetupActivity"
|
||||
android:label="@string/setup_title"
|
||||
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
@@ -126,7 +126,7 @@
|
||||
android:label="@string/app_name"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:theme="@style/BriarTheme.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize|stateUnchanged">
|
||||
android:windowSoftInputMode="stateHidden|adjustResize">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
|
||||
@@ -145,7 +145,7 @@
|
||||
android:name="org.briarproject.briar.android.privategroup.creation.CreateGroupActivity"
|
||||
android:label="@string/groups_create_group_title"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
|
||||
@@ -174,7 +174,8 @@
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity"
|
||||
android:label="@string/groups_member_list"
|
||||
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity">
|
||||
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity"/>
|
||||
@@ -183,7 +184,8 @@
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity"
|
||||
android:label="@string/groups_reveal_contacts"
|
||||
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity">
|
||||
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity"
|
||||
android:windowSoftInputMode="adjustResize|stateAlwaysHidden">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity"/>
|
||||
@@ -221,7 +223,7 @@
|
||||
android:name="org.briarproject.briar.android.forum.CreateForumActivity"
|
||||
android:label="@string/create_forum_title"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
|
||||
@@ -290,7 +292,7 @@
|
||||
android:name="org.briarproject.briar.android.blog.WriteBlogPostActivity"
|
||||
android:label="@string/blogs_write_blog_post"
|
||||
android:parentActivityName="org.briarproject.briar.android.blog.BlogActivity"
|
||||
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
|
||||
android:windowSoftInputMode="stateVisible|adjustResize">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.blog.BlogActivity"/>
|
||||
@@ -300,7 +302,7 @@
|
||||
android:name="org.briarproject.briar.android.blog.ReblogActivity"
|
||||
android:label="@string/blogs_reblog_button"
|
||||
android:parentActivityName="org.briarproject.briar.android.blog.BlogActivity"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden">
|
||||
android:windowSoftInputMode="stateHidden">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.blog.BlogActivity"/>
|
||||
@@ -310,7 +312,7 @@
|
||||
android:name="org.briarproject.briar.android.blog.RssFeedImportActivity"
|
||||
android:label="@string/blogs_rss_feeds_import"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
|
||||
android:windowSoftInputMode="stateVisible|adjustResize">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
|
||||
@@ -339,7 +341,7 @@
|
||||
android:name="org.briarproject.briar.android.introduction.IntroductionActivity"
|
||||
android:label="@string/introduction_activity_title"
|
||||
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden">
|
||||
android:windowSoftInputMode="stateHidden|adjustResize">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.conversation.ConversationActivity"/>
|
||||
@@ -367,8 +369,7 @@
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.login.ChangePasswordActivity"
|
||||
android:label="@string/change_password"
|
||||
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity"
|
||||
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
|
||||
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.settings.SettingsActivity"/>
|
||||
@@ -423,7 +424,7 @@
|
||||
android:name=".android.contact.add.remote.AddContactActivity"
|
||||
android:label="@string/add_contact_remotely_title_case"
|
||||
android:theme="@style/BriarTheme"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden"/>
|
||||
android:windowSoftInputMode="stateHidden|adjustResize"/>
|
||||
|
||||
<activity
|
||||
android:name=".android.contact.add.remote.PendingContactListActivity"
|
||||
|
||||
@@ -30,7 +30,6 @@ import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
||||
import org.briarproject.briar.BriarCoreEagerSingletons;
|
||||
import org.briarproject.briar.BriarCoreModule;
|
||||
import org.briarproject.briar.android.attachment.AttachmentModule;
|
||||
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
|
||||
import org.briarproject.briar.android.login.SignInReminderReceiver;
|
||||
import org.briarproject.briar.android.reporting.BriarReportSender;
|
||||
@@ -69,8 +68,7 @@ import dagger.Component;
|
||||
BriarCoreModule.class,
|
||||
BrambleAndroidModule.class,
|
||||
BriarAccountModule.class,
|
||||
AppModule.class,
|
||||
AttachmentModule.class
|
||||
AppModule.class
|
||||
})
|
||||
public interface AndroidComponent
|
||||
extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,
|
||||
|
||||
@@ -347,10 +347,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
if (currentTime - lastSound > SOUND_DELAY) {
|
||||
boolean sound = settings.getBoolean(PREF_NOTIFY_SOUND, true);
|
||||
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
|
||||
if (sound && !StringUtils.isNullOrEmpty(ringtoneUri)) {
|
||||
Uri uri = Uri.parse(ringtoneUri);
|
||||
if (!"file".equals(uri.getScheme())) b.setSound(uri);
|
||||
}
|
||||
if (sound && !StringUtils.isNullOrEmpty(ringtoneUri))
|
||||
b.setSound(Uri.parse(ringtoneUri));
|
||||
b.setDefaults(getDefaults());
|
||||
lastSound = currentTime;
|
||||
}
|
||||
@@ -361,8 +359,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
int defaults = DEFAULT_LIGHTS;
|
||||
boolean sound = settings.getBoolean(PREF_NOTIFY_SOUND, true);
|
||||
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
|
||||
if (sound && (StringUtils.isNullOrEmpty(ringtoneUri) ||
|
||||
"file".equals(Uri.parse(ringtoneUri).getScheme())))
|
||||
if (sound && StringUtils.isNullOrEmpty(ringtoneUri))
|
||||
defaults |= DEFAULT_SOUND;
|
||||
if (settings.getBoolean(PREF_NOTIFY_VIBRATION, true))
|
||||
defaults |= DEFAULT_VIBRATE;
|
||||
|
||||
@@ -243,7 +243,7 @@ public class AppModule {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnablePrivateMessageDeletion() {
|
||||
public boolean shouldEnableRemoteContacts() {
|
||||
return IS_DEBUG_BUILD;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.briarproject.briar.android;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application.ActivityLifecycleCallbacks;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class BackgroundMonitor implements ActivityLifecycleCallbacks {
|
||||
|
||||
private final AtomicInteger foregroundActivities = new AtomicInteger(0);
|
||||
|
||||
boolean isRunningInBackground() {
|
||||
return foregroundActivities.get() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Activity a, @Nullable Bundle state) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStarted(Activity a) {
|
||||
foregroundActivities.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResumed(Activity a) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityPaused(Activity a) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStopped(Activity a) {
|
||||
foregroundActivities.decrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivitySaveInstanceState(Activity a,
|
||||
@Nullable Bundle outState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroyed(Activity a) {
|
||||
}
|
||||
}
|
||||
@@ -34,9 +34,9 @@ import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static java.util.logging.Level.FINE;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.acra.ReportField.ANDROID_VERSION;
|
||||
import static org.acra.ReportField.APP_VERSION_CODE;
|
||||
import static org.acra.ReportField.APP_VERSION_NAME;
|
||||
@@ -82,9 +82,10 @@ public class BriarApplicationImpl extends Application
|
||||
implements BriarApplication {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(BriarApplicationImpl.class.getName());
|
||||
Logger.getLogger(BriarApplicationImpl.class.getName());
|
||||
|
||||
private final CachingLogHandler logHandler = new CachingLogHandler();
|
||||
private final BackgroundMonitor backgroundMonitor = new BackgroundMonitor();
|
||||
|
||||
private AndroidComponent applicationComponent;
|
||||
private volatile SharedPreferences prefs;
|
||||
@@ -107,16 +108,12 @@ public class BriarApplicationImpl extends Application
|
||||
|
||||
if (IS_DEBUG_BUILD) enableStrictMode();
|
||||
|
||||
Logger rootLogger = getLogger("");
|
||||
Handler[] handlers = rootLogger.getHandlers();
|
||||
// Disable the Android logger for release builds
|
||||
for (Handler handler : handlers) rootLogger.removeHandler(handler);
|
||||
if (IS_DEBUG_BUILD || IS_BETA_BUILD) {
|
||||
// We can't set the level of the Android logger at runtime, so
|
||||
// raise records to the logger's default level
|
||||
rootLogger.addHandler(new LevelRaisingHandler(FINE, INFO));
|
||||
// Restore the default handlers after the level raising handler
|
||||
for (Handler handler : handlers) rootLogger.addHandler(handler);
|
||||
Logger rootLogger = Logger.getLogger("");
|
||||
if (!IS_DEBUG_BUILD && !IS_BETA_BUILD) {
|
||||
// Remove default log handlers so system log is not used
|
||||
for (Handler handler : rootLogger.getHandlers()) {
|
||||
rootLogger.removeHandler(handler);
|
||||
}
|
||||
}
|
||||
rootLogger.addHandler(logHandler);
|
||||
rootLogger.setLevel(IS_DEBUG_BUILD || IS_BETA_BUILD ? FINE : INFO);
|
||||
@@ -125,6 +122,9 @@ public class BriarApplicationImpl extends Application
|
||||
|
||||
applicationComponent = createApplicationComponent();
|
||||
EmojiManager.install(new GoogleEmojiProvider());
|
||||
|
||||
if (SDK_INT < 16)
|
||||
registerActivityLifecycleCallbacks(backgroundMonitor);
|
||||
}
|
||||
|
||||
protected AndroidComponent createApplicationComponent() {
|
||||
@@ -186,8 +186,12 @@ public class BriarApplicationImpl extends Application
|
||||
|
||||
@Override
|
||||
public boolean isRunningInBackground() {
|
||||
RunningAppProcessInfo info = new RunningAppProcessInfo();
|
||||
ActivityManager.getMyMemoryState(info);
|
||||
return (info.importance != IMPORTANCE_FOREGROUND);
|
||||
if (SDK_INT >= 16) {
|
||||
RunningAppProcessInfo info = new RunningAppProcessInfo();
|
||||
ActivityManager.getMyMemoryState(info);
|
||||
return (info.importance != IMPORTANCE_FOREGROUND);
|
||||
} else {
|
||||
return backgroundMonitor.isRunningInBackground();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,6 +238,8 @@ public class BriarService extends Service {
|
||||
} else if (level == TRIM_MEMORY_RUNNING_LOW) {
|
||||
LOG.info("Trim memory: running low");
|
||||
} else if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
|
||||
// This level may be received if SDK_INT < 16, although the
|
||||
// constant isn't declared until API level 16
|
||||
LOG.warning("Trim memory: running critically low");
|
||||
// If we're not in the foreground, clear the UI to save memory
|
||||
if (app.isRunningInBackground()) hideUi();
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
package org.briarproject.briar.android;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* Log handler that raises all records at or above a given source level to a
|
||||
* given destination level. This affects the level seen by subsequent handlers.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class LevelRaisingHandler extends Handler {
|
||||
|
||||
private final Level dest;
|
||||
private final int srcInt, destInt;
|
||||
|
||||
LevelRaisingHandler(Level src, Level dest) {
|
||||
this.dest = dest;
|
||||
srcInt = src.intValue();
|
||||
destInt = dest.intValue();
|
||||
if (srcInt > destInt) throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publish(LogRecord record) {
|
||||
int recordInt = record.getLevel().intValue();
|
||||
if (recordInt >= srcInt && recordInt < destInt) record.setLevel(dest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
||||
@@ -150,7 +150,7 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
|
||||
// Get permissions
|
||||
String[] requestedPermissions = packageInfo.requestedPermissions;
|
||||
if (requestedPermissions == null) return false;
|
||||
if (SDK_INT < 23) {
|
||||
if (SDK_INT >= 16 && SDK_INT < 23) {
|
||||
// Check whether the permission has been requested and granted
|
||||
int[] flags = packageInfo.requestedPermissionsFlags;
|
||||
for (int i = 0; i < requestedPermissions.length; i++) {
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.briar.android.account;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.TextInputEditText;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.text.Editable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -11,15 +10,18 @@ import android.widget.Button;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT;
|
||||
import static android.view.inputmethod.EditorInfo.IME_ACTION_NONE;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.util.StringUtils.toUtf8;
|
||||
import static org.briarproject.briar.android.util.UiUtils.setError;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showSoftKeyboard;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -62,6 +64,12 @@ public class AuthorNameFragment extends SetupFragment {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
showSoftKeyboard(authorNameInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHelpText() {
|
||||
return getString(R.string.setup_name_explanation);
|
||||
@@ -69,21 +77,20 @@ public class AuthorNameFragment extends SetupFragment {
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence authorName, int i, int i1, int i2) {
|
||||
int authorNameLength = toUtf8(authorName.toString().trim()).length;
|
||||
int authorNameLength = StringUtils.toUtf8(authorName.toString()).length;
|
||||
boolean error = authorNameLength > MAX_AUTHOR_NAME_LENGTH;
|
||||
setError(authorNameWrapper, getString(R.string.name_too_long), error);
|
||||
boolean enabled = authorNameLength > 0 && !error;
|
||||
authorNameInput
|
||||
.setImeOptions(enabled ? IME_ACTION_NEXT : IME_ACTION_NONE);
|
||||
authorNameInput.setOnEditorActionListener(enabled ? this : null);
|
||||
nextButton.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Editable text = authorNameInput.getText();
|
||||
if (text != null) {
|
||||
setupController.setAuthorName(text.toString().trim());
|
||||
setupController.showPasswordFragment();
|
||||
}
|
||||
setupController.setAuthorName(authorNameInput.getText().toString());
|
||||
setupController.showPasswordFragment();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ public class SetPasswordFragment extends SetupFragment {
|
||||
strengthMeter = v.findViewById(R.id.strength_meter);
|
||||
passwordEntryWrapper = v.findViewById(R.id.password_entry_wrapper);
|
||||
passwordEntry = v.findViewById(R.id.password_entry);
|
||||
passwordEntry.requestFocus();
|
||||
passwordConfirmationWrapper =
|
||||
v.findViewById(R.id.password_confirm_wrapper);
|
||||
passwordConfirmation = v.findViewById(R.id.password_confirm);
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.briar.android.activity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.app.Fragment;
|
||||
@@ -11,6 +12,7 @@ import android.support.v7.widget.Toolbar;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
@@ -42,10 +44,10 @@ import javax.inject.Inject;
|
||||
import static android.arch.lifecycle.Lifecycle.State.STARTED;
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
|
||||
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS;
|
||||
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
||||
|
||||
/**
|
||||
* Warning: Some activities don't extend {@link BaseActivity}.
|
||||
@@ -215,6 +217,17 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
});
|
||||
}
|
||||
|
||||
public void showSoftKeyboard(View view) {
|
||||
Object o = getSystemService(INPUT_METHOD_SERVICE);
|
||||
((InputMethodManager) o).showSoftInput(view, SHOW_IMPLICIT);
|
||||
}
|
||||
|
||||
public void hideSoftKeyboard(View view) {
|
||||
IBinder token = view.getWindowToken();
|
||||
Object o = getSystemService(INPUT_METHOD_SERVICE);
|
||||
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void handleDbException(DbException e) {
|
||||
supportFinishAfterTransition();
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package org.briarproject.briar.android.attachment;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory.Options;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
@@ -14,17 +12,11 @@ import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
import org.jsoup.UnsupportedMimeTypeException;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static android.graphics.Bitmap.CompressFormat.JPEG;
|
||||
import static android.graphics.BitmapFactory.decodeStream;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||
@@ -32,7 +24,6 @@ import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
|
||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
|
||||
|
||||
@NotNullByDefault
|
||||
class AttachmentCreationTask {
|
||||
@@ -40,11 +31,8 @@ class AttachmentCreationTask {
|
||||
private static Logger LOG =
|
||||
getLogger(AttachmentCreationTask.class.getName());
|
||||
|
||||
private static final int MAX_ATTACHMENT_DIMENSION = 1000;
|
||||
|
||||
private final MessagingManager messagingManager;
|
||||
private final ContentResolver contentResolver;
|
||||
private final ImageSizeCalculator imageSizeCalculator;
|
||||
private final GroupId groupId;
|
||||
private final Collection<Uri> uris;
|
||||
private final boolean needsSize;
|
||||
@@ -55,26 +43,24 @@ class AttachmentCreationTask {
|
||||
|
||||
AttachmentCreationTask(MessagingManager messagingManager,
|
||||
ContentResolver contentResolver,
|
||||
AttachmentCreator attachmentCreator,
|
||||
ImageSizeCalculator imageSizeCalculator,
|
||||
GroupId groupId, Collection<Uri> uris, boolean needsSize) {
|
||||
AttachmentCreator attachmentCreator, GroupId groupId,
|
||||
Collection<Uri> uris, boolean needsSize) {
|
||||
this.messagingManager = messagingManager;
|
||||
this.contentResolver = contentResolver;
|
||||
this.imageSizeCalculator = imageSizeCalculator;
|
||||
this.groupId = groupId;
|
||||
this.uris = uris;
|
||||
this.needsSize = needsSize;
|
||||
this.attachmentCreator = attachmentCreator;
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
public void cancel() {
|
||||
canceled = true;
|
||||
attachmentCreator = null;
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
void storeAttachments() {
|
||||
for (Uri uri : uris) processUri(uri);
|
||||
public void storeAttachments() {
|
||||
for (Uri uri: uris) processUri(uri);
|
||||
AttachmentCreator attachmentCreator = this.attachmentCreator;
|
||||
if (!canceled && attachmentCreator != null)
|
||||
attachmentCreator.onAttachmentCreationFinished();
|
||||
@@ -112,8 +98,6 @@ class AttachmentCreationTask {
|
||||
}
|
||||
InputStream is = contentResolver.openInputStream(uri);
|
||||
if (is == null) throw new IOException();
|
||||
is = compressImage(is, contentType);
|
||||
contentType = "image/jpeg";
|
||||
long timestamp = System.currentTimeMillis();
|
||||
AttachmentHeader h = messagingManager
|
||||
.addLocalAttachment(groupId, timestamp, contentType, is);
|
||||
@@ -129,48 +113,4 @@ class AttachmentCreationTask {
|
||||
return false;
|
||||
}
|
||||
|
||||
private InputStream compressImage(InputStream is, String contentType)
|
||||
throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
try {
|
||||
Bitmap bitmap = createBitmap(is, contentType);
|
||||
for (int quality = 100; quality >= 0; quality -= 10) {
|
||||
if (!bitmap.compress(JPEG, quality, out))
|
||||
throw new IOException();
|
||||
if (out.size() <= MAX_IMAGE_SIZE) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Compressed image to "
|
||||
+ out.size() + " bytes, quality " + quality);
|
||||
}
|
||||
return new ByteArrayInputStream(out.toByteArray());
|
||||
}
|
||||
out.reset();
|
||||
}
|
||||
throw new IOException();
|
||||
} finally {
|
||||
tryToClose(is, LOG, WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap createBitmap(InputStream is, String contentType)
|
||||
throws IOException {
|
||||
is = new BufferedInputStream(is);
|
||||
Size size = imageSizeCalculator.getSize(is, contentType);
|
||||
if (size.error) throw new IOException();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Original image size: " + size.width + "x" + size.height);
|
||||
int dimension = Math.max(size.width, size.height);
|
||||
int inSampleSize = 1;
|
||||
while (dimension > MAX_ATTACHMENT_DIMENSION) {
|
||||
inSampleSize *= 2;
|
||||
dimension /= 2;
|
||||
}
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Scaling attachment by factor of " + inSampleSize);
|
||||
Options options = new Options();
|
||||
options.inSampleSize = inSampleSize;
|
||||
Bitmap bitmap = decodeStream(is, null, options);
|
||||
if (bitmap == null) throw new IOException();
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,82 @@
|
||||
package org.briarproject.briar.android.attachment;
|
||||
|
||||
|
||||
import android.app.Application;
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.lifecycle.MutableLiveData;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.api.messaging.Attachment;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.messaging.FileTooBigException;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
import org.jsoup.UnsupportedMimeTypeException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface AttachmentCreator {
|
||||
public class AttachmentCreator {
|
||||
|
||||
private static Logger LOG = getLogger(AttachmentCreator.class.getName());
|
||||
|
||||
private final Application app;
|
||||
@IoExecutor
|
||||
private final Executor ioExecutor;
|
||||
private final MessagingManager messagingManager;
|
||||
private final AttachmentRetriever retriever;
|
||||
|
||||
private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>();
|
||||
private final CopyOnWriteArrayList<AttachmentItemResult> itemResults =
|
||||
new CopyOnWriteArrayList<>();
|
||||
|
||||
private final MutableLiveData<AttachmentResult> result =
|
||||
new MutableLiveData<>();
|
||||
@Nullable
|
||||
private AttachmentCreationTask task;
|
||||
|
||||
public AttachmentCreator(Application app, @IoExecutor Executor ioExecutor,
|
||||
MessagingManager messagingManager, AttachmentRetriever retriever) {
|
||||
this.app = app;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.messagingManager = messagingManager;
|
||||
this.retriever = retriever;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
LiveData<AttachmentResult> storeAttachments(LiveData<GroupId> groupId,
|
||||
Collection<Uri> newUris);
|
||||
public LiveData<AttachmentResult> storeAttachments(
|
||||
LiveData<GroupId> groupId, Collection<Uri> newUris) {
|
||||
if (task != null || !uris.isEmpty())
|
||||
throw new IllegalStateException();
|
||||
uris.addAll(newUris);
|
||||
observeForeverOnce(groupId, id -> {
|
||||
if (id == null) throw new IllegalStateException();
|
||||
boolean needsSize = uris.size() == 1;
|
||||
task = new AttachmentCreationTask(messagingManager,
|
||||
app.getContentResolver(), this, id, uris, needsSize);
|
||||
ioExecutor.execute(() -> task.storeAttachments());
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be only called after configuration changes.
|
||||
@@ -26,10 +84,68 @@ public interface AttachmentCreator {
|
||||
* They are already being created and returned by the existing LiveData.
|
||||
*/
|
||||
@UiThread
|
||||
LiveData<AttachmentResult> getLiveAttachments();
|
||||
public LiveData<AttachmentResult> getLiveAttachments() {
|
||||
if (task == null || uris.isEmpty())
|
||||
throw new IllegalStateException();
|
||||
// A task is already running. It will update the result LiveData.
|
||||
// So nothing more to do here.
|
||||
return result;
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
void onAttachmentHeaderReceived(Uri uri, AttachmentHeader h,
|
||||
boolean needsSize) {
|
||||
// get and cache AttachmentItem for ImagePreview
|
||||
try {
|
||||
Attachment a = retriever.getMessageAttachment(h);
|
||||
AttachmentItem item = retriever.getAttachmentItem(a, needsSize);
|
||||
if (item.hasError()) throw new IOException();
|
||||
AttachmentItemResult itemResult =
|
||||
new AttachmentItemResult(uri, item);
|
||||
itemResults.add(itemResult);
|
||||
result.postValue(getResult(false));
|
||||
} catch (IOException | DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
onAttachmentError(uri, e);
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
void onAttachmentError(Uri uri, Throwable t) {
|
||||
// get error message
|
||||
String errorMsg;
|
||||
if (t instanceof UnsupportedMimeTypeException) {
|
||||
String mimeType = ((UnsupportedMimeTypeException) t).getMimeType();
|
||||
errorMsg = app.getString(
|
||||
R.string.image_attach_error_invalid_mime_type, mimeType);
|
||||
} else if (t instanceof FileTooBigException) {
|
||||
int mb = MAX_IMAGE_SIZE / 1024 / 1024;
|
||||
errorMsg = app.getString(R.string.image_attach_error_too_big, mb);
|
||||
} else {
|
||||
errorMsg = null; // generic error
|
||||
}
|
||||
AttachmentItemResult itemResult =
|
||||
new AttachmentItemResult(uri, errorMsg);
|
||||
itemResults.add(itemResult);
|
||||
result.postValue(getResult(false));
|
||||
// expect to receive a cancel from the UI
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
void onAttachmentCreationFinished() {
|
||||
result.postValue(getResult(true));
|
||||
}
|
||||
|
||||
@UiThread
|
||||
List<AttachmentHeader> getAttachmentHeadersForSending();
|
||||
public List<AttachmentHeader> getAttachmentHeadersForSending() {
|
||||
List<AttachmentHeader> headers = new ArrayList<>(itemResults.size());
|
||||
for (AttachmentItemResult itemResult : itemResults) {
|
||||
// check if we are trying to send attachment items with errors
|
||||
if (itemResult.getItem() == null) throw new IllegalStateException();
|
||||
headers.add(itemResult.getItem().getHeader());
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the attachments as sent and adds the items to the cache for display
|
||||
@@ -37,24 +153,66 @@ public interface AttachmentCreator {
|
||||
* @param id The MessageId of the sent message.
|
||||
*/
|
||||
@UiThread
|
||||
void onAttachmentsSent(MessageId id);
|
||||
public void onAttachmentsSent(MessageId id) {
|
||||
List<AttachmentItem> items = new ArrayList<>(itemResults.size());
|
||||
for (AttachmentItemResult itemResult : itemResults) {
|
||||
// check if we are trying to send attachment items with errors
|
||||
if (itemResult.getItem() == null) throw new IllegalStateException();
|
||||
items.add(itemResult.getItem());
|
||||
}
|
||||
retriever.cachePut(id, items);
|
||||
resetState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Needs to be called when created attachments will not be sent anymore.
|
||||
*/
|
||||
@UiThread
|
||||
void cancel();
|
||||
public void cancel() {
|
||||
if (task == null) throw new AssertionError();
|
||||
task.cancel();
|
||||
deleteUnsentAttachments();
|
||||
resetState();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void deleteUnsentAttachments();
|
||||
private void resetState() {
|
||||
task = null;
|
||||
uris.clear();
|
||||
itemResults.clear();
|
||||
result.setValue(null);
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
void onAttachmentHeaderReceived(Uri uri, AttachmentHeader h,
|
||||
boolean needsSize);
|
||||
@UiThread
|
||||
public void deleteUnsentAttachments() {
|
||||
// Make a copy for the IoExecutor as we clear the itemResults soon
|
||||
List<AttachmentHeader> headers = new ArrayList<>(itemResults.size());
|
||||
for (AttachmentItemResult itemResult : itemResults) {
|
||||
// check if we are trying to send attachment items with errors
|
||||
if (itemResult.getItem() != null)
|
||||
headers.add(itemResult.getItem().getHeader());
|
||||
}
|
||||
ioExecutor.execute(() -> {
|
||||
for (AttachmentHeader header : headers) {
|
||||
try {
|
||||
messagingManager.removeAttachment(header);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
void onAttachmentError(Uri uri, Throwable t);
|
||||
private AttachmentResult getResult(boolean finished) {
|
||||
// Make a copy of the list,
|
||||
// because our copy will continue to change in the background.
|
||||
// (As it's a CopyOnWriteArrayList,
|
||||
// the code that receives the result can safely do simple things
|
||||
// like iterating over the list,
|
||||
// but anything that involves calling more than one list method
|
||||
// is still unsafe.)
|
||||
Collection<AttachmentItemResult> items = new ArrayList<>(itemResults);
|
||||
return new AttachmentResult(items, finished);
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
void onAttachmentCreationFinished();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
package org.briarproject.briar.android.attachment;
|
||||
|
||||
|
||||
import android.app.Application;
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.lifecycle.MutableLiveData;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.api.messaging.Attachment;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.messaging.FileTooBigException;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
import org.jsoup.UnsupportedMimeTypeException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
|
||||
|
||||
@NotNullByDefault
|
||||
class AttachmentCreatorImpl implements AttachmentCreator {
|
||||
|
||||
private static Logger LOG =
|
||||
getLogger(AttachmentCreatorImpl.class.getName());
|
||||
|
||||
private final Application app;
|
||||
@IoExecutor
|
||||
private final Executor ioExecutor;
|
||||
private final MessagingManager messagingManager;
|
||||
private final AttachmentRetriever retriever;
|
||||
private final ImageSizeCalculator imageSizeCalculator;
|
||||
|
||||
private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>();
|
||||
private final CopyOnWriteArrayList<AttachmentItemResult> itemResults =
|
||||
new CopyOnWriteArrayList<>();
|
||||
|
||||
@Nullable
|
||||
private AttachmentCreationTask task;
|
||||
|
||||
@Nullable
|
||||
private volatile MutableLiveData<AttachmentResult> result;
|
||||
|
||||
@Inject
|
||||
AttachmentCreatorImpl(Application app, @IoExecutor Executor ioExecutor,
|
||||
MessagingManager messagingManager, AttachmentRetriever retriever,
|
||||
ImageSizeCalculator imageSizeCalculator) {
|
||||
this.app = app;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.messagingManager = messagingManager;
|
||||
this.retriever = retriever;
|
||||
this.imageSizeCalculator = imageSizeCalculator;
|
||||
}
|
||||
|
||||
@Override
|
||||
@UiThread
|
||||
public LiveData<AttachmentResult> storeAttachments(
|
||||
LiveData<GroupId> groupId, Collection<Uri> newUris) {
|
||||
if (task != null || result != null || !uris.isEmpty())
|
||||
throw new IllegalStateException();
|
||||
MutableLiveData<AttachmentResult> result = new MutableLiveData<>();
|
||||
this.result = result;
|
||||
uris.addAll(newUris);
|
||||
observeForeverOnce(groupId, id -> {
|
||||
if (id == null) throw new IllegalStateException();
|
||||
boolean needsSize = uris.size() == 1;
|
||||
task = new AttachmentCreationTask(messagingManager,
|
||||
app.getContentResolver(), this, imageSizeCalculator, id,
|
||||
uris, needsSize);
|
||||
ioExecutor.execute(() -> task.storeAttachments());
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@UiThread
|
||||
public LiveData<AttachmentResult> getLiveAttachments() {
|
||||
MutableLiveData<AttachmentResult> result = this.result;
|
||||
if (task == null || result == null || uris.isEmpty())
|
||||
throw new IllegalStateException();
|
||||
// A task is already running. It will update the result LiveData.
|
||||
// So nothing more to do here.
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@IoExecutor
|
||||
public void onAttachmentHeaderReceived(Uri uri, AttachmentHeader h,
|
||||
boolean needsSize) {
|
||||
// get and cache AttachmentItem for ImagePreview
|
||||
try {
|
||||
Attachment a = retriever.getMessageAttachment(h);
|
||||
AttachmentItem item = retriever.getAttachmentItem(a, needsSize);
|
||||
if (item.hasError()) throw new IOException();
|
||||
AttachmentItemResult itemResult =
|
||||
new AttachmentItemResult(uri, item);
|
||||
itemResults.add(itemResult);
|
||||
MutableLiveData<AttachmentResult> result = this.result;
|
||||
if (result != null) result.postValue(getResult(false));
|
||||
} catch (IOException | DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
onAttachmentError(uri, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@IoExecutor
|
||||
public void onAttachmentError(Uri uri, Throwable t) {
|
||||
// get error message
|
||||
String errorMsg;
|
||||
if (t instanceof UnsupportedMimeTypeException) {
|
||||
String mimeType = ((UnsupportedMimeTypeException) t).getMimeType();
|
||||
errorMsg = app.getString(
|
||||
R.string.image_attach_error_invalid_mime_type, mimeType);
|
||||
} else if (t instanceof FileTooBigException) {
|
||||
int mb = MAX_IMAGE_SIZE / 1024 / 1024;
|
||||
errorMsg = app.getString(R.string.image_attach_error_too_big, mb);
|
||||
} else {
|
||||
errorMsg = null; // generic error
|
||||
}
|
||||
AttachmentItemResult itemResult =
|
||||
new AttachmentItemResult(uri, errorMsg);
|
||||
itemResults.add(itemResult);
|
||||
MutableLiveData<AttachmentResult> result = this.result;
|
||||
if (result != null) result.postValue(getResult(false));
|
||||
// expect to receive a cancel from the UI
|
||||
}
|
||||
|
||||
@Override
|
||||
@IoExecutor
|
||||
public void onAttachmentCreationFinished() {
|
||||
MutableLiveData<AttachmentResult> result = this.result;
|
||||
if (result != null) result.postValue(getResult(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@UiThread
|
||||
public List<AttachmentHeader> getAttachmentHeadersForSending() {
|
||||
List<AttachmentHeader> headers = new ArrayList<>(itemResults.size());
|
||||
for (AttachmentItemResult itemResult : itemResults) {
|
||||
// check if we are trying to send attachment items with errors
|
||||
if (itemResult.getItem() == null) throw new IllegalStateException();
|
||||
headers.add(itemResult.getItem().getHeader());
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
@UiThread
|
||||
public void onAttachmentsSent(MessageId id) {
|
||||
List<AttachmentItem> items = new ArrayList<>(itemResults.size());
|
||||
for (AttachmentItemResult itemResult : itemResults) {
|
||||
// check if we are trying to send attachment items with errors
|
||||
if (itemResult.getItem() == null) throw new IllegalStateException();
|
||||
items.add(itemResult.getItem());
|
||||
}
|
||||
retriever.cachePut(id, items);
|
||||
resetState();
|
||||
}
|
||||
|
||||
@Override
|
||||
@UiThread
|
||||
public void cancel() {
|
||||
if (task == null) throw new AssertionError();
|
||||
task.cancel();
|
||||
deleteUnsentAttachments();
|
||||
resetState();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void resetState() {
|
||||
task = null;
|
||||
uris.clear();
|
||||
itemResults.clear();
|
||||
MutableLiveData<AttachmentResult> result = this.result;
|
||||
if (result != null) {
|
||||
result.setValue(null);
|
||||
this.result = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@UiThread
|
||||
public void deleteUnsentAttachments() {
|
||||
// Make a copy for the IoExecutor as we clear the itemResults soon
|
||||
List<AttachmentHeader> headers = new ArrayList<>(itemResults.size());
|
||||
for (AttachmentItemResult itemResult : itemResults) {
|
||||
// check if we are trying to send attachment items with errors
|
||||
if (itemResult.getItem() != null)
|
||||
headers.add(itemResult.getItem().getHeader());
|
||||
}
|
||||
ioExecutor.execute(() -> {
|
||||
for (AttachmentHeader header : headers) {
|
||||
try {
|
||||
messagingManager.removeAttachment(header);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private AttachmentResult getResult(boolean finished) {
|
||||
// Make a copy of the list,
|
||||
// because our copy will continue to change in the background.
|
||||
// (As it's a CopyOnWriteArrayList,
|
||||
// the code that receives the result can safely do simple things
|
||||
// like iterating over the list,
|
||||
// but anything that involves calling more than one list method
|
||||
// is still unsafe.)
|
||||
Collection<AttachmentItemResult> items = new ArrayList<>(itemResults);
|
||||
return new AttachmentResult(items, finished);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class AttachmentDimensions {
|
||||
public class AttachmentDimensions {
|
||||
|
||||
final int defaultSize;
|
||||
final int minWidth, maxWidth;
|
||||
@@ -26,7 +26,7 @@ class AttachmentDimensions {
|
||||
this.maxHeight = maxHeight;
|
||||
}
|
||||
|
||||
static AttachmentDimensions getAttachmentDimensions(Resources res) {
|
||||
public static AttachmentDimensions getAttachmentDimensions(Resources res) {
|
||||
int defaultSize =
|
||||
res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
|
||||
int minWidth = res.getDimensionPixelSize(
|
||||
|
||||
@@ -4,14 +4,12 @@ import android.arch.lifecycle.LiveData;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
public interface AttachmentManager {
|
||||
|
||||
LiveData<AttachmentResult> storeAttachments(Collection<Uri> uri,
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package org.briarproject.briar.android.attachment;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static org.briarproject.briar.android.attachment.AttachmentDimensions.getAttachmentDimensions;
|
||||
|
||||
@Module
|
||||
public class AttachmentModule {
|
||||
|
||||
@Provides
|
||||
ImageHelper provideImageHelper(ImageHelperImpl imageHelper) {
|
||||
return imageHelper;
|
||||
}
|
||||
|
||||
@Provides
|
||||
ImageSizeCalculator provideImageSizeCalculator(ImageHelper imageHelper) {
|
||||
return new ImageSizeCalculator(imageHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
AttachmentDimensions provideAttachmentDimensions(Application app) {
|
||||
return getAttachmentDimensions(app.getResources());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
AttachmentRetriever provideAttachmentRetriever(
|
||||
AttachmentRetrieverImpl attachmentRetriever) {
|
||||
return attachmentRetriever;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
AttachmentCreator provideAttachmentCreator(
|
||||
AttachmentCreatorImpl attachmentCreator) {
|
||||
return attachmentCreator;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,241 @@
|
||||
package org.briarproject.briar.android.attachment;
|
||||
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.BitmapFactory.Options;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.support.media.ExifInterface;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import com.bumptech.glide.util.MarkEnforcingInputStream;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult;
|
||||
import org.briarproject.briar.api.messaging.Attachment;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_270;
|
||||
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_90;
|
||||
import static android.support.media.ExifInterface.ORIENTATION_TRANSPOSE;
|
||||
import static android.support.media.ExifInterface.ORIENTATION_TRANSVERSE;
|
||||
import static android.support.media.ExifInterface.TAG_IMAGE_LENGTH;
|
||||
import static android.support.media.ExifInterface.TAG_IMAGE_WIDTH;
|
||||
import static android.support.media.ExifInterface.TAG_ORIENTATION;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface AttachmentRetriever {
|
||||
public class AttachmentRetriever {
|
||||
|
||||
void cachePut(MessageId messageId, List<AttachmentItem> attachments);
|
||||
private static final Logger LOG =
|
||||
getLogger(AttachmentRetriever.class.getName());
|
||||
private static final int READ_LIMIT = 1024 * 8192;
|
||||
|
||||
private final MessagingManager messagingManager;
|
||||
private final ImageHelper imageHelper;
|
||||
private final int defaultSize;
|
||||
private final int minWidth, maxWidth;
|
||||
private final int minHeight, maxHeight;
|
||||
|
||||
private final Map<MessageId, List<AttachmentItem>> attachmentCache =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
@VisibleForTesting
|
||||
AttachmentRetriever(MessagingManager messagingManager,
|
||||
AttachmentDimensions dimensions, ImageHelper imageHelper) {
|
||||
this.messagingManager = messagingManager;
|
||||
this.imageHelper = imageHelper;
|
||||
defaultSize = dimensions.defaultSize;
|
||||
minWidth = dimensions.minWidth;
|
||||
maxWidth = dimensions.maxWidth;
|
||||
minHeight = dimensions.minHeight;
|
||||
maxHeight = dimensions.maxHeight;
|
||||
}
|
||||
|
||||
public AttachmentRetriever(MessagingManager messagingManager,
|
||||
AttachmentDimensions dimensions) {
|
||||
this(messagingManager, dimensions, new ImageHelper() {
|
||||
@Override
|
||||
public DecodeResult decodeStream(InputStream is) {
|
||||
Options options = new Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeStream(is, null, options);
|
||||
String mimeType = options.outMimeType;
|
||||
if (mimeType == null) mimeType = "";
|
||||
return new DecodeResult(options.outWidth, options.outHeight,
|
||||
mimeType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getExtensionFromMimeType(String mimeType) {
|
||||
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
||||
return mimeTypeMap.getExtensionFromMimeType(mimeType);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void cachePut(MessageId messageId,
|
||||
List<AttachmentItem> attachments) {
|
||||
attachmentCache.put(messageId, attachments);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
List<AttachmentItem> cacheGet(MessageId messageId);
|
||||
public List<AttachmentItem> cacheGet(MessageId messageId) {
|
||||
return attachmentCache.get(messageId);
|
||||
}
|
||||
|
||||
Attachment getMessageAttachment(AttachmentHeader h) throws DbException;
|
||||
public Attachment getMessageAttachment(AttachmentHeader h)
|
||||
throws DbException {
|
||||
return messagingManager.getAttachment(h);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link AttachmentItem} from the {@link Attachment}'s
|
||||
* {@link InputStream} which will be closed when this method returns.
|
||||
*/
|
||||
AttachmentItem getAttachmentItem(Attachment a, boolean needsSize);
|
||||
public AttachmentItem getAttachmentItem(Attachment a, boolean needsSize) {
|
||||
AttachmentHeader h = a.getHeader();
|
||||
if (!needsSize) {
|
||||
String extension =
|
||||
imageHelper.getExtensionFromMimeType(h.getContentType());
|
||||
boolean hasError = false;
|
||||
if (extension == null) {
|
||||
extension = "";
|
||||
hasError = true;
|
||||
}
|
||||
return new AttachmentItem(h, 0, 0, extension, 0, 0, hasError);
|
||||
}
|
||||
|
||||
Size size = new Size();
|
||||
InputStream is = new MarkEnforcingInputStream(
|
||||
new BufferedInputStream(a.getStream()));
|
||||
is.mark(READ_LIMIT);
|
||||
try {
|
||||
// use exif to get size
|
||||
if (h.getContentType().equals("image/jpeg")) {
|
||||
size = getSizeFromExif(is);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
try {
|
||||
// use BitmapFactory to get size
|
||||
if (size.error) {
|
||||
is.reset();
|
||||
// need to mark again to re-add read limit
|
||||
is.mark(READ_LIMIT);
|
||||
size = getSizeFromBitmap(is);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
} finally {
|
||||
tryToClose(is, LOG, WARNING);
|
||||
}
|
||||
|
||||
// calculate thumbnail size
|
||||
Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType);
|
||||
if (!size.error) {
|
||||
thumbnailSize =
|
||||
getThumbnailSize(size.width, size.height, size.mimeType);
|
||||
}
|
||||
// get file extension
|
||||
String extension = imageHelper.getExtensionFromMimeType(size.mimeType);
|
||||
boolean hasError = extension == null || size.error;
|
||||
if (!h.getContentType().equals(size.mimeType)) {
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Header has different mime type (" +
|
||||
h.getContentType() + ") than image (" + size.mimeType +
|
||||
").");
|
||||
}
|
||||
hasError = true;
|
||||
}
|
||||
if (extension == null) extension = "";
|
||||
return new AttachmentItem(h, size.width, size.height, extension,
|
||||
thumbnailSize.width, thumbnailSize.height, hasError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of a JPEG {@link InputStream} if EXIF info is available.
|
||||
*/
|
||||
private Size getSizeFromExif(InputStream is) throws IOException {
|
||||
ExifInterface exif = new ExifInterface(is);
|
||||
// these can return 0 independent of default value
|
||||
int width = exif.getAttributeInt(TAG_IMAGE_WIDTH, 0);
|
||||
int height = exif.getAttributeInt(TAG_IMAGE_LENGTH, 0);
|
||||
if (width == 0 || height == 0) return new Size();
|
||||
int orientation = exif.getAttributeInt(TAG_ORIENTATION, 0);
|
||||
if (orientation == ORIENTATION_ROTATE_90 ||
|
||||
orientation == ORIENTATION_ROTATE_270 ||
|
||||
orientation == ORIENTATION_TRANSVERSE ||
|
||||
orientation == ORIENTATION_TRANSPOSE) {
|
||||
//noinspection SuspiciousNameCombination
|
||||
return new Size(height, width, "image/jpeg");
|
||||
}
|
||||
return new Size(width, height, "image/jpeg");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of any image {@link InputStream}.
|
||||
*/
|
||||
private Size getSizeFromBitmap(InputStream is) {
|
||||
DecodeResult result = imageHelper.decodeStream(is);
|
||||
if (result.width < 1 || result.height < 1) return new Size();
|
||||
return new Size(result.width, result.height, result.mimeType);
|
||||
}
|
||||
|
||||
private Size getThumbnailSize(int width, int height, String mimeType) {
|
||||
float widthPercentage = maxWidth / (float) width;
|
||||
float heightPercentage = maxHeight / (float) height;
|
||||
float scaleFactor = Math.min(widthPercentage, heightPercentage);
|
||||
if (scaleFactor > 1) scaleFactor = 1f;
|
||||
int thumbnailWidth = (int) (width * scaleFactor);
|
||||
int thumbnailHeight = (int) (height * scaleFactor);
|
||||
if (thumbnailWidth < minWidth || thumbnailHeight < minHeight) {
|
||||
widthPercentage = minWidth / (float) width;
|
||||
heightPercentage = minHeight / (float) height;
|
||||
scaleFactor = Math.max(widthPercentage, heightPercentage);
|
||||
thumbnailWidth = (int) (width * scaleFactor);
|
||||
thumbnailHeight = (int) (height * scaleFactor);
|
||||
if (thumbnailWidth > maxWidth) thumbnailWidth = maxWidth;
|
||||
if (thumbnailHeight > maxHeight) thumbnailHeight = maxHeight;
|
||||
}
|
||||
return new Size(thumbnailWidth, thumbnailHeight, mimeType);
|
||||
}
|
||||
|
||||
private static class Size {
|
||||
|
||||
private final int width;
|
||||
private final int height;
|
||||
private final String mimeType;
|
||||
private final boolean error;
|
||||
|
||||
private Size(int width, int height, String mimeType) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.mimeType = mimeType;
|
||||
this.error = false;
|
||||
}
|
||||
|
||||
private Size() {
|
||||
this.width = 0;
|
||||
this.height = 0;
|
||||
this.mimeType = "";
|
||||
this.error = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
package org.briarproject.briar.android.attachment;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.messaging.Attachment;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
|
||||
@NotNullByDefault
|
||||
class AttachmentRetrieverImpl implements AttachmentRetriever {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(AttachmentRetrieverImpl.class.getName());
|
||||
|
||||
private final MessagingManager messagingManager;
|
||||
private final ImageHelper imageHelper;
|
||||
private final ImageSizeCalculator imageSizeCalculator;
|
||||
private final int defaultSize;
|
||||
private final int minWidth, maxWidth;
|
||||
private final int minHeight, maxHeight;
|
||||
|
||||
private final Map<MessageId, List<AttachmentItem>> attachmentCache =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
@Inject
|
||||
AttachmentRetrieverImpl(MessagingManager messagingManager,
|
||||
AttachmentDimensions dimensions, ImageHelper imageHelper,
|
||||
ImageSizeCalculator imageSizeCalculator) {
|
||||
this.messagingManager = messagingManager;
|
||||
this.imageHelper = imageHelper;
|
||||
this.imageSizeCalculator = imageSizeCalculator;
|
||||
defaultSize = dimensions.defaultSize;
|
||||
minWidth = dimensions.minWidth;
|
||||
maxWidth = dimensions.maxWidth;
|
||||
minHeight = dimensions.minHeight;
|
||||
maxHeight = dimensions.maxHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cachePut(MessageId messageId,
|
||||
List<AttachmentItem> attachments) {
|
||||
attachmentCache.put(messageId, attachments);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public List<AttachmentItem> cacheGet(MessageId messageId) {
|
||||
return attachmentCache.get(messageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attachment getMessageAttachment(AttachmentHeader h)
|
||||
throws DbException {
|
||||
return messagingManager.getAttachment(h);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttachmentItem getAttachmentItem(Attachment a, boolean needsSize) {
|
||||
AttachmentHeader h = a.getHeader();
|
||||
if (!needsSize) {
|
||||
String extension =
|
||||
imageHelper.getExtensionFromMimeType(h.getContentType());
|
||||
boolean hasError = false;
|
||||
if (extension == null) {
|
||||
extension = "";
|
||||
hasError = true;
|
||||
}
|
||||
return new AttachmentItem(h, 0, 0, extension, 0, 0, hasError);
|
||||
}
|
||||
|
||||
InputStream is = new BufferedInputStream(a.getStream());
|
||||
Size size = imageSizeCalculator.getSize(is, h.getContentType());
|
||||
|
||||
// calculate thumbnail size
|
||||
Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType);
|
||||
if (!size.error) {
|
||||
thumbnailSize =
|
||||
getThumbnailSize(size.width, size.height, size.mimeType);
|
||||
}
|
||||
// get file extension
|
||||
String extension = imageHelper.getExtensionFromMimeType(size.mimeType);
|
||||
boolean hasError = extension == null || size.error;
|
||||
if (!h.getContentType().equals(size.mimeType)) {
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Header has different mime type (" +
|
||||
h.getContentType() + ") than image (" + size.mimeType +
|
||||
").");
|
||||
}
|
||||
hasError = true;
|
||||
}
|
||||
if (extension == null) extension = "";
|
||||
return new AttachmentItem(h, size.width, size.height, extension,
|
||||
thumbnailSize.width, thumbnailSize.height, hasError);
|
||||
}
|
||||
|
||||
private Size getThumbnailSize(int width, int height, String mimeType) {
|
||||
float widthPercentage = maxWidth / (float) width;
|
||||
float heightPercentage = maxHeight / (float) height;
|
||||
float scaleFactor = Math.min(widthPercentage, heightPercentage);
|
||||
if (scaleFactor > 1) scaleFactor = 1f;
|
||||
int thumbnailWidth = (int) (width * scaleFactor);
|
||||
int thumbnailHeight = (int) (height * scaleFactor);
|
||||
if (thumbnailWidth < minWidth || thumbnailHeight < minHeight) {
|
||||
widthPercentage = minWidth / (float) width;
|
||||
heightPercentage = minHeight / (float) height;
|
||||
scaleFactor = Math.max(widthPercentage, heightPercentage);
|
||||
thumbnailWidth = (int) (width * scaleFactor);
|
||||
thumbnailHeight = (int) (height * scaleFactor);
|
||||
if (thumbnailWidth > maxWidth) thumbnailWidth = maxWidth;
|
||||
if (thumbnailHeight > maxHeight) thumbnailHeight = maxHeight;
|
||||
}
|
||||
return new Size(thumbnailWidth, thumbnailHeight, mimeType);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import java.io.InputStream;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface ImageHelper {
|
||||
interface ImageHelper {
|
||||
|
||||
DecodeResult decodeStream(InputStream is);
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
package org.briarproject.briar.android.attachment;
|
||||
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class ImageHelperImpl implements ImageHelper {
|
||||
|
||||
@Inject
|
||||
ImageHelperImpl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecodeResult decodeStream(InputStream is) {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeStream(is, null, options);
|
||||
String mimeType = options.outMimeType;
|
||||
if (mimeType == null) mimeType = "";
|
||||
return new DecodeResult(options.outWidth, options.outHeight,
|
||||
mimeType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getExtensionFromMimeType(String mimeType) {
|
||||
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
||||
return mimeTypeMap.getExtensionFromMimeType(mimeType);
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package org.briarproject.briar.android.attachment;
|
||||
|
||||
import android.support.media.ExifInterface;
|
||||
|
||||
import com.bumptech.glide.util.MarkEnforcingInputStream;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_270;
|
||||
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_90;
|
||||
import static android.support.media.ExifInterface.ORIENTATION_TRANSPOSE;
|
||||
import static android.support.media.ExifInterface.ORIENTATION_TRANSVERSE;
|
||||
import static android.support.media.ExifInterface.TAG_IMAGE_LENGTH;
|
||||
import static android.support.media.ExifInterface.TAG_IMAGE_WIDTH;
|
||||
import static android.support.media.ExifInterface.TAG_ORIENTATION;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@NotNullByDefault
|
||||
class ImageSizeCalculator {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(ImageSizeCalculator.class.getName());
|
||||
|
||||
private static final int READ_LIMIT = 1024 * 8192;
|
||||
|
||||
private final ImageHelper imageHelper;
|
||||
|
||||
ImageSizeCalculator(ImageHelper imageHelper) {
|
||||
this.imageHelper = imageHelper;
|
||||
}
|
||||
|
||||
Size getSize(InputStream is, String contentType) {
|
||||
Size size = new Size();
|
||||
is = new MarkEnforcingInputStream(is);
|
||||
is.mark(READ_LIMIT);
|
||||
if (contentType.equals("image/jpeg")) {
|
||||
try {
|
||||
// use exif to get size
|
||||
size = getSizeFromExif(is);
|
||||
is.reset();
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
if (size.error) {
|
||||
// need to mark again to re-add read limit
|
||||
is.mark(READ_LIMIT);
|
||||
try {
|
||||
// use BitmapFactory to get size
|
||||
size = getSizeFromBitmap(is);
|
||||
is.reset();
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of a JPEG {@link InputStream} if EXIF info is available.
|
||||
*/
|
||||
private Size getSizeFromExif(InputStream is) throws IOException {
|
||||
ExifInterface exif = new ExifInterface(is);
|
||||
// these can return 0 independent of default value
|
||||
int width = exif.getAttributeInt(TAG_IMAGE_WIDTH, 0);
|
||||
int height = exif.getAttributeInt(TAG_IMAGE_LENGTH, 0);
|
||||
if (width == 0 || height == 0) return new Size();
|
||||
int orientation = exif.getAttributeInt(TAG_ORIENTATION, 0);
|
||||
if (orientation == ORIENTATION_ROTATE_90 ||
|
||||
orientation == ORIENTATION_ROTATE_270 ||
|
||||
orientation == ORIENTATION_TRANSVERSE ||
|
||||
orientation == ORIENTATION_TRANSPOSE) {
|
||||
//noinspection SuspiciousNameCombination
|
||||
return new Size(height, width, "image/jpeg");
|
||||
}
|
||||
return new Size(width, height, "image/jpeg");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of any image {@link InputStream}.
|
||||
*/
|
||||
private Size getSizeFromBitmap(InputStream is) {
|
||||
DecodeResult result = imageHelper.decodeStream(is);
|
||||
if (result.width < 1 || result.height < 1) return new Size();
|
||||
return new Size(result.width, result.height, result.mimeType);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package org.briarproject.briar.android.attachment;
|
||||
|
||||
class Size {
|
||||
|
||||
final int width;
|
||||
final int height;
|
||||
final String mimeType;
|
||||
final boolean error;
|
||||
|
||||
Size(int width, int height, String mimeType) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.mimeType = mimeType;
|
||||
this.error = false;
|
||||
}
|
||||
|
||||
Size() {
|
||||
this.width = 0;
|
||||
this.height = 0;
|
||||
this.mimeType = "";
|
||||
this.error = true;
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,6 @@ import static android.view.View.VISIBLE;
|
||||
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
||||
|
||||
public class RssFeedImportActivity extends BriarActivity {
|
||||
|
||||
@@ -78,6 +77,7 @@ public class RssFeedImportActivity extends BriarActivity {
|
||||
if (actionId == IME_ACTION_DONE && importButton.isEnabled() &&
|
||||
importButton.getVisibility() == VISIBLE) {
|
||||
publish();
|
||||
hideSoftKeyboard(urlInput);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -123,7 +123,6 @@ public class RssFeedImportActivity extends BriarActivity {
|
||||
// hide import button, show progress bar
|
||||
importButton.setVisibility(GONE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
hideSoftKeyboard(urlInput);
|
||||
|
||||
String url = validateAndNormaliseUrl(urlInput.getText().toString());
|
||||
if (url == null) throw new AssertionError();
|
||||
|
||||
@@ -3,8 +3,11 @@ package org.briarproject.briar.android.blog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
@@ -41,7 +44,7 @@ import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_L
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class WriteBlogPostActivity extends BriarActivity
|
||||
implements SendListener {
|
||||
implements OnEditorActionListener, SendListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(WriteBlogPostActivity.class.getName());
|
||||
@@ -110,6 +113,12 @@ public class WriteBlogPostActivity extends BriarActivity
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
|
||||
input.requestFocus();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendClick(@Nullable String text,
|
||||
List<AttachmentHeader> headers) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.FeatureFlags;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
@@ -56,6 +57,7 @@ import javax.inject.Inject;
|
||||
|
||||
import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
|
||||
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
|
||||
import io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE;
|
||||
@@ -83,6 +85,8 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
||||
EventBus eventBus;
|
||||
@Inject
|
||||
AndroidNotificationManager notificationManager;
|
||||
@Inject
|
||||
FeatureFlags featureFlags;
|
||||
|
||||
private ContactListAdapter adapter;
|
||||
private BriarRecyclerView list;
|
||||
@@ -122,7 +126,19 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
||||
container, false);
|
||||
|
||||
FabSpeedDial speedDial = contentView.findViewById(R.id.speedDial);
|
||||
speedDial.addOnMenuItemClickListener(this);
|
||||
if (featureFlags.shouldEnableRemoteContacts()) {
|
||||
speedDial.addOnMenuItemClickListener(this);
|
||||
} else {
|
||||
speedDial.setMenu(new FabSpeedDialMenu(contentView.getContext()));
|
||||
speedDial.addOnStateChangeListener(open -> {
|
||||
if (open) {
|
||||
Intent intent = new Intent(getContext(),
|
||||
ContactExchangeActivity.class);
|
||||
startActivity(intent);
|
||||
speedDial.closeMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
OnContactClickListener<ContactListItem> onContactClickListener =
|
||||
(view, item) -> {
|
||||
@@ -153,10 +169,9 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
||||
startActivity(i);
|
||||
}
|
||||
};
|
||||
adapter = new ContactListAdapter(requireContext(),
|
||||
onContactClickListener);
|
||||
adapter = new ContactListAdapter(getContext(), onContactClickListener);
|
||||
list = contentView.findViewById(R.id.list);
|
||||
list.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
list.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
list.setAdapter(adapter);
|
||||
list.setEmptyImage(R.drawable.ic_empty_state_contact_list);
|
||||
list.setEmptyText(getString(R.string.no_contacts));
|
||||
@@ -252,7 +267,7 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
||||
if (revision == adapter.getRevision()) {
|
||||
adapter.incrementRevision();
|
||||
if (contacts.isEmpty()) list.showData();
|
||||
else adapter.replaceAll(contacts);
|
||||
else adapter.addAll(contacts);
|
||||
} else {
|
||||
LOG.info("Concurrent update, reloading");
|
||||
loadContacts();
|
||||
|
||||
@@ -9,10 +9,8 @@ import android.support.annotation.Nullable;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.NoSuchPendingContactException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||
import org.briarproject.briar.android.viewmodel.LiveResult;
|
||||
@@ -120,19 +118,4 @@ public class AddContactViewModel extends AndroidViewModel {
|
||||
return addContactResult;
|
||||
}
|
||||
|
||||
public void updatePendingContact(String name, PendingContact p) {
|
||||
dbExecutor.execute(() -> {
|
||||
try {
|
||||
contactManager.removePendingContact(p.getId());
|
||||
addContact(name);
|
||||
} catch(NoSuchPendingContactException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
// no error in UI as pending contact was converted into contact
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
addContactResult.postValue(new LiveResult<>(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.briar.android.contact.add.remote;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.arch.lifecycle.ViewModelProvider;
|
||||
import android.arch.lifecycle.ViewModelProviders;
|
||||
import android.content.ClipData;
|
||||
@@ -12,9 +11,7 @@ import android.support.v4.app.ShareCompat.IntentBuilder;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -37,8 +34,7 @@ import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class LinkExchangeFragment extends BaseFragment
|
||||
implements OnGlobalLayoutListener {
|
||||
public class LinkExchangeFragment extends BaseFragment {
|
||||
|
||||
private static final String TAG = LinkExchangeFragment.class.getName();
|
||||
|
||||
@@ -94,30 +90,9 @@ public class LinkExchangeFragment extends BaseFragment
|
||||
observeOnce(viewModel.getHandshakeLink(), this,
|
||||
this::onHandshakeLinkLoaded);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
ScrollView scrollView = (ScrollView) v;
|
||||
// we need to wait for views to be laid out to get the heights
|
||||
scrollView.getViewTreeObserver().addOnGlobalLayoutListener(this);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
ScrollView scrollView = (ScrollView) requireNonNull(getView());
|
||||
View layout = scrollView.getChildAt(0);
|
||||
int scrollBy = layout.getHeight() - scrollView.getHeight();
|
||||
if (scrollBy > 0) {
|
||||
// smoothScrollTo() is too fast due to the transition animation
|
||||
ObjectAnimator animator = ObjectAnimator
|
||||
.ofInt(scrollView, "scrollY", scrollBy);
|
||||
animator.setDuration(1000);
|
||||
animator.start();
|
||||
}
|
||||
layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
}
|
||||
|
||||
private void onHandshakeLinkLoaded(String link) {
|
||||
View v = requireNonNull(getView());
|
||||
|
||||
|
||||
@@ -2,15 +2,10 @@ package org.briarproject.briar.android.contact.add.remote;
|
||||
|
||||
import android.arch.lifecycle.ViewModelProvider;
|
||||
import android.arch.lifecycle.ViewModelProviders;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.design.widget.TextInputEditText;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.support.v7.app.AlertDialog.Builder;
|
||||
import android.text.Editable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -20,10 +15,6 @@ import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
import org.briarproject.bramble.api.db.ContactExistsException;
|
||||
import org.briarproject.bramble.api.db.PendingContactExistsException;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
@@ -33,13 +24,9 @@ import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.support.v4.content.ContextCompat.getColor;
|
||||
import static android.support.v4.content.ContextCompat.getDrawable;
|
||||
import static android.support.v4.graphics.drawable.DrawableCompat.setTint;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
|
||||
|
||||
@@ -95,20 +82,19 @@ public class NicknameFragment extends BaseFragment {
|
||||
|
||||
@Nullable
|
||||
private String getNicknameOrNull() {
|
||||
Editable text = contactNameInput.getText();
|
||||
if (text == null || text.toString().trim().length() == 0) {
|
||||
Editable name = contactNameInput.getText();
|
||||
if (name == null || name.toString().trim().length() == 0) {
|
||||
contactNameLayout.setError(getString(R.string.nickname_missing));
|
||||
contactNameInput.requestFocus();
|
||||
return null;
|
||||
}
|
||||
String name = text.toString().trim();
|
||||
if (utf8IsTooLong(name, MAX_AUTHOR_NAME_LENGTH)) {
|
||||
if (utf8IsTooLong(name.toString(), MAX_AUTHOR_NAME_LENGTH)) {
|
||||
contactNameLayout.setError(getString(R.string.name_too_long));
|
||||
contactNameInput.requestFocus();
|
||||
return null;
|
||||
}
|
||||
contactNameLayout.setError(null);
|
||||
return name;
|
||||
return name.toString().trim();
|
||||
}
|
||||
|
||||
private void onAddButtonClicked() {
|
||||
@@ -120,95 +106,23 @@ public class NicknameFragment extends BaseFragment {
|
||||
|
||||
viewModel.getAddContactResult().observe(this, result -> {
|
||||
if (result == null) return;
|
||||
if (result.hasError())
|
||||
handleException(name, requireNonNull(result.getException()));
|
||||
else
|
||||
showPendingContactListActivity();
|
||||
if (result.hasError()) {
|
||||
int stringRes;
|
||||
if (result
|
||||
.getException() instanceof UnsupportedVersionException) {
|
||||
stringRes = R.string.unsupported_link;
|
||||
} else {
|
||||
stringRes = R.string.adding_contact_error;
|
||||
}
|
||||
Toast.makeText(getContext(), stringRes, LENGTH_LONG).show();
|
||||
} else {
|
||||
Intent intent = new Intent(getActivity(),
|
||||
PendingContactListActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
finish();
|
||||
});
|
||||
viewModel.addContact(name);
|
||||
}
|
||||
|
||||
private void showPendingContactListActivity() {
|
||||
Intent intent = new Intent(getActivity(),
|
||||
PendingContactListActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void handleException(String name, Exception e) {
|
||||
if (e instanceof ContactExistsException) {
|
||||
ContactExistsException ce = (ContactExistsException) e;
|
||||
handleExistingContact(name, ce.getRemoteAuthor());
|
||||
} else if (e instanceof PendingContactExistsException) {
|
||||
PendingContactExistsException pe =
|
||||
(PendingContactExistsException) e;
|
||||
handleExistingPendingContact(name, pe.getPendingContact());
|
||||
} else if (e instanceof UnsupportedVersionException) {
|
||||
int stringRes = R.string.unsupported_link;
|
||||
Toast.makeText(getContext(), stringRes, LENGTH_LONG).show();
|
||||
finish();
|
||||
} else {
|
||||
int stringRes = R.string.adding_contact_error;
|
||||
Toast.makeText(getContext(), stringRes, LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleExistingContact(String name, Author existing) {
|
||||
OnClickListener listener = (d, w) -> {
|
||||
d.dismiss();
|
||||
String str = getString(R.string.contact_already_exists, name);
|
||||
Toast.makeText(getContext(), str, LENGTH_LONG).show();
|
||||
finish();
|
||||
};
|
||||
showSameLinkDialog(existing.getName(), name,
|
||||
R.string.duplicate_link_dialog_text_1_contact, listener);
|
||||
}
|
||||
|
||||
private void handleExistingPendingContact(String name, PendingContact p) {
|
||||
OnClickListener listener = (d, w) -> {
|
||||
viewModel.updatePendingContact(name, p);
|
||||
Toast.makeText(getContext(), R.string.pending_contact_updated_toast,
|
||||
LENGTH_LONG).show();
|
||||
d.dismiss();
|
||||
showPendingContactListActivity();
|
||||
};
|
||||
showSameLinkDialog(p.getAlias(), name,
|
||||
R.string.duplicate_link_dialog_text_1, listener);
|
||||
}
|
||||
|
||||
private void showSameLinkDialog(String name1, String name2,
|
||||
@StringRes int existsRes, OnClickListener samePersonListener) {
|
||||
Context ctx = requireContext();
|
||||
Builder b = new Builder(ctx, R.style.BriarDialogTheme_Neutral);
|
||||
b.setTitle(getString(R.string.duplicate_link_dialog_title));
|
||||
String msg = getString(existsRes, name1) + "\n\n" +
|
||||
getString(R.string.duplicate_link_dialog_text_2, name2, name1);
|
||||
b.setMessage(msg);
|
||||
b.setPositiveButton(R.string.same_person_button, samePersonListener);
|
||||
b.setNegativeButton(R.string.different_person_button, (d, w) -> {
|
||||
d.dismiss();
|
||||
showWarningDialog(name1, name2);
|
||||
});
|
||||
b.setCancelable(false);
|
||||
b.show();
|
||||
}
|
||||
|
||||
private void showWarningDialog(String name1, String name2) {
|
||||
Context ctx = requireContext();
|
||||
Builder b = new Builder(ctx, R.style.BriarDialogTheme);
|
||||
Drawable icon = getDrawable(ctx, R.drawable.alerts_and_states_error);
|
||||
setTint(requireNonNull(icon), getColor(ctx, R.color.color_primary));
|
||||
b.setIcon(icon);
|
||||
b.setTitle(getString(R.string.duplicate_link_dialog_title));
|
||||
b.setMessage(
|
||||
getString(R.string.duplicate_link_dialog_text_3, name1, name2));
|
||||
b.setPositiveButton(R.string.ok, (d, w) -> {
|
||||
d.dismiss();
|
||||
finish();
|
||||
});
|
||||
b.setCancelable(false);
|
||||
b.show();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public class PendingContactListViewModel extends AndroidViewModel
|
||||
Collection<Pair<PendingContact, PendingContactState>> pairs =
|
||||
contactManager.getPendingContacts();
|
||||
List<PendingContactItem> items = new ArrayList<>(pairs.size());
|
||||
boolean online = items.isEmpty();
|
||||
boolean online = false;
|
||||
for (Pair<PendingContact, PendingContactState> pair : pairs) {
|
||||
PendingContact p = pair.getFirst();
|
||||
PendingContactState state = pair.getSecond();
|
||||
|
||||
@@ -21,12 +21,9 @@ import org.briarproject.briar.android.activity.BaseActivity;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.util.StringUtils.toUtf8;
|
||||
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showSoftKeyboard;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -79,14 +76,13 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
|
||||
setButton.setOnClickListener(v1 -> onSetButtonClicked());
|
||||
|
||||
Button cancelButton = v.findViewById(R.id.cancelButton);
|
||||
cancelButton.setOnClickListener(v1 -> onCancelButtonClicked());
|
||||
cancelButton.setOnClickListener(v1 -> getDialog().cancel());
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
private void onSetButtonClicked() {
|
||||
hideSoftKeyboard(aliasEditText);
|
||||
String alias = aliasEditText.getText().toString().trim();
|
||||
String alias = aliasEditText.getText().toString();
|
||||
if (toUtf8(alias).length > MAX_AUTHOR_NAME_LENGTH) {
|
||||
aliasEditLayout.setError(getString(R.string.name_too_long));
|
||||
} else {
|
||||
@@ -95,17 +91,4 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void onCancelButtonClicked() {
|
||||
hideSoftKeyboard(aliasEditText);
|
||||
getDialog().cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
requireNonNull(getDialog().getWindow())
|
||||
.setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||
showSoftKeyboard(aliasEditText);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -276,7 +276,7 @@ public class ConversationActivity extends BriarActivity
|
||||
textInputView.setSendController(sendController);
|
||||
textInputView.setMaxTextLength(MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
||||
textInputView.setReady(false);
|
||||
textInputView.setOnKeyboardShownListener(this::scrollToBottom);
|
||||
textInputView.addOnKeyboardShownListener(this::scrollToBottom);
|
||||
}
|
||||
|
||||
private void scrollToBottom() {
|
||||
@@ -356,11 +356,6 @@ public class ConversationActivity extends BriarActivity
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.conversation_actions, menu);
|
||||
|
||||
// Hide private message deletion action if feature is not enabled
|
||||
if (!featureFlags.shouldEnablePrivateMessageDeletion()) {
|
||||
menu.removeItem(R.id.action_delete_all_messages);
|
||||
}
|
||||
|
||||
// enable introduction action if available
|
||||
observeOnce(viewModel.showIntroductionAction(), this, enable -> {
|
||||
if (enable != null && enable) {
|
||||
@@ -394,9 +389,6 @@ public class ConversationActivity extends BriarActivity
|
||||
AliasDialogFragment.newInstance().show(
|
||||
getSupportFragmentManager(), AliasDialogFragment.TAG);
|
||||
return true;
|
||||
case R.id.action_delete_all_messages:
|
||||
askToDeleteAllMessages();
|
||||
return true;
|
||||
case R.id.action_social_remove_person:
|
||||
askToRemoveContact();
|
||||
return true;
|
||||
@@ -735,52 +727,6 @@ public class ConversationActivity extends BriarActivity
|
||||
addConversationItem(h.accept(visitor));
|
||||
}
|
||||
|
||||
private void askToDeleteAllMessages() {
|
||||
AlertDialog.Builder builder =
|
||||
new AlertDialog.Builder(this, R.style.BriarDialogTheme);
|
||||
builder.setTitle(getString(R.string.dialog_title_delete_all_messages));
|
||||
builder.setMessage(
|
||||
getString(R.string.dialog_message_delete_all_messages));
|
||||
builder.setNegativeButton(R.string.delete,
|
||||
(dialog, which) -> deleteAllMessages());
|
||||
builder.setPositiveButton(R.string.cancel, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void deleteAllMessages() {
|
||||
list.showProgressBar();
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
boolean allDeleted =
|
||||
conversationManager.deleteAllMessages(contactId);
|
||||
reloadConversationAfterDeletingAllMessages(allDeleted);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
runOnUiThreadUnlessDestroyed(() -> list.showData());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void reloadConversationAfterDeletingAllMessages(
|
||||
boolean allDeleted) {
|
||||
runOnUiThreadUnlessDestroyed(() -> {
|
||||
adapter.clear();
|
||||
loadMessages();
|
||||
if (!allDeleted) showNotAllDeletedDialog();
|
||||
});
|
||||
}
|
||||
|
||||
private void showNotAllDeletedDialog() {
|
||||
AlertDialog.Builder builder =
|
||||
new AlertDialog.Builder(this, R.style.BriarDialogTheme);
|
||||
builder.setTitle(
|
||||
getString(R.string.dialog_title_not_all_messages_deleted));
|
||||
builder.setMessage(
|
||||
getString(R.string.dialog_message_not_all_messages_deleted));
|
||||
builder.setPositiveButton(R.string.ok, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void askToRemoveContact() {
|
||||
DialogInterface.OnClickListener okListener =
|
||||
(dialog, which) -> removeContact();
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||
import org.briarproject.bramble.api.db.TransactionManager;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
@@ -50,6 +51,7 @@ import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
import static org.briarproject.briar.android.attachment.AttachmentDimensions.getAttachmentDimensions;
|
||||
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
||||
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
||||
|
||||
@@ -99,13 +101,10 @@ public class ConversationViewModel extends AndroidViewModel
|
||||
@Inject
|
||||
ConversationViewModel(Application application,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
TransactionManager db,
|
||||
MessagingManager messagingManager,
|
||||
ContactManager contactManager,
|
||||
@IoExecutor Executor ioExecutor, TransactionManager db,
|
||||
MessagingManager messagingManager, ContactManager contactManager,
|
||||
SettingsManager settingsManager,
|
||||
PrivateMessageFactory privateMessageFactory,
|
||||
AttachmentRetriever attachmentRetriever,
|
||||
AttachmentCreator attachmentCreator) {
|
||||
PrivateMessageFactory privateMessageFactory) {
|
||||
super(application);
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.db = db;
|
||||
@@ -113,8 +112,10 @@ public class ConversationViewModel extends AndroidViewModel
|
||||
this.contactManager = contactManager;
|
||||
this.settingsManager = settingsManager;
|
||||
this.privateMessageFactory = privateMessageFactory;
|
||||
this.attachmentRetriever = attachmentRetriever;
|
||||
this.attachmentCreator = attachmentCreator;
|
||||
this.attachmentRetriever = new AttachmentRetriever(messagingManager,
|
||||
getAttachmentDimensions(application.getResources()));
|
||||
this.attachmentCreator = new AttachmentCreator(getApplication(),
|
||||
ioExecutor, messagingManager, attachmentRetriever);
|
||||
messagingGroupId = Transformations
|
||||
.map(contact, c -> messagingManager.getContactGroup(c).getId());
|
||||
contactDeleted.setValue(false);
|
||||
|
||||
@@ -142,8 +142,11 @@ public class ImageActivity extends BriarActivity
|
||||
viewPager.setAdapter(pagerAdapter);
|
||||
viewPager.setCurrentItem(position);
|
||||
|
||||
viewModel.getOnImageClicked().observeEvent(this, this::onImageClicked);
|
||||
window.getDecorView().setSystemUiVisibility(UI_FLAGS_DEFAULT);
|
||||
if (SDK_INT >= 16) {
|
||||
viewModel.getOnImageClicked()
|
||||
.observeEvent(this, this::onImageClicked);
|
||||
window.getDecorView().setSystemUiVisibility(UI_FLAGS_DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -171,7 +174,11 @@ public class ImageActivity extends BriarActivity
|
||||
viewModel.setToolbarPosition(
|
||||
appBarLayout.getTop(), appBarLayout.getBottom()
|
||||
);
|
||||
layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
if (SDK_INT >= 16) {
|
||||
layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
} else {
|
||||
layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -258,7 +265,7 @@ public class ImageActivity extends BriarActivity
|
||||
* when the previous activity (with visible status bar) is shown.
|
||||
*/
|
||||
private void showStatusBarBeforeFinishing() {
|
||||
if (appBarLayout.getVisibility() == GONE) {
|
||||
if (SDK_INT >= 16 && appBarLayout.getVisibility() == GONE) {
|
||||
View decorView = getWindow().getDecorView();
|
||||
decorView.setSystemUiVisibility(UI_FLAGS_DEFAULT);
|
||||
}
|
||||
|
||||
@@ -34,8 +34,6 @@ import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
import static org.briarproject.briar.android.util.UiUtils.enterPressed;
|
||||
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showSoftKeyboard;
|
||||
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@@ -93,6 +91,12 @@ public class CreateForumActivity extends BriarActivity {
|
||||
progress = findViewById(R.id.createForumProgressBar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
showSoftKeyboard(nameEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
|
||||
@@ -182,7 +182,7 @@ public class ForumListFragment extends BaseEventFragment implements
|
||||
if (revision == adapter.getRevision()) {
|
||||
adapter.incrementRevision();
|
||||
if (forums.isEmpty()) list.showData();
|
||||
else adapter.replaceAll(forums);
|
||||
else adapter.addAll(forums);
|
||||
} else {
|
||||
LOG.info("Concurrent update, reloading");
|
||||
loadForums();
|
||||
|
||||
@@ -43,7 +43,6 @@ import static java.util.Objects.requireNonNull;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
||||
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_TEXT_LENGTH;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@@ -185,7 +184,7 @@ public class IntroductionMessageFragment extends BaseFragment
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
hideSoftKeyboard(ui.message);
|
||||
introductionActivity.hideSoftKeyboard(ui.message);
|
||||
introductionActivity.onBackPressed();
|
||||
return true;
|
||||
default:
|
||||
@@ -202,7 +201,7 @@ public class IntroductionMessageFragment extends BaseFragment
|
||||
makeIntroduction(contact1, contact2, text);
|
||||
|
||||
// don't wait for the introduction to be made before finishing activity
|
||||
hideSoftKeyboard(ui.message);
|
||||
introductionActivity.hideSoftKeyboard(ui.message);
|
||||
introductionActivity.setResult(RESULT_OK);
|
||||
introductionActivity.supportFinishAfterTransition();
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import static android.hardware.Camera.Parameters.FOCUS_MODE_FIXED;
|
||||
import static android.hardware.Camera.Parameters.FOCUS_MODE_MACRO;
|
||||
import static android.hardware.Camera.Parameters.SCENE_MODE_AUTO;
|
||||
import static android.hardware.Camera.Parameters.SCENE_MODE_BARCODE;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
@@ -339,7 +340,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||
|
||||
@UiThread
|
||||
private void setVideoStabilisation(Parameters params) {
|
||||
if (params.isVideoStabilizationSupported()) {
|
||||
if (SDK_INT >= 15 && params.isVideoStabilizationSupported()) {
|
||||
params.setVideoStabilization(true);
|
||||
}
|
||||
}
|
||||
@@ -414,8 +415,10 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||
} catch (RuntimeException e) {
|
||||
throw new CameraException(e);
|
||||
}
|
||||
LOG.info("Video stabilisation enabled: "
|
||||
+ params.getVideoStabilization());
|
||||
if (SDK_INT >= 15) {
|
||||
LOG.info("Video stabilisation enabled: "
|
||||
+ params.getVideoStabilization());
|
||||
}
|
||||
LOG.info("Scene mode: " + params.getSceneMode());
|
||||
LOG.info("Focus mode: " + params.getFocusMode());
|
||||
LOG.info("Flash mode: " + params.getFlashMode());
|
||||
|
||||
@@ -62,7 +62,8 @@ public class ContactExchangeActivity extends KeyAgreementActivity {
|
||||
@UiThread
|
||||
private void contactExchangeSucceeded(Author remoteAuthor) {
|
||||
String contactName = remoteAuthor.getName();
|
||||
String text = getString(R.string.contact_added_toast, contactName);
|
||||
String format = getString(R.string.contact_added_toast);
|
||||
String text = String.format(format, contactName);
|
||||
Toast.makeText(this, text, LENGTH_LONG).show();
|
||||
supportFinishAfterTransition();
|
||||
}
|
||||
|
||||
@@ -183,14 +183,10 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
||||
if (bt == null) {
|
||||
setBluetoothState(BluetoothState.NO_ADAPTER);
|
||||
} else {
|
||||
setBluetoothState(BluetoothState.WAITING);
|
||||
wasAdapterEnabled = bt.isEnabled();
|
||||
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
|
||||
if (i.resolveActivity(getPackageManager()) != null) {
|
||||
setBluetoothState(BluetoothState.WAITING);
|
||||
wasAdapterEnabled = bt.isEnabled();
|
||||
startActivityForResult(i, REQUEST_BLUETOOTH_DISCOVERABLE);
|
||||
} else {
|
||||
setBluetoothState(BluetoothState.NO_ADAPTER);
|
||||
}
|
||||
startActivityForResult(i, REQUEST_BLUETOOTH_DISCOVERABLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,8 +26,6 @@ import javax.inject.Inject;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
|
||||
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showSoftKeyboard;
|
||||
|
||||
public class ChangePasswordActivity extends BriarActivity
|
||||
implements OnClickListener, OnEditorActionListener {
|
||||
|
||||
@@ -83,6 +83,12 @@ public class PasswordFragment extends BaseFragment implements TextWatcher {
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
showSoftKeyboard(password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count,
|
||||
int after) {
|
||||
|
||||
@@ -47,9 +47,7 @@ public class GroupActivity extends
|
||||
@Inject
|
||||
GroupController controller;
|
||||
|
||||
@Nullable
|
||||
private Boolean isCreator = null;
|
||||
private boolean isDissolved = false;
|
||||
private boolean isCreator, isDissolved = false;
|
||||
private MenuItem revealMenuItem, inviteMenuItem, leaveMenuItem,
|
||||
dissolveMenuItem;
|
||||
|
||||
@@ -139,14 +137,6 @@ public class GroupActivity extends
|
||||
inviteMenuItem = menu.findItem(R.id.action_group_invite);
|
||||
leaveMenuItem = menu.findItem(R.id.action_group_leave);
|
||||
dissolveMenuItem = menu.findItem(R.id.action_group_dissolve);
|
||||
|
||||
// all role-dependent items are invisible until we know our role
|
||||
revealMenuItem.setVisible(false);
|
||||
inviteMenuItem.setVisible(false);
|
||||
leaveMenuItem.setVisible(false);
|
||||
dissolveMenuItem.setVisible(false);
|
||||
|
||||
// show items based on role
|
||||
showMenuItems();
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
@@ -161,27 +151,19 @@ public class GroupActivity extends
|
||||
startActivity(i1);
|
||||
return true;
|
||||
case R.id.action_group_reveal:
|
||||
if (isCreator == null || isCreator)
|
||||
throw new IllegalStateException();
|
||||
Intent i2 = new Intent(this, RevealContactsActivity.class);
|
||||
i2.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivity(i2);
|
||||
return true;
|
||||
case R.id.action_group_invite:
|
||||
if (isCreator == null || !isCreator)
|
||||
throw new IllegalStateException();
|
||||
Intent i3 = new Intent(this, GroupInviteActivity.class);
|
||||
i3.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivityForResult(i3, REQUEST_GROUP_INVITE);
|
||||
return true;
|
||||
case R.id.action_group_leave:
|
||||
if (isCreator == null || isCreator)
|
||||
throw new IllegalStateException();
|
||||
showLeaveGroupDialog();
|
||||
return true;
|
||||
case R.id.action_group_dissolve:
|
||||
if (isCreator == null || !isCreator)
|
||||
throw new IllegalStateException();
|
||||
showDissolveGroupDialog();
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
@@ -227,12 +209,18 @@ public class GroupActivity extends
|
||||
}
|
||||
|
||||
private void showMenuItems() {
|
||||
// we need to have the menu items and know if we are the creator
|
||||
if (leaveMenuItem == null || isCreator == null) return;
|
||||
revealMenuItem.setVisible(!isCreator);
|
||||
inviteMenuItem.setVisible(isCreator);
|
||||
leaveMenuItem.setVisible(!isCreator);
|
||||
dissolveMenuItem.setVisible(isCreator);
|
||||
if (leaveMenuItem == null || dissolveMenuItem == null) return;
|
||||
if (isCreator) {
|
||||
revealMenuItem.setVisible(false);
|
||||
inviteMenuItem.setVisible(true);
|
||||
leaveMenuItem.setVisible(false);
|
||||
dissolveMenuItem.setVisible(true);
|
||||
} else {
|
||||
revealMenuItem.setVisible(true);
|
||||
inviteMenuItem.setVisible(false);
|
||||
leaveMenuItem.setVisible(true);
|
||||
dissolveMenuItem.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void showLeaveGroupDialog() {
|
||||
|
||||
@@ -24,7 +24,6 @@ import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
|
||||
import static org.briarproject.briar.android.util.UiUtils.enterPressed;
|
||||
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@@ -92,6 +91,12 @@ public class CreateGroupFragment extends BaseFragment {
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
listener.showSoftKeyboard(nameEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
@@ -115,7 +120,7 @@ public class CreateGroupFragment extends BaseFragment {
|
||||
|
||||
private void createGroup() {
|
||||
if (!validateName()) return;
|
||||
hideSoftKeyboard(nameEntry);
|
||||
listener.hideSoftKeyboard(nameEntry);
|
||||
createGroupButton.setVisibility(GONE);
|
||||
progress.setVisibility(VISIBLE);
|
||||
listener.onGroupNameChosen(nameEntry.getText().toString());
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
package org.briarproject.briar.android.privategroup.creation;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||
|
||||
interface CreateGroupListener extends BaseFragmentListener {
|
||||
|
||||
void onGroupNameChosen(String name);
|
||||
|
||||
void showSoftKeyboard(View view);
|
||||
|
||||
void hideSoftKeyboard(View view);
|
||||
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ public class GroupListFragment extends BaseFragment implements
|
||||
if (revision == adapter.getRevision()) {
|
||||
adapter.incrementRevision();
|
||||
if (groups.isEmpty()) list.showData();
|
||||
else adapter.replaceAll(groups);
|
||||
else adapter.addAll(groups);
|
||||
} else {
|
||||
LOG.info("Concurrent update, reloading");
|
||||
loadGroups();
|
||||
|
||||
@@ -90,9 +90,14 @@ public class BriarReportPrimer implements ReportPrimer {
|
||||
ActivityManager.MemoryInfo mem = new ActivityManager.MemoryInfo();
|
||||
am.getMemoryInfo(mem);
|
||||
String systemMemory;
|
||||
systemMemory = (mem.totalMem / 1024 / 1024) + " MiB total, "
|
||||
+ (mem.availMem / 1024 / 1204) + " MiB free, "
|
||||
+ (mem.threshold / 1024 / 1024) + " MiB threshold";
|
||||
if (Build.VERSION.SDK_INT >= 16) {
|
||||
systemMemory = (mem.totalMem / 1024 / 1024) + " MiB total, "
|
||||
+ (mem.availMem / 1024 / 1204) + " MiB free, "
|
||||
+ (mem.threshold / 1024 / 1024) + " MiB threshold";
|
||||
} else {
|
||||
systemMemory = (mem.availMem / 1024 / 1204) + " MiB free, "
|
||||
+ (mem.threshold / 1024 / 1024) + " MiB threshold";
|
||||
}
|
||||
customData.put("System memory", systemMemory);
|
||||
|
||||
// Virtual machine memory
|
||||
|
||||
@@ -218,17 +218,11 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
});
|
||||
|
||||
if (SDK_INT < 27) {
|
||||
// remove System Default Theme option from preference entries
|
||||
// as it is not functional on this API anyway
|
||||
// remove System Default Theme option
|
||||
List<CharSequence> entries =
|
||||
new ArrayList<>(Arrays.asList(theme.getEntries()));
|
||||
entries.remove(getString(R.string.pref_theme_system));
|
||||
theme.setEntries(entries.toArray(new CharSequence[0]));
|
||||
// also remove corresponding value
|
||||
List<CharSequence> values =
|
||||
new ArrayList<>(Arrays.asList(theme.getEntryValues()));
|
||||
values.remove(getString(R.string.pref_theme_system_value));
|
||||
theme.setEntryValues(values.toArray(new CharSequence[0]));
|
||||
}
|
||||
if (IS_DEBUG_BUILD) {
|
||||
findPreference("pref_key_explode").setOnPreferenceClickListener(
|
||||
@@ -495,13 +489,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
Intent intent = new Intent(ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
||||
.putExtra(EXTRA_APP_PACKAGE, packageName)
|
||||
.putExtra(EXTRA_CHANNEL_ID, channelId);
|
||||
Context ctx = requireContext();
|
||||
if (intent.resolveActivity(ctx.getPackageManager()) != null) {
|
||||
startActivity(intent);
|
||||
} else {
|
||||
Toast.makeText(ctx, R.string.error_start_activity, LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
startActivity(intent);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -523,12 +511,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
else uri = Uri.parse(ringtoneUri);
|
||||
i.putExtra(EXTRA_RINGTONE_EXISTING_URI, uri);
|
||||
}
|
||||
if (i.resolveActivity(requireActivity().getPackageManager()) != null) {
|
||||
startActivityForResult(i, REQUEST_RINGTONE);
|
||||
} else {
|
||||
Toast.makeText(getContext(), R.string.cannot_load_ringtone,
|
||||
LENGTH_SHORT).show();
|
||||
}
|
||||
startActivityForResult(i, REQUEST_RINGTONE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -663,7 +646,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
} else {
|
||||
// The user chose a ringtone other than the default
|
||||
Ringtone r = RingtoneManager.getRingtone(getContext(), uri);
|
||||
if (r == null || "file".equals(uri.getScheme())) {
|
||||
if (r == null) {
|
||||
Toast.makeText(getContext(), R.string.cannot_load_ringtone,
|
||||
LENGTH_SHORT).show();
|
||||
} else {
|
||||
|
||||
@@ -64,6 +64,12 @@ public abstract class BaseMessageFragment extends BaseFragment
|
||||
@StringRes
|
||||
protected abstract int getHintText();
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
message.showSoftKeyboard();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.briarproject.briar.android.threaded.ThreadListController.ThreadListDa
|
||||
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener;
|
||||
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||
import org.briarproject.briar.android.view.KeyboardAwareLinearLayout;
|
||||
import org.briarproject.briar.android.view.TextInputView;
|
||||
import org.briarproject.briar.android.view.TextSendController;
|
||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||
@@ -283,10 +284,14 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
|
||||
scrollToItemAtTop(item);
|
||||
} else {
|
||||
// wait with scrolling until keyboard opened
|
||||
textInput.setOnKeyboardShownListener(() -> {
|
||||
scrollToItemAtTop(item);
|
||||
textInput.setOnKeyboardShownListener(null);
|
||||
});
|
||||
textInput.addOnKeyboardShownListener(
|
||||
new KeyboardAwareLinearLayout.OnKeyboardShownListener() {
|
||||
@Override
|
||||
public void onKeyboardShown() {
|
||||
scrollToItemAtTop(item);
|
||||
textInput.removeOnKeyboardShownListener(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,6 +332,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
|
||||
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);
|
||||
|
||||
@@ -79,10 +79,6 @@ public abstract class BriarAdapter<T, V extends ViewHolder>
|
||||
this.items.addAll(items);
|
||||
}
|
||||
|
||||
public void replaceAll(Collection<T> items) {
|
||||
this.items.replaceAll(items);
|
||||
}
|
||||
|
||||
public void setItems(Collection<T> items) {
|
||||
this.items.beginBatchedUpdates();
|
||||
this.items.clear();
|
||||
|
||||
@@ -4,18 +4,13 @@ import android.support.annotation.ColorRes;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.design.widget.Snackbar.Callback;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE;
|
||||
import static android.support.v4.content.ContextCompat.getColor;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
@NotNullByDefault
|
||||
public class BriarSnackbarBuilder {
|
||||
@@ -35,24 +30,6 @@ public class BriarSnackbarBuilder {
|
||||
R.color.briar_button_text_positive));
|
||||
s.setAction(actionResId, onClickListener);
|
||||
}
|
||||
// Workaround for https://issuetracker.google.com/issues/64285517
|
||||
if (duration == LENGTH_INDEFINITE && SDK_INT < 21) {
|
||||
// Hide snackbar while it's opening to make bouncing less noticeable
|
||||
s.getView().setVisibility(INVISIBLE);
|
||||
s.addCallback(new Callback() {
|
||||
@Override
|
||||
public void onShown(Snackbar snackbar) {
|
||||
snackbar.getView().setVisibility(VISIBLE);
|
||||
// Request layout again in case snackbar is in wrong place
|
||||
snackbar.getView().requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismissed(Snackbar snackbar, int event) {
|
||||
snackbar.getView().setVisibility(INVISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -92,11 +92,9 @@ public class UiUtils {
|
||||
public static final float GREY_OUT = 0.5f;
|
||||
|
||||
public static void showSoftKeyboard(View view) {
|
||||
if (view.requestFocus()) {
|
||||
InputMethodManager imm = requireNonNull(getSystemService(
|
||||
view.getContext(), InputMethodManager.class));
|
||||
imm.showSoftInput(view, SHOW_IMPLICIT);
|
||||
}
|
||||
InputMethodManager imm = requireNonNull(
|
||||
getSystemService(view.getContext(), InputMethodManager.class));
|
||||
imm.showSoftInput(view, SHOW_IMPLICIT);
|
||||
}
|
||||
|
||||
public static void hideSoftKeyboard(View view) {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.briarproject.briar.android.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.design.widget.CoordinatorLayout;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
public class BriarRecyclerViewBehavior
|
||||
extends CoordinatorLayout.Behavior<BriarRecyclerView> {
|
||||
|
||||
public BriarRecyclerViewBehavior(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDependentViewChanged(CoordinatorLayout parent,
|
||||
BriarRecyclerView child, View dependency) {
|
||||
|
||||
// FIXME the below code works, but does not reset margin when snackbar is dismissed
|
||||
/*
|
||||
int margin = 0;
|
||||
if (dependency.isShown()) margin = dependency.getHeight();
|
||||
|
||||
// set snackbar height as bottom margin if it is shown
|
||||
CoordinatorLayout.LayoutParams params =
|
||||
(CoordinatorLayout.LayoutParams) child.getLayoutParams();
|
||||
params.setMargins(0, 0, 0, margin);
|
||||
child.setLayoutParams(params);
|
||||
|
||||
child.scrollToPosition(0);
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean layoutDependsOn(CoordinatorLayout parent,
|
||||
BriarRecyclerView child, View dependency) {
|
||||
// we only want to trigger the change
|
||||
// only when the changes is from a snackbar
|
||||
return dependency instanceof Snackbar.SnackbarLayout;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import android.widget.ProgressBar;
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public class CompositeSendButton extends FrameLayout {
|
||||
@@ -74,24 +75,33 @@ public class CompositeSendButton extends FrameLayout {
|
||||
if (showImageButton) {
|
||||
imageButton.setVisibility(VISIBLE);
|
||||
sendButton.setEnabled(false);
|
||||
sendButton.clearAnimation();
|
||||
sendButton.animate().alpha(0f).withEndAction(() -> {
|
||||
if (SDK_INT <= 15) {
|
||||
sendButton.setVisibility(INVISIBLE);
|
||||
imageButton.setEnabled(true);
|
||||
}).start();
|
||||
imageButton.clearAnimation();
|
||||
imageButton.animate().alpha(1f).start();
|
||||
} else {
|
||||
sendButton.clearAnimation();
|
||||
sendButton.animate().alpha(0f).withEndAction(() -> {
|
||||
sendButton.setVisibility(INVISIBLE);
|
||||
imageButton.setEnabled(true);
|
||||
}).start();
|
||||
imageButton.clearAnimation();
|
||||
imageButton.animate().alpha(1f).start();
|
||||
}
|
||||
} else {
|
||||
sendButton.setVisibility(VISIBLE);
|
||||
// enable/disable buttons right away to allow fast sending
|
||||
sendButton.setEnabled(sendEnabled);
|
||||
imageButton.setEnabled(false);
|
||||
sendButton.clearAnimation();
|
||||
sendButton.animate().alpha(1f).start();
|
||||
imageButton.clearAnimation();
|
||||
imageButton.animate().alpha(0f).withEndAction(() ->
|
||||
imageButton.setVisibility(INVISIBLE)
|
||||
).start();
|
||||
if (SDK_INT <= 15) {
|
||||
imageButton.setVisibility(INVISIBLE);
|
||||
} else {
|
||||
sendButton.clearAnimation();
|
||||
sendButton.animate().alpha(1f).start();
|
||||
imageButton.clearAnimation();
|
||||
imageButton.animate().alpha(0f).withEndAction(() ->
|
||||
imageButton.setVisibility(INVISIBLE)
|
||||
).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.vanniktech.emoji.EmojiEditText;
|
||||
import com.vanniktech.emoji.EmojiPopup;
|
||||
import com.vanniktech.emoji.RecentEmoji;
|
||||
|
||||
@@ -28,12 +28,10 @@ import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
||||
import static android.view.KeyEvent.KEYCODE_ENTER;
|
||||
import static android.view.inputmethod.EditorInfo.IME_ACTION_SEND;
|
||||
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
|
||||
import static java.lang.Character.isWhitespace;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
|
||||
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
|
||||
|
||||
public class EmojiTextInputView extends LinearLayout implements
|
||||
public class EmojiTextInputView extends KeyboardAwareLinearLayout implements
|
||||
TextWatcher {
|
||||
|
||||
@Inject
|
||||
@@ -42,16 +40,12 @@ public class EmojiTextInputView extends LinearLayout implements
|
||||
private final AppCompatImageButton emojiToggle;
|
||||
private final EmojiPopup emojiPopup;
|
||||
private final EditText editText;
|
||||
private final InputMethodManager imm;
|
||||
|
||||
@Nullable
|
||||
private TextInputListener listener;
|
||||
@Nullable
|
||||
private OnKeyboardShownListener keyboardShownListener;
|
||||
private int maxLength = Integer.MAX_VALUE;
|
||||
private boolean emptyTextAllowed = false;
|
||||
private boolean isEmpty = true;
|
||||
private boolean keyboardOpen = false;
|
||||
|
||||
public EmojiTextInputView(Context context) {
|
||||
this(context, null);
|
||||
@@ -85,6 +79,7 @@ public class EmojiTextInputView extends LinearLayout implements
|
||||
editText = findViewById(R.id.input_text);
|
||||
editText.setPadding(0, 0, paddingEnd, paddingBottom);
|
||||
if (maxLines > 0) editText.setMaxLines(maxLines);
|
||||
editText.setOnClickListener(v -> showSoftKeyboard());
|
||||
editText.addTextChangedListener(this);
|
||||
editText.setOnEditorActionListener((v, actionId, event) -> {
|
||||
if (actionId == IME_ACTION_SEND) {
|
||||
@@ -108,30 +103,18 @@ public class EmojiTextInputView extends LinearLayout implements
|
||||
// stuff we can't do in edit mode goes below
|
||||
if (isInEditMode()) {
|
||||
emojiPopup = null;
|
||||
imm = null;
|
||||
return;
|
||||
}
|
||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||
imm = (InputMethodManager) requireNonNull(o);
|
||||
|
||||
BriarApplication app =
|
||||
(BriarApplication) context.getApplicationContext();
|
||||
app.getApplicationComponent().inject(this);
|
||||
emojiPopup = EmojiPopup.Builder
|
||||
.fromRootView(getRootView())
|
||||
.fromRootView(this)
|
||||
.setRecentEmoji(recentEmoji)
|
||||
.setOnEmojiPopupShownListener(this::showKeyboardIcon)
|
||||
.setOnEmojiPopupDismissListener(this::showEmojiIcon)
|
||||
.setKeyboardAnimationStyle(R.style.emoji_fade_animation_style)
|
||||
.setOnSoftKeyboardOpenListener(this::onKeyboardOpened)
|
||||
.setOnSoftKeyboardCloseListener(this::onKeyboardClosed)
|
||||
.setIconColor(resolveColorAttribute(getContext(),
|
||||
R.attr.colorControlNormal))
|
||||
.build(editText);
|
||||
.build((EmojiEditText) editText);
|
||||
emojiToggle.setOnClickListener(v -> emojiPopup.toggle());
|
||||
editText.setOnClickListener(v -> {
|
||||
if (emojiPopup.isShowing()) emojiPopup.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -142,31 +125,19 @@ public class EmojiTextInputView extends LinearLayout implements
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before,
|
||||
int count) {
|
||||
if (emptyTextAllowed || listener == null) return;
|
||||
// Work out whether the trimmed text has become empty or non-empty
|
||||
if (isEmpty) {
|
||||
// We only need to check the characters that were added
|
||||
if (countLeadingWhitespace(s, start, count) < count) {
|
||||
isEmpty = false;
|
||||
listener.onTextIsEmptyChanged(false);
|
||||
}
|
||||
} else if (before > 0) {
|
||||
// Characters have been removed or replaced - check from the start
|
||||
int length = s.length();
|
||||
if (countLeadingWhitespace(s, 0, length) == length) {
|
||||
// Need to start at position 0 to change empty
|
||||
if (start != 0 || emptyTextAllowed || listener == null) return;
|
||||
if (s.length() == 0) {
|
||||
if (!isEmpty) {
|
||||
isEmpty = true;
|
||||
listener.onTextIsEmptyChanged(true);
|
||||
}
|
||||
} else if (isEmpty) {
|
||||
isEmpty = false;
|
||||
listener.onTextIsEmptyChanged(false);
|
||||
}
|
||||
}
|
||||
|
||||
private int countLeadingWhitespace(CharSequence s, int off, int len) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (!isWhitespace(s.charAt(off + i))) return i;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
@@ -247,10 +218,6 @@ public class EmojiTextInputView extends LinearLayout implements
|
||||
editText.setHint(hint);
|
||||
}
|
||||
|
||||
boolean isKeyboardOpen() {
|
||||
return keyboardOpen || imm.isFullscreenMode();
|
||||
}
|
||||
|
||||
private void showEmojiIcon() {
|
||||
emojiToggle.setImageResource(R.drawable.ic_emoji_toggle);
|
||||
}
|
||||
@@ -260,43 +227,22 @@ public class EmojiTextInputView extends LinearLayout implements
|
||||
}
|
||||
|
||||
void showSoftKeyboard() {
|
||||
if (editText.requestFocus()) imm.showSoftInput(editText, SHOW_IMPLICIT);
|
||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||
InputMethodManager imm = (InputMethodManager) requireNonNull(o);
|
||||
imm.showSoftInput(editText, SHOW_IMPLICIT);
|
||||
}
|
||||
|
||||
void hideSoftKeyboard() {
|
||||
if (emojiPopup.isShowing()) emojiPopup.dismiss();
|
||||
IBinder token = editText.getWindowToken();
|
||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||
InputMethodManager imm = (InputMethodManager) requireNonNull(o);
|
||||
imm.hideSoftInputFromWindow(token, 0);
|
||||
}
|
||||
|
||||
private void onKeyboardOpened(
|
||||
@SuppressWarnings("unused") int keyboardHeight) {
|
||||
keyboardOpen = true;
|
||||
if (keyboardShownListener != null)
|
||||
keyboardShownListener.onKeyboardShown();
|
||||
}
|
||||
|
||||
private void onKeyboardClosed() {
|
||||
if (imm.isFullscreenMode()) {
|
||||
onKeyboardOpened(0);
|
||||
return;
|
||||
}
|
||||
keyboardOpen = false;
|
||||
}
|
||||
|
||||
void setOnKeyboardShownListener(
|
||||
@Nullable OnKeyboardShownListener listener) {
|
||||
keyboardShownListener = listener;
|
||||
}
|
||||
|
||||
interface TextInputListener {
|
||||
void onTextIsEmptyChanged(boolean isEmpty);
|
||||
|
||||
void onSendEvent();
|
||||
}
|
||||
|
||||
public interface OnKeyboardShownListener {
|
||||
void onKeyboardShown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
Taken from Signal, licences under GPLv3
|
||||
*/
|
||||
|
||||
package org.briarproject.briar.android.view;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.content.Context.WINDOW_SERVICE;
|
||||
import static android.view.Surface.ROTATION_270;
|
||||
import static android.view.Surface.ROTATION_90;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
/**
|
||||
* RelativeLayout that, when a view container, will report back when it thinks
|
||||
* a soft keyboard has been opened and what its height would be.
|
||||
*/
|
||||
@UiThread
|
||||
public class KeyboardAwareLinearLayout extends LinearLayout {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(KeyboardAwareLinearLayout.class.getName());
|
||||
|
||||
private final Rect rect = new Rect();
|
||||
private final Set<OnKeyboardShownListener> shownListeners = new HashSet<>();
|
||||
private final int minKeyboardSize;
|
||||
private final int minCustomKeyboardSize;
|
||||
private final int defaultCustomKeyboardSize;
|
||||
private final int minCustomKeyboardTopMargin;
|
||||
private final int statusBarHeight;
|
||||
|
||||
private int viewInset;
|
||||
|
||||
private boolean keyboardOpen = false;
|
||||
private int rotation = -1;
|
||||
|
||||
public KeyboardAwareLinearLayout(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public KeyboardAwareLinearLayout(Context context,
|
||||
@Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public KeyboardAwareLinearLayout(Context context,
|
||||
@Nullable AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
rotation = getDeviceRotation();
|
||||
int statusBarRes = getResources()
|
||||
.getIdentifier("status_bar_height", "dimen", "android");
|
||||
minKeyboardSize =
|
||||
getResources().getDimensionPixelSize(R.dimen.min_keyboard_size);
|
||||
minCustomKeyboardSize = getResources()
|
||||
.getDimensionPixelSize(R.dimen.min_custom_keyboard_size);
|
||||
defaultCustomKeyboardSize = getResources()
|
||||
.getDimensionPixelSize(R.dimen.default_custom_keyboard_size);
|
||||
minCustomKeyboardTopMargin = getResources()
|
||||
.getDimensionPixelSize(R.dimen.min_custom_keyboard_top_margin);
|
||||
statusBarHeight = statusBarRes > 0 ?
|
||||
getResources().getDimensionPixelSize(statusBarRes) : 0;
|
||||
viewInset = getViewInset();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
updateRotation();
|
||||
updateKeyboardState();
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
private void updateRotation() {
|
||||
int oldRotation = rotation;
|
||||
rotation = getDeviceRotation();
|
||||
if (oldRotation != rotation) {
|
||||
LOG.info("Rotation changed");
|
||||
onKeyboardClose();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateKeyboardState() {
|
||||
if (isLandscape()) {
|
||||
if (keyboardOpen) onKeyboardClose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (viewInset == 0 && Build.VERSION.SDK_INT >= 21)
|
||||
viewInset = getViewInset();
|
||||
int availableHeight =
|
||||
getRootView().getHeight() - statusBarHeight - viewInset;
|
||||
getWindowVisibleDisplayFrame(rect);
|
||||
|
||||
int keyboardHeight = availableHeight - (rect.bottom - rect.top);
|
||||
|
||||
if (keyboardHeight > minKeyboardSize) {
|
||||
if (getKeyboardHeight() != keyboardHeight)
|
||||
setKeyboardPortraitHeight(keyboardHeight);
|
||||
if (!keyboardOpen) onKeyboardOpen(keyboardHeight);
|
||||
} else if (keyboardOpen) {
|
||||
onKeyboardClose();
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(21)
|
||||
private int getViewInset() {
|
||||
try {
|
||||
Field attachInfoField = View.class.getDeclaredField("mAttachInfo");
|
||||
attachInfoField.setAccessible(true);
|
||||
Object attachInfo = attachInfoField.get(this);
|
||||
if (attachInfo != null) {
|
||||
Field stableInsetsField =
|
||||
attachInfo.getClass().getDeclaredField("mStableInsets");
|
||||
stableInsetsField.setAccessible(true);
|
||||
Rect insets = (Rect) stableInsetsField.get(attachInfo);
|
||||
return insets.bottom;
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
LOG.log(WARNING,
|
||||
"field reflection error when measuring view inset", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
LOG.log(WARNING,
|
||||
"access reflection error when measuring view inset", e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected void onKeyboardOpen(int keyboardHeight) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("onKeyboardOpen(" + keyboardHeight + ")");
|
||||
keyboardOpen = true;
|
||||
|
||||
notifyShownListeners();
|
||||
}
|
||||
|
||||
protected void onKeyboardClose() {
|
||||
LOG.info("onKeyboardClose()");
|
||||
keyboardOpen = false;
|
||||
}
|
||||
|
||||
public boolean isKeyboardOpen() {
|
||||
return keyboardOpen;
|
||||
}
|
||||
|
||||
public int getKeyboardHeight() {
|
||||
return isLandscape() ? getKeyboardLandscapeHeight() :
|
||||
getKeyboardPortraitHeight();
|
||||
}
|
||||
|
||||
public boolean isLandscape() {
|
||||
int rotation = getDeviceRotation();
|
||||
return rotation == ROTATION_90 || rotation == ROTATION_270;
|
||||
}
|
||||
|
||||
private int getDeviceRotation() {
|
||||
WindowManager windowManager =
|
||||
(WindowManager) getContext().getSystemService(WINDOW_SERVICE);
|
||||
return requireNonNull(windowManager).getDefaultDisplay().getRotation();
|
||||
}
|
||||
|
||||
private int getKeyboardLandscapeHeight() {
|
||||
return Math.max(getHeight(), getRootView().getHeight()) / 2;
|
||||
}
|
||||
|
||||
private int getKeyboardPortraitHeight() {
|
||||
SharedPreferences prefs =
|
||||
PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
int keyboardHeight = prefs.getInt("keyboard_height_portrait",
|
||||
defaultCustomKeyboardSize);
|
||||
return clamp(keyboardHeight, minCustomKeyboardSize,
|
||||
getRootView().getHeight() - minCustomKeyboardTopMargin);
|
||||
}
|
||||
|
||||
private int clamp(int value, int min, int max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
private void setKeyboardPortraitHeight(int height) {
|
||||
SharedPreferences prefs =
|
||||
PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
prefs.edit().putInt("keyboard_height_portrait", height).apply();
|
||||
}
|
||||
|
||||
public void addOnKeyboardShownListener(OnKeyboardShownListener listener) {
|
||||
shownListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeOnKeyboardShownListener(
|
||||
OnKeyboardShownListener listener) {
|
||||
shownListeners.remove(listener);
|
||||
}
|
||||
|
||||
private void notifyShownListeners() {
|
||||
// Make a copy as listeners may remove themselves when called
|
||||
Set<OnKeyboardShownListener> listeners = new HashSet<>(shownListeners);
|
||||
for (OnKeyboardShownListener listener : listeners) {
|
||||
listener.onKeyboardShown();
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnKeyboardShownListener {
|
||||
void onKeyboardShown();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package org.briarproject.briar.android.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.design.widget.CoordinatorLayout;
|
||||
import android.support.design.widget.CoordinatorLayout.Behavior;
|
||||
import android.support.design.widget.CoordinatorLayout.LayoutParams;
|
||||
import android.support.design.widget.Snackbar.SnackbarLayout;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
/**
|
||||
* This behavior makes room for a snackbar at the bottom of the screen. The
|
||||
* proper solution is to use layout_dodgeInsetEdges="bottom", but when used on
|
||||
* a scrollable view that results in the view being pushed under the app bar
|
||||
* (see https://issuetracker.google.com/issues/116541304).
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public class SnackbarAwareBehavior<V extends View> extends Behavior<V> {
|
||||
|
||||
public SnackbarAwareBehavior(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDependentViewChanged(CoordinatorLayout parent,
|
||||
V child, View snackbar) {
|
||||
setMargin(child, snackbar.getHeight());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDependentViewRemoved(CoordinatorLayout parent,
|
||||
V child, View snackbar) {
|
||||
setMargin(child, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean layoutDependsOn(CoordinatorLayout parent,
|
||||
V child, View dependency) {
|
||||
return dependency instanceof SnackbarLayout;
|
||||
}
|
||||
|
||||
private void setMargin(V child, int margin) {
|
||||
LayoutParams params = (LayoutParams) child.getLayoutParams();
|
||||
params.setMargins(0, 0, 0, margin);
|
||||
child.setLayoutParams(params);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import android.widget.LinearLayout;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.view.EmojiTextInputView.OnKeyboardShownListener;
|
||||
import org.briarproject.briar.android.view.KeyboardAwareLinearLayout.OnKeyboardShownListener;
|
||||
|
||||
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
@@ -139,9 +139,13 @@ public class TextInputView extends LinearLayout {
|
||||
textInput.hideSoftKeyboard();
|
||||
}
|
||||
|
||||
public void setOnKeyboardShownListener(
|
||||
@Nullable OnKeyboardShownListener listener) {
|
||||
textInput.setOnKeyboardShownListener(listener);
|
||||
public void addOnKeyboardShownListener(OnKeyboardShownListener listener) {
|
||||
textInput.addOnKeyboardShownListener(listener);
|
||||
}
|
||||
|
||||
public void removeOnKeyboardShownListener(
|
||||
OnKeyboardShownListener listener) {
|
||||
textInput.removeOnKeyboardShownListener(listener);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,17 +29,13 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:passwordToggleEnabled="true">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
<EditText
|
||||
android:id="@+id/current_password_entry"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/current_password"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textPassword"
|
||||
android:maxLines="1"/>
|
||||
|
||||
<requestFocus/>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
@@ -53,12 +49,11 @@
|
||||
app:layout_constraintTop_toBottomOf="@id/current_password_entry_wrapper"
|
||||
app:passwordToggleEnabled="true">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
<EditText
|
||||
android:id="@+id/new_password_entry"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/choose_new_password"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textPassword"
|
||||
android:maxLines="1"/>
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
@@ -74,13 +69,12 @@
|
||||
app:layout_constraintTop_toBottomOf="@id/new_password_entry_wrapper"
|
||||
app:passwordToggleEnabled="true">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
<EditText
|
||||
android:id="@+id/new_password_confirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/confirm_new_password"
|
||||
android:imeOptions="actionDone"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textPassword"
|
||||
android:maxLines="1"/>
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
@@ -15,17 +15,14 @@
|
||||
app:errorEnabled="true"
|
||||
app:hintEnabled="false">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
<EditText
|
||||
android:id="@+id/createForumNameEntry"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/choose_forum_hint"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text|textCapSentences"
|
||||
android:maxLines="1"/>
|
||||
|
||||
<requestFocus/>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<Button
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
app:cardCornerRadius="0dp"
|
||||
app:cardUseCompatPadding="false">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
<EditText
|
||||
android:id="@+id/urlInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -26,14 +26,9 @@
|
||||
android:gravity="top"
|
||||
android:hint="@string/blogs_rss_feeds_import_hint"
|
||||
android:imeOptions="actionDone"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textUri"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:textColor="?android:attr/textColorPrimary">
|
||||
|
||||
<requestFocus/>
|
||||
|
||||
</android.support.design.widget.TextInputEditText>
|
||||
android:textColor="?android:attr/textColorPrimary"/>
|
||||
|
||||
</android.support.v7.widget.CardView>
|
||||
|
||||
|
||||
@@ -14,11 +14,7 @@
|
||||
android:gravity="bottom"
|
||||
app:buttonText="@string/blogs_publish_blog_post"
|
||||
app:fillHeight="true"
|
||||
app:hint="@string/blogs_write_blog_post_body_hint">
|
||||
|
||||
<requestFocus/>
|
||||
|
||||
</org.briarproject.briar.android.view.LargeTextInputView>
|
||||
app:hint="@string/blogs_write_blog_post_body_hint"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
tools:parentTag="android.widget.LinearLayout"
|
||||
tools:parentTag="org.briarproject.briar.android.view.KeyboardAwareLinearLayout"
|
||||
tools:showIn="@layout/fragment_reblog">
|
||||
|
||||
<android.support.v7.widget.AppCompatImageButton
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/set_contact_alias_hint"
|
||||
android:inputType="text|textCapWords"
|
||||
android:inputType="textPersonName"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="@dimen/text_size_medium"/>
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="org.briarproject.briar.android.view.SnackbarAwareBehavior"
|
||||
app:scrollToEnd="false"/>
|
||||
|
||||
<io.github.kobakei.materialfabspeeddial.FabSpeedDial
|
||||
@@ -20,8 +19,6 @@
|
||||
app:fab_fabRippleColor="@android:color/transparent"
|
||||
app:fab_menu="@menu/contact_list_actions"
|
||||
app:fab_miniFabTextBackground="@color/briar_accent"
|
||||
app:fab_miniFabTextColor="@android:color/white"
|
||||
app:layout_anchorGravity="bottom|right|end"
|
||||
app:layout_behavior="org.briarproject.briar.android.view.SnackbarAwareBehavior"/>
|
||||
app:fab_miniFabTextColor="@android:color/white"/>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
|
||||
@@ -15,17 +15,14 @@
|
||||
app:errorEnabled="true"
|
||||
app:hintEnabled="false">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
<EditText
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/groups_create_group_hint"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text|textCapSentences"
|
||||
android:maxLines="1"/>
|
||||
|
||||
<requestFocus/>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<Button
|
||||
|
||||
@@ -26,8 +26,7 @@
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
app:layout_constraintVertical_chainStyle="packed"/>
|
||||
app:layout_constraintVertical_bias="0.0"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stepOneText"
|
||||
@@ -75,14 +74,12 @@
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:src="@drawable/ic_nickname"
|
||||
app:layout_constraintBottom_toTopOf="@+id/nicknameIcon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHeight_max="256dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/stepOneText"
|
||||
tools:ignore="ContentDescription"/>
|
||||
@@ -121,7 +118,6 @@
|
||||
android:layout_marginTop="16dp"
|
||||
app:errorEnabled="true"
|
||||
app:hintEnabled="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/space"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -137,16 +133,6 @@
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<Space
|
||||
android:id="@+id/space"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/addButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHeight_default="wrap"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/contactNameLayout"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/addButton"
|
||||
style="@style/BriarButton"
|
||||
|
||||
@@ -31,9 +31,6 @@
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="textPassword"
|
||||
android:maxLines="1"/>
|
||||
|
||||
<requestFocus/>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<Button
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
android:maxLines="1"/>
|
||||
|
||||
<requestFocus/>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<Button
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
android:maxLines="1">
|
||||
|
||||
<requestFocus/>
|
||||
|
||||
</android.support.design.widget.TextInputEditText>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
@@ -16,11 +16,6 @@
|
||||
android:enabled="false"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_delete_all_messages"
|
||||
android:title="@string/delete_all_messages"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_social_remove_person"
|
||||
android:icon="@drawable/action_delete_white"
|
||||
|
||||
@@ -175,13 +175,6 @@
|
||||
<string name="step_1">واحدة</string>
|
||||
<!--This is a numeral indicating the second step in a series of screens-->
|
||||
<string name="step_2">2</string>
|
||||
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||
string will be used in a dialog button, so if the translation of this string is longer than 20
|
||||
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
||||
<!--This is a button for answering that two nicknames refer to different people. This string
|
||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">قم بتقديم جهات إتصالك</string>
|
||||
<string name="introduction_onboarding_text">يمكنك أن تقدم جهات إتصالك لبعضها البعض، فلا يحتاجون للمقابلة الشخصية ليتواصلوا عبر Briar (براير).</string>
|
||||
|
||||
@@ -118,9 +118,7 @@
|
||||
<string name="message_hint">Mesaj yazın</string>
|
||||
<string name="image_caption_hint">Mövzu əlavə edin (isteyə bağlı)</string>
|
||||
<string name="image_attach">Şəkil əlavə edin</string>
|
||||
<string name="image_attach_error">Şəkil (lər) əlavə etmək alınmadı</string>
|
||||
<string name="image_attach_error_too_big">Şəkilin həcmi böyükdür. Limit %d MB.</string>
|
||||
<string name="image_attach_error_invalid_mime_type">Şəkil formatı dəstəklənmir: %s</string>
|
||||
<string name="image_attach_error">Şəkil əlavə edilmədi</string>
|
||||
<string name="set_contact_alias">Kontakt adı dəyişdirin</string>
|
||||
<string name="set_contact_alias_hint">Əlaqə adı</string>
|
||||
<string name="set_alias_button">Dəyiş</string>
|
||||
@@ -140,7 +138,6 @@
|
||||
<string name="dialog_title_image_support">İndi bu kontakta şəkilləri göndərə bilərsiniz</string>
|
||||
<string name="dialog_message_image_support">Şəkilləri əlavə etmək üçün bu simvola toxunun.</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Yaxında kontakt əlavə etmək</string>
|
||||
<string name="face_to_face">Kontakta əlavə etmək istədiyiniz şəxslə tanış olmalısınız. Bu, hər kəsin kimliyinizi və ya mesajlarınızı gələcəkdə oxumasını maneə törədir.</string>
|
||||
<string name="continue_button">Davam et</string>
|
||||
<string name="try_again_button">Yenidən cəhd elə</string>
|
||||
@@ -158,56 +155,12 @@
|
||||
<string name="connection_error_explanation">Həmin Wi-Fi şəbəkəsinə qoşulduğunuzu yoxlayın.</string>
|
||||
<string name="connection_error_feedback">Bu problem davam edərsə, tətbiqin təkmilləşdirilməsinə kömək etmək üçün rəy göndərin.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Məsafədə kontakt əlavə etmək </string>
|
||||
<string name="add_contact_nearby_title">Yaxında kontakt əlavə etmək </string>
|
||||
<string name="add_contact_remotely_title">Məsafədə kontakt əlavə etmək </string>
|
||||
<string name="contact_name_hint">Niklə kontakt saxlayın</string>
|
||||
<string name="contact_link_intro">Kontaktın linkini buraya daxil edin</string>
|
||||
<string name="contact_link_hint">Kontaktın linki</string>
|
||||
<string name="paste_button">Yapışdır</string>
|
||||
<string name="add_contact_button">Kontankt əlavə et</string>
|
||||
<string name="copy_button">Kopyala</string>
|
||||
<string name="share_button">Paylaş</string>
|
||||
<string name="send_link_title">Link mübadiləsi</string>
|
||||
<string name="add_contact_choose_nickname">Ad seç</string>
|
||||
<string name="add_contact_choose_a_nickname">Ad daxil et</string>
|
||||
<string name="nickname_intro">Niklə kontakt saxlayın. Bunu ancaq siz görəcəksiniz. </string>
|
||||
<string name="your_link">Əlavə etmək istədiyiniz kontakt üçün bu linki verin</string>
|
||||
<string name="link_clip_label">Briar link</string>
|
||||
<string name="link_copied_toast">Link kopyalandı</string>
|
||||
<string name="adding_contact_error">Kontaktı əlavə edərkən səhv baş verdi.</string>
|
||||
<string name="pending_contact_requests_snackbar">Gözləyən kontakt sorğuları var. </string>
|
||||
<string name="pending_contact_requests">Kənara qoyulmuş kontak sorğuları</string>
|
||||
<string name="no_pending_contacts">Gözləyən kontakt yoxdur</string>
|
||||
<string name="add_contact_remote_connecting">Qoşulur...</string>
|
||||
<string name="waiting_for_contact_to_come_online">Kontaktı xəttdə gözləyin ...</string>
|
||||
<string name="connecting">Qoşulur...</string>
|
||||
<string name="adding_contact">Kontaktın əlavə etməsi... </string>
|
||||
<string name="adding_contact_failed">Kontakt əlavə etməsi alınmadı</string>
|
||||
<string name="dialog_title_remove_pending_contact">Silməyi təstiqlə</string>
|
||||
<string name="dialog_message_remove_pending_contact">Bu kontakt hələ də əlavə olunur. İndi onu çıxararsanız, əlavə olunmayacaq.</string>
|
||||
<string name="own_link_error">Kontaktınızın linkini daxil edin, özünüzü deyil</string>
|
||||
<string name="nickname_missing">Zəhmət olmasa adı daxil edin</string>
|
||||
<string name="invalid_link">Səhv link</string>
|
||||
<string name="unsupported_link">Bu link Briar-ın daha yeni versiyasından gəlir. Ən son versiyaya keçin və yenidən cəhd edin.</string>
|
||||
<string name="intent_own_link">Öz linkini açdın. Əlavə etmək istədiyiniz kontaktlardan birini istifadə edin!</string>
|
||||
<string name="missing_link">Zəhmət olmasa linki daxil edin</string>
|
||||
<!--This is a numeral indicating the first step in a series of screens-->
|
||||
<string name="step_1">1</string>
|
||||
<!--This is a numeral indicating the second step in a series of screens-->
|
||||
<string name="step_2">2</string>
|
||||
<string name="offline_state">İnternet bağlantısı yoxdur</string>
|
||||
<string name="duplicate_link_dialog_title">Dublikat Link</string>
|
||||
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||
string will be used in a dialog button, so if the translation of this string is longer than 20
|
||||
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
||||
<string name="same_person_button">Eyni şəxs</string>
|
||||
<!--This is a button for answering that two nicknames refer to different people. This string
|
||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||
<string name="different_person_button">Fərqli şəxs</string>
|
||||
<string name="pending_contact_updated_toast">Gözləyən kontakt yeniləndi</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">Kontaktınızı tanıtın</string>
|
||||
<string name="introduction_onboarding_text">Kontaktlarınızı bir-birinizə təqdim edə bilərsiniz, lakin Briar-a qoşulmaq üçün şəxsən görüşmək lazım deyil.</string>
|
||||
|
||||
@@ -1,501 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--Setup-->
|
||||
<string name="setup_title">Dobrodošli u Briar</string>
|
||||
<string name="setup_name_explanation">Vaše korisničko ime će biti prikazano pored sadržaja posta. Nemožete ga mijenjati nakon kreiranje računa.</string>
|
||||
<string name="setup_next">Dalje</string>
|
||||
<string name="setup_password_intro">Izaberite lozinku</string>
|
||||
<string name="setup_password_explanation">Vaš Briar račun je kriptovan i sačuvan na vašem uređaju, ne na serveru ili oblaku. Ako zaboravite šifru ili deinstalirate Briar, ne postoji način da povratite vaš račun.\n\nIzaberite dugačku lozinku koju je teško pogoditi, kao četiri nasumične riječi ili deset nasumičnih slova, brojeva i simbola.</string>
|
||||
<string name="setup_doze_title">Konekcije u pozadinskom modu</string>
|
||||
<string name="setup_doze_intro">Da bi primao poruke, Briar mora da ostane konektovan u pozadinskom modu.</string>
|
||||
<string name="setup_doze_explanation">Da bi primao poruke, Briar mora da ostane konektovan u pozadinskom modu. Molim vas da isključite optimizacije baterije kako bi Briar ostao konektovan.</string>
|
||||
<string name="setup_doze_button">Dozvolite konekcije</string>
|
||||
<string name="choose_nickname">Izaberite vaše korisničko ime</string>
|
||||
<string name="choose_password">Izaberite vašu lozinku</string>
|
||||
<string name="confirm_password">Potvrdite vašu lozinku</string>
|
||||
<string name="name_too_long">Ime je predugačko</string>
|
||||
<string name="password_too_weak">Lozinka nije dovoljno jaka</string>
|
||||
<string name="passwords_do_not_match">Lozinke se ne poklapaju</string>
|
||||
<string name="create_account_button">Kreirajte korisnički račun</string>
|
||||
<string name="more_info">Više informacija</string>
|
||||
<string name="don_t_ask_again">Ne pitaj ponovo</string>
|
||||
<string name="setup_huawei_text">Molim dotaknite dugme ispod i provjerite da je Briar zaštićen na \"Protected Apps\" listi.</string>
|
||||
<string name="setup_huawei_button">Zaštiti Briar</string>
|
||||
<string name="setup_huawei_help">Ako Briar nije dodat u listu zaštićenih aplikacija - protected apps, neće moći da radi u pozadinskom modu.</string>
|
||||
<string name="warning_dozed">%s nije mogao da se pokrene u pozadinskom modu</string>
|
||||
<!--Login-->
|
||||
<string name="enter_password">Lozinka</string>
|
||||
<string name="try_again">Pogrešna lozinka, probajte ponovo</string>
|
||||
<string name="sign_in_button">Prijavite se</string>
|
||||
<string name="forgotten_password">Zaboravio/la sam lozinku</string>
|
||||
<string name="dialog_title_lost_password">Izgubljena Lozinka</string>
|
||||
<string name="dialog_message_lost_password">Vaš Briar račun je kriptovan i sačuvan na vašem uređaju, ne na serveru ili oblaku, pa nemožemo resetovati vašu lozinku. Želite li da izbrišete račun i počnete iz početka?\n\nPažnja: Vaši identiteti, kontakti i poruke će biti premanentno izgubljene.</string>
|
||||
<string name="startup_failed_notification_title">Brirar nije mogao da se pokrene</string>
|
||||
<string name="startup_failed_notification_text">Dotaknite za više informacija.</string>
|
||||
<string name="startup_failed_activity_title">Briar neuspješno pokretanje</string>
|
||||
<string name="startup_failed_db_error">Iz nekog razloga, Briar baza podataka je nepovratno oštećena. Vaš račun, vaši podaci i svi kontakti su izgubljeni. Nažalost, morate reinstalirati Briar ili podesiti novi račun tako što ćete izabrati opciju \'ZaboraviIi ste lozinku?\' .</string>
|
||||
<string name="startup_failed_data_too_old_error">Vaš račun je napravljen sa starom verzijom aplikacije i nemože se otvoriti novom verzijom. Morate reinstalirati staru verziju ili podesiti novi račun tako što ćete izabrati opciju \'ZaboraviIi ste lozinku?\' kod unosa lozinke.</string>
|
||||
<string name="startup_failed_data_too_new_error">Ova verzija aplikacije je stara. Molim vas da aplikaciju ažurirate na posljednju verziju a zatim pokušate ponovo.</string>
|
||||
<string name="startup_failed_service_error">Briar nije mogao da pokrene potreban plugin-dodatak. 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_update">Krajnji datum testiranja je pomjeren. Vaš račun će sada isteći za %d dana.</string>
|
||||
<string name="expiry_date_reached">Ovaj softver nije više aktuelan.\nHvala na testiranju!</string>
|
||||
<string name="download_briar">Da bi nastavili koristiti Briar, molimo preuzmite verziju 1.0</string>
|
||||
<string name="create_new_account">Morate kreirati novi račun, ali možete koristiti isto korisničko ime.</string>
|
||||
<string name="download_briar_button">Preuzmite Briar 1.0</string>
|
||||
<string name="startup_open_database">Dekriptujem bazu...</string>
|
||||
<string name="startup_migrate_database">Ažuriram bazu...</string>
|
||||
<string name="startup_compact_database">Baza se kompaktuje...</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">Otvori navigacionu ladicu</string>
|
||||
<string name="nav_drawer_close_description">Zatvori navigacionu ladicu</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>
|
||||
<!--This is part of the main menu. The app will be locked when this is tapped.-->
|
||||
<string name="lock_button">Zaključaj aplikaciju</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="reminder_notification_title">Odjavljeni ste iz Briara</string>
|
||||
<string name="reminder_notification_text">Dotaknite da se prijavite nazad.</string>
|
||||
<string name="reminder_notification_channel_title">Briarov podsjetnik za prijavu</string>
|
||||
<string name="reminder_notification_dismiss">Obustavi</string>
|
||||
<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">Prikaži</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">Obriši</string>
|
||||
<string name="accept">Prihvati</string>
|
||||
<string name="decline">Odbij</string>
|
||||
<string name="options">Opcije</string>
|
||||
<string name="online">Online</string>
|
||||
<string name="offline">Offline</string>
|
||||
<string name="send">Poš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">Tekst koji ste unijeli je predugačak</string>
|
||||
<string name="show_onboarding">Otvori dijalog za Pomoć</string>
|
||||
<string name="fix">Popravi</string>
|
||||
<string name="help">Pomoć</string>
|
||||
<string name="sorry">Izvini</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">Nema kontakata za prikazivanje</string>
|
||||
<string name="no_contacts_action">Taknite + ikonu da dodate kontakt</string>
|
||||
<string name="date_no_private_messages">Nema poruka.</string>
|
||||
<string name="no_private_messages">Nema poruka za prikazivanje</string>
|
||||
<string name="message_hint">Ukucajte poruku</string>
|
||||
<string name="image_caption_hint">Dodaj naslov (opciono)</string>
|
||||
<string name="image_attach">Priloži fotografiju</string>
|
||||
<string name="image_attach_error">Nije moguće priložiti fotografiju/e</string>
|
||||
<string name="image_attach_error_too_big">Fotografija je prevelika. Limit je %d MB.</string>
|
||||
<string name="image_attach_error_invalid_mime_type">Fromat fotografije nije podržan: %s</string>
|
||||
<string name="set_contact_alias">Promijeni ime kontakta</string>
|
||||
<string name="set_contact_alias_hint">Ime kontakta</string>
|
||||
<string name="set_alias_button">Promijeni</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 je izbrisan</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">Vi</string>
|
||||
<string name="save_image">Sačuvaj fotografiju</string>
|
||||
<string name="dialog_title_save_image">Želite li sačuvati fotografiju?</string>
|
||||
<string name="dialog_message_save_image">Čuvanje ove fotografije će omogućiti drugim aplikacijama da joj pristupe.\n\nJeste li sigurni da želite da je sačuvate?</string>
|
||||
<string name="save_image_success">Fotografija je sačuvana</string>
|
||||
<string name="save_image_error">Nije moguće sačuvati fotografiju</string>
|
||||
<string name="dialog_title_no_image_support">Slike nisu dostupne</string>
|
||||
<string name="dialog_message_no_image_support">Briar ne podržava prložene fotografije. Nakon nadogradnje vidjet ćete drugu ikonu.</string>
|
||||
<string name="dialog_title_image_support">Sada možete slati fotografije ovom kontaktu</string>
|
||||
<string name="dialog_message_image_support">Dodirni ovu ikonu da priložiš fotografije</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Dodaj kontakt u blizini vas</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">Nastavi</string>
|
||||
<string name="try_again_button">Probajte ponovo</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 o kontaktu\u2026</string>
|
||||
<string name="contact_added_toast">Dodani kontakt: %s</string>
|
||||
<string name="contact_already_exists">Kontakt %s već postoji</string>
|
||||
<string name="qr_code_invalid">QR kod nije validan</string>
|
||||
<string name="qr_code_too_old">Skenirani QR kod pripada staroj verziji %s.\n\nMolimo vas da zatražite od vašeg kontakta da ažurira aplikaciju na posljednjiu verziju, a zatim pokušajte ponovo</string>
|
||||
<string name="qr_code_too_new">QR kod koji skenirate pripada novijoj verziji %s.\n\nMolimo vas nadogradite aplikaciju na posljednju verziju, a zatim pokušajte ponovo.</string>
|
||||
<string name="camera_error">Greska kamere</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_error_title">Nije moguće povezivanje sa vašim kontaktom</string>
|
||||
<string name="connection_error_explanation">Provjerite jeste li oboje povezani na ist Wi-Fi mrežu.</string>
|
||||
<string name="connection_error_feedback">Ako se problem ponavlja, Molimo vas <a href="feedback">pošaljite povratne informacije</a> kako bi pomogli poboljšanju aplikacije.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Dodaj udaljeni konktakt</string>
|
||||
<string name="add_contact_nearby_title">Dodaj kontakt koji je u blizini</string>
|
||||
<string name="add_contact_remotely_title">Dodaj udaljeni kontakt</string>
|
||||
<string name="contact_name_hint">Dodjeli kontaktu nadimak</string>
|
||||
<string name="contact_link_intro">Unesite link vašeg kontakta ovdje</string>
|
||||
<string name="contact_link_hint">Kontaktov link</string>
|
||||
<string name="paste_button">Zalepi</string>
|
||||
<string name="add_contact_button">Dodaj kontakt</string>
|
||||
<string name="copy_button">Kopiraj</string>
|
||||
<string name="share_button">Podijeli</string>
|
||||
<string name="send_link_title">Razmjena linkova</string>
|
||||
<string name="add_contact_choose_nickname">Izaberi korisničko ime</string>
|
||||
<string name="add_contact_choose_a_nickname">Unesi korisničko ime</string>
|
||||
<string name="nickname_intro">Dodjeli vašem kontaktu nadimak. Samo vi možete vidjeti dodjeljeni nadimak.</string>
|
||||
<string name="your_link">Dodjeli link kontaktu kojeg želite dodati</string>
|
||||
<string name="link_clip_label">Briar link</string>
|
||||
<string name="link_copied_toast">Link je kopiran</string>
|
||||
<string name="adding_contact_error">Došlo je do greške tokom dodavanja kontakta.</string>
|
||||
<string name="pending_contact_requests_snackbar">Postoje zahtjevi za kontaktima koji čekanju</string>
|
||||
<string name="pending_contact_requests">Zahtjevi za kontaktima na čekanju</string>
|
||||
<string name="no_pending_contacts">Nema Zahtjeva za kontaktima na čekanju</string>
|
||||
<string name="add_contact_remote_connecting">Konektovanje…</string>
|
||||
<string name="waiting_for_contact_to_come_online">Čekanje da kontakt bude online…</string>
|
||||
<string name="connecting">Konektovanje…</string>
|
||||
<string name="adding_contact">Dodavanje kontakta...</string>
|
||||
<string name="adding_contact_failed">Dodavanje kontakta nije uspjelo</string>
|
||||
<string name="dialog_title_remove_pending_contact">Potvrda Uklanjanja</string>
|
||||
<string name="dialog_message_remove_pending_contact">Ovaj se kontakt još dodaje. Ako ga sada uklonite, neće biti dodan.</string>
|
||||
<string name="own_link_error">Unesite link vašeg kontakta, a ne vaš vlastiti link.</string>
|
||||
<string name="nickname_missing">Molim vas unesite korisničko ime</string>
|
||||
<string name="invalid_link">Neispravan link</string>
|
||||
<string name="unsupported_link">Ovaj link dolazi sa nove verzije Briar-a. Molim vas ažurirajte aplikaciju na posljednju verziju, a zatim pokušajte ponovo.</string>
|
||||
<string name="intent_own_link">Otvorili ste vaš vlastiti link. Koristite link od kontakta kojeg želite dodati.</string>
|
||||
<string name="missing_link">Molim vas unesite link</string>
|
||||
<!--This is a numeral indicating the first step in a series of screens-->
|
||||
<string name="step_1">1</string>
|
||||
<!--This is a numeral indicating the second step in a series of screens-->
|
||||
<string name="step_2">2</string>
|
||||
<string name="adding_contact_slow_warning">Dodavanje ovog kontakta traje duže nego obično</string>
|
||||
<string name="adding_contact_slow_title">Nije moguće povezivanje sa kontaktom</string>
|
||||
<string name="adding_contact_slow_text">Dodavanje ovog kontakta traje duže nego obično.\n\nMolim vas provjerite da li je vaš kontakt dobio vaš link i dodao vas:</string>
|
||||
<string name="offline_state">Nema konekcije na internet</string>
|
||||
<string name="duplicate_link_dialog_title">Duliciran Link</string>
|
||||
<string name="duplicate_link_dialog_text_1">Već imate kontakta na čekanju sa ovim linkom: %s</string>
|
||||
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||
<string name="duplicate_link_dialog_text_2">Da li su %s i %s ista osoba?</string>
|
||||
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||
string will be used in a dialog button, so if the translation of this string is longer than 20
|
||||
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
||||
<string name="same_person_button">Ista osoba</string>
|
||||
<!--This is a button for answering that two nicknames refer to different people. This string
|
||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||
<string name="different_person_button">Različita osoba</string>
|
||||
<string name="duplicate_link_dialog_text_3">%s i %s poslsli su vam isti link.\n\nJedan od njih moguće da pokušava otkriti ko su vaši kontakti.\n\nNemojte im reći da ste primili isti link od druge osobe.</string>
|
||||
<string name="pending_contact_updated_toast">Ažuriranje kontakta je na čekanju</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, nije potrebno da se oni sreću licem u lice kako bi se povezali na Briar-u.</string>
|
||||
<string name="introduction_menu_item">Izvršite upoznavanje</string>
|
||||
<string name="introduction_activity_title">Izaberite kontakt</string>
|
||||
<string name="introduction_not_possible">Vi već imate započeto jedno upoznavanje ovim kontaktom. Molimo vas dozvolite da ono prvo završi. Ako ste vi ili vaš kontakt rijetko na vezi, ovo može da potraje.</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 ne zna, 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 %1$s.</string>
|
||||
<string name="introduction_response_accepted_sent_info">Prije nego što %1$s bude u vašim kontaktima, mora da prihvati i upoznavanje. Ovo može potrajati.</string>
|
||||
<string name="introduction_response_declined_sent">Odbili ste upoznavanje sa %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 odbija upoznavanje sa %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">Nema grupa za prikazivanje</string>
|
||||
<string name="groups_list_empty_action">Dotaknite + ikonu da kreirate grupu, ili pitajte vaše kontakte da podijele grupe sa vama</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_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">%s je pristupio-la grupi</string>
|
||||
<string name="groups_leave">Napusti 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 raspustiti ovu grupu?\n\nSvi ostali članovi neće moći nastaviti konverzaciju i možda neće primiti najnovije 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\nNe mož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 %1$s da se pridruži 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 %s.</string>
|
||||
<string name="groups_invitations_response_declined_sent">Odbili ste grupni poziv od %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">Nema foruma za prikazivanje</string>
|
||||
<string name="no_forums_action">Dotaknite + ikonu da kreirate forum, ili pitajte vaše kontakte da podijele forume sa vama</string>
|
||||
<string name="create_forum_title">Kreiraj forum</string>
|
||||
<string name="choose_forum_hint">Izaberite ime za vaš forum</string>
|
||||
<string name="create_forum_button">Kreiraj forum</string>
|
||||
<string name="forum_created_toast">Forum je kreiran</string>
|
||||
<string name="no_forum_posts">Nema postova za prikazivanje</string>
|
||||
<string name="no_posts">Nema postova</string>
|
||||
<string name="forum_new_message_hint">Novi post</string>
|
||||
<string name="forum_message_reply_hint">Novi odgovor</string>
|
||||
<string name="btn_reply">Odgovor</string>
|
||||
<string name="forum_leave">Napusti 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 ovaj forum?\n\nKontakti sa kojima ste podijelili ovaj forum možda prestanu dobijati novosti.</string>
|
||||
<string name="dialog_button_leave">Napusti</string>
|
||||
<string name="forum_left_toast">Napušten forum</string>
|
||||
<!--Forum Sharing-->
|
||||
<string name="forum_share_button">Podijeli forum</string>
|
||||
<string name="contacts_selected">Kontakti selektovani</string>
|
||||
<string name="activity_share_toolbar_header">Izaberite kontakte</string>
|
||||
<string name="no_contacts_selector">Nema kontakata za prikazivanje</string>
|
||||
<string name="no_contacts_selector_action">Molimo vas vratite se ovdje nakon dodavanja kontakta</string>
|
||||
<string name="forum_shared_snackbar">Forum je podijeljen sa selektovanim kontaktima</string>
|
||||
<string name="forum_share_message">Dodajte poruku (opciono)</string>
|
||||
<string name="forum_share_error">Došlo je do greške prilikom dijeljenja 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">Već ste prihvatili poziv u ovaj forum.\n\nPrihvatanje dodatnih poziva će učiniti vašu vezu sa forumom bržom i pouzdanijom.</string>
|
||||
<string name="forum_joined_toast">Forumu ste pristupili</string>
|
||||
<string name="forum_declined_toast">Poziv odbijen</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 forum 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">Nema postova za prikazivanje</string>
|
||||
<string name="read_more">pročitaj više</string>
|
||||
<string name="blogs_write_blog_post">Napiši Blog Post</string>
|
||||
<string name="blogs_write_blog_post_body_hint">Ukucajte vaš blog post</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">Nema postova za prikazivanje</string>
|
||||
<string name="blogs_feed_empty_state_action">Postovi od vaših kontakata i blogova na koje se prijavite će se prikazivati ovdje.\n\nDotaknite ikonu olovke da napišete post</string>
|
||||
<string name="blogs_remove_blog">Ukloni blog</string>
|
||||
<string name="blogs_remove_blog_dialog_message">Jeste li sigurni da želite da uklonite blog?\n\nPostovi će biti uklonjeni sa vašeg uređaja ali ne is uređaja drugih ljudi.\n\nKontakti kojima ste podijelili ovaj blog će možda prestati da dobijaju novosti.</string>
|
||||
<string name="blogs_remove_blog_ok">Ukloni</string>
|
||||
<string name="blogs_blog_removed">Blog je uklonjen</string>
|
||||
<string name="blogs_reblog_comment_hint">Dodajte komentar (opciono)</string>
|
||||
<string name="blogs_reblog_button">Rebloguj</string>
|
||||
<!--Blog Sharing-->
|
||||
<string name="blogs_sharing_share">Podijeli 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">Prijavljeni ste na blog</string>
|
||||
<string name="blogs_sharing_declined_toast">Poziv odbijen</string>
|
||||
<string name="sharing_status_blog">Bilo ko, ko se prijavi za ovaj blog, može blog da 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">Upravljanje 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 kanal?\n\nPostovi će biti uklonjeni sa vašeg uređaja ali ne is uređaja drugih ljudi.\n\nKontakti kojima ste podijelili ovaj blog će možda prestati da dobijaju novosti.</string>
|
||||
<string name="blogs_rss_remove_feed_ok">Ukloni</string>
|
||||
<string name="blogs_rss_feeds_manage_delete_error">Kanal nije bilo moguće ukloniti!</string>
|
||||
<string name="blogs_rss_feeds_manage_empty_state">Nema RSS kanala za prikazivanje\n\nDotaknite + ikonu da uvezete kanal</string>
|
||||
<string name="blogs_rss_feeds_manage_error">Došlo je do problema pri učitavanju vaših kanala. Probajte opet kasnije.</string>
|
||||
<!--Settings Display-->
|
||||
<string name="pref_language_title">Jezik i regija</string>
|
||||
<string name="pref_language_changed">Ovo podešavanje će stupiti na snagu kada restartujete Briar. Molim vas da se odjavite i restartujete Briar.</string>
|
||||
<string name="pref_language_default">Podrazumjevane postavke sistema</string>
|
||||
<string name="display_settings_title">Ekran</string>
|
||||
<string name="pref_theme_title">Tema</string>
|
||||
<string name="pref_theme_light">Svijetlost</string>
|
||||
<string name="pref_theme_dark">Tamna</string>
|
||||
<string name="pref_theme_auto">Automatski (Dnevno)</string>
|
||||
<string name="pref_theme_system">Podrazumjevane postavke sistema</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">Konektuj se preko Interneta (Tor)</string>
|
||||
<string name="tor_network_setting_automatic">Automatski prema na lokaciji</string>
|
||||
<string name="tor_network_setting_without_bridges">Koristi Tor bez mostova</string>
|
||||
<string name="tor_network_setting_with_bridges">Koristi Tor sa mostovima</string>
|
||||
<string name="tor_network_setting_never">Ne konektuj se</string>
|
||||
<!--How and when Tor will connect after Automatic: E.g. Don't connect (in China) or Use Tor with bridges (in Belarus)-->
|
||||
<string name="tor_network_setting_summary">Automatski: %1$s (za %2$s)</string>
|
||||
<string name="tor_mobile_data_title">Koristi mobilinu mrežu</string>
|
||||
<string name="tor_only_when_charging_title">Konektuj se preko Interneta (Tor) samo tokom punjenja uređaja</string>
|
||||
<string name="tor_only_when_charging_summary">Onemogući interent konekciju kada se uređaj napaja samo sa baterijie</string>
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">Sigurnost</string>
|
||||
<string name="pref_lock_title">Zaključavanje applikacije</string>
|
||||
<string name="pref_lock_summary">Koristi zaključavanje ekrana uređaja da bi zaštitili Briar dok ste prijavljeni</string>
|
||||
<string name="pref_lock_disabled_summary">Da bi koristili ovu mogućnost, podesite zaključavanje ekrana za vašem uređaju</string>
|
||||
<string name="pref_lock_timeout_title">Zaključavanje aplikacije zbog neaktivnosti</string>
|
||||
<!--The %s placeholder is replaced with the following time spans, e.g. 5 Minutes, 1 Hour-->
|
||||
<string name="pref_lock_timeout_summary">Kada se ne koristi Briar, automatski zaključaj poslije %s</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
<string name="pref_lock_timeout_1">1 minute</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
<string name="pref_lock_timeout_5">5 minuta</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
<string name="pref_lock_timeout_15">15 minuta</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
<string name="pref_lock_timeout_30">30 minuta</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
<string name="pref_lock_timeout_60">1 sata</string>
|
||||
<string name="pref_lock_timeout_never">Nikad</string>
|
||||
<string name="pref_lock_timeout_never_summary">Ne zaključavaj Briar automatski</string>
|
||||
<string name="change_password">Promijeni lozinku</string>
|
||||
<string name="current_password">Trenutna lozinka</string>
|
||||
<string name="choose_new_password">Nova lozinka</string>
|
||||
<string name="confirm_new_password">Potvrdi novu lozinku</string>
|
||||
<string name="password_changed">Lozinka 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="panic_setting_destructive_action">Destruktivne akcije</string>
|
||||
<string name="panic_setting_signout_title">Odjava</string>
|
||||
<string name="panic_setting_signout_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 trajno 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_sign_in_title">Podsjeti me da se prijavim</string>
|
||||
<string name="notify_sign_in_summary">Prikazi podsjetnik kada se telefon uključi ili kada se aplikacija ažurira</string>
|
||||
<string name="notify_private_messages_setting_title">Privatne poruke</string>
|
||||
<string name="notify_private_messages_setting_summary">Prikaži upozorenja za privatne poruke</string>
|
||||
<string name="notify_private_messages_setting_summary_26">Podesite upozorenja za 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_group_messages_setting_summary_26">Podesite 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_forum_posts_setting_summary_26">Podesite 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_blog_posts_setting_summary_26">Podesite upozorenja za blog postove</string>
|
||||
<string name="notify_vibration_setting">Vibriranje</string>
|
||||
<string name="notify_sound_setting">Zvuk</string>
|
||||
<string name="notify_sound_setting_default">Podrazumijevana melodija</string>
|
||||
<string name="notify_sound_setting_disabled">Ništa</string>
|
||||
<string name="choose_ringtone_title">Izaberi melodiju</string>
|
||||
<string name="cannot_load_ringtone">Ne mogu otvoriti melodiju</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 upozorenja</string>
|
||||
<string name="link_warning_intro">Upravo ćete otvoriti slijedeći link sa eksternom aplikacijom</string>
|
||||
<string name="link_warning_text">Ovo se može koristiti da se otkrije vaš identitet. Razmislite o tome vjerujete li osobi koja vam je poslala ovaj link i razmislite o otvaranju s Tor Browser-om.</string>
|
||||
<string name="link_warning_open_link">Otvori link</string>
|
||||
<!--Crash Reporter-->
|
||||
<string name="crash_report_title">Briar izvještaj o rušenju aplikacije</string>
|
||||
<string name="briar_crashed">Izvinite, Briar aplikacija se srušila.</string>
|
||||
<string name="not_your_fault">Greška nije do vas.</string>
|
||||
<string name="please_send_report">Molimo pomozite nam da napravimo bolji Briar tako što ćete nam poslati izvještaj o rušenju aplikacije.</string>
|
||||
<string name="report_is_encrypted">Obećavamo da je izvještaj kriptovan i sigurno poslat.</string>
|
||||
<string name="feedback_title">Povratne informacije</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 adresa (opciono)</string>
|
||||
<string name="include_debug_report_crash">Uključite anonimne podatke o rušenju</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 je sačuvan. Biće poslat slijedeći put kada se ulogujete u Briar.</string>
|
||||
<!--Sign Out-->
|
||||
<string name="progress_title_logout">Odjavite se iz Briara...</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">Detektovano je prekrivanje ekrana</string>
|
||||
<string name="screen_filter_body">Druga aplikacija precrtava preko Briara. Da bi vas zaštitio, Briar neće reagovati na dodir dok druga aplikacija precrtava preko ekrana.\n\nSlijedeće aplikacije mogu precrtavati preko:\n\n%1$s</string>
|
||||
<string name="screen_filter_allow">Dozvoli ovim aplikacijama da precrtavaju ekran</string>
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">Dozvole kamere</string>
|
||||
<string name="permission_camera_request_body">Da bi skenirao QR kod, Briaru je potreban pristup kameri.</string>
|
||||
<string name="permission_location_title">Dozvole lokacije</string>
|
||||
<string name="permission_location_request_body">Da bi otkrio Bluetooth uređaj, Brair treba douzvolu za pristup vašoj lokaciji.\n\nBriar neće snimati vašu lokaciju ili je dijeliti sa bilo kime.</string>
|
||||
<string name="permission_camera_location_title">Kamera i locationkacija</string>
|
||||
<string name="permission_camera_location_request_body">Da bi skenirao QR kod, Briar treba pristup vašoj kameri.\n\nDa bi otkrio Bluetooth uređaj, Brair treba douzvolu za pristup vašoj lokaciji.\n\nBriar neće snimati vašu lokaciju ili je dijeliti sa bilo kime.</string>
|
||||
<string name="permission_camera_denied_body">Uskratili ste pristup kameri, ali dodavanje kontakata zahtijeva pristup kameri.\n\nMolim vas da uzmete u obzir odobravanja pristupa.</string>
|
||||
<string name="qr_code">QR kod</string>
|
||||
<string name="show_qr_code_fullscreen">Prikazi QR kod na puni ekran</string>
|
||||
<!--App Locking-->
|
||||
<string name="lock_unlock">Otključaj Briar</string>
|
||||
<string name="lock_unlock_verbose">Unesite PIN uređaja, figuru ili lozinku</string>
|
||||
<string name="lock_unlock_fingerprint_description">Dodirnite senzor otiska sa registrovanim prstom da bi nastavili</string>
|
||||
<string name="lock_unlock_password">Koristi lozinku</string>
|
||||
<string name="lock_is_locked">Briar je zaključan</string>
|
||||
<string name="lock_tap_to_unlock">Dotakni da otključaš</string>
|
||||
<!--Screenshots-->
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_alice">Fata</string>
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_bob">Mujo</string>
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_carol">Suljo</string>
|
||||
<!--This is a message to be used in screenshots. Please use the same translation for Bob!-->
|
||||
<string name="screenshot_message_1">Zdravo Mujo!</string>
|
||||
<!--This is a message to be used in screenshots. Please use the same translation for Alice!-->
|
||||
<string name="screenshot_message_2">Zdravo Fato! Hvala što mi govoriš o Briar-u!</string>
|
||||
<!--This is a message to be used in screenshots.-->
|
||||
<string name="screenshot_message_3">Nema problema, nadam se da ti se sviđa 😀</string>
|
||||
</resources>
|
||||
@@ -48,7 +48,7 @@
|
||||
<string name="download_briar_button">Descarrega Briar 1.0</string>
|
||||
<string name="startup_open_database">S\'està desxifrant la base de dades...</string>
|
||||
<string name="startup_migrate_database">S\'està actualitzant la base de dades...</string>
|
||||
<string name="startup_compact_database">S\'està compactant la base de dades...</string>
|
||||
<string name="startup_compact_database">Compactant la base de dades...</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">Obre el calaix de navegació</string>
|
||||
<string name="nav_drawer_close_description">Tanca el calaix de navegació</string>
|
||||
@@ -116,11 +116,9 @@
|
||||
<string name="date_no_private_messages">Sense missatges.</string>
|
||||
<string name="no_private_messages">No hi ha cap missatge</string>
|
||||
<string name="message_hint">Escriviu un missatge</string>
|
||||
<string name="image_caption_hint">Afegiu una nota (opcional)</string>
|
||||
<string name="image_caption_hint">Afegeix una captura (opcional)</string>
|
||||
<string name="image_attach">Afegeix una imatge</string>
|
||||
<string name="image_attach_error">La imatge(s) no s\'ha pogut afegir </string>
|
||||
<string name="image_attach_error_too_big">La imatge és massa gran. El límit és %d MB.</string>
|
||||
<string name="image_attach_error_invalid_mime_type">El format de la imatge no s\'admet: %s</string>
|
||||
<string name="image_attach_error">La imatge no s\'ha pogut afegir </string>
|
||||
<string name="set_contact_alias">Canvia el nom del contacte</string>
|
||||
<string name="set_contact_alias_hint">Nom del contacte</string>
|
||||
<string name="set_alias_button">Canvia</string>
|
||||
@@ -129,19 +127,18 @@
|
||||
<string name="dialog_message_delete_contact">Segur que voleu suprimir aquest contacte i tots els missatges que us heu intercanviat?</string>
|
||||
<string name="contact_deleted_toast">S\'ha suprimit el contacte</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">Vós</string>
|
||||
<string name="save_image">Desa la imatge</string>
|
||||
<string name="you">Vos</string>
|
||||
<string name="save_image">Desa imatge</string>
|
||||
<string name="dialog_title_save_image">Voleu desar la imatge?</string>
|
||||
<string name="dialog_message_save_image">Si deseu aquesta imatge la resta d\'aplicacions també hi podran accedir.\n\nSegur que voleu desar-la?</string>
|
||||
<string name="dialog_message_save_image">Desant aquesta imatge es permetrà a altres aplicacions accedir-hi.\n\nConfirmeu que la voleu desar?</string>
|
||||
<string name="save_image_success">La imatge s\'ha desat</string>
|
||||
<string name="save_image_error">La imatge no s\'ha pogut desar </string>
|
||||
<string name="dialog_title_no_image_support">No hi ha cap imatge disponible</string>
|
||||
<string name="dialog_message_no_image_support">La versió de Briar del vostre contacte no permet afegir imatges.
|
||||
Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<string name="dialog_title_image_support">Ara ja podeu enviar imatges a aquest contacte</string>
|
||||
<string name="dialog_title_no_image_support">Cap imatge disponible</string>
|
||||
<string name="dialog_message_no_image_support">El vostre contacte de Briar encara no admet afegir imatges.
|
||||
Un cop s\'actualitzi ja li veureu una icona diferent </string>
|
||||
<string name="dialog_title_image_support">Ara podeu enviar imatges a aquest contacte</string>
|
||||
<string name="dialog_message_image_support">Premeu aquesta icona per afegir imatges.</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Afegeix un contacte proper</string>
|
||||
<string name="face_to_face">Heu de coincidir en el mateix lloc amb la persona que voleu afegir com a contacte.\n\nD\'aquesta manera evitareu que algú suplanti les vostres identitats o pugui llegir els vostres missatges en el futur.</string>
|
||||
<string name="continue_button">Continua</string>
|
||||
<string name="try_again_button">Torna-ho a provar</string>
|
||||
@@ -150,8 +147,8 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<string name="contact_added_toast">S\'ha afegit el contacte %s</string>
|
||||
<string name="contact_already_exists">El contacte %s ja existia</string>
|
||||
<string name="qr_code_invalid">El codi QR és invàlid</string>
|
||||
<string name="qr_code_too_old">El codi QR que heu escanejat prové d\'una versió antiga de %s.\n\nDemaneu al vostre contacte que l\'actualitzi i torneu a provar-ho.</string>
|
||||
<string name="qr_code_too_new">El codi QR que heu escanejat prové d\'una versió de %s més nova que la vostra.\n\nActualitzeu la vostra aplicació i torneu a provar-ho.</string>
|
||||
<string name="qr_code_too_old">El codi QR que heu escanejat prové d\'una versió antiga de %s.\n\nDemaneu al vostre contacte que actualitzi a l\'última versió i torneu a provar-ho.</string>
|
||||
<string name="qr_code_too_new">El codi QR que heu escanejat prové d\'una versió nova de %s.\n\nActualitzeu a l\'última versió i torneu a provar-ho.</string>
|
||||
<string name="camera_error">Error de la càmera</string>
|
||||
<string name="connecting_to_device">Connectant-se al dispositiu\u2026</string>
|
||||
<string name="authenticating_with_device">Autenticant-se amb el dispositiu\u2026</string>
|
||||
@@ -159,66 +156,14 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<string name="connection_error_explanation">Comproveu que esteu connectats a la mateixa xarxa Wi-Fi.</string>
|
||||
<string name="connection_error_feedback">Si aquest problema persisteix, <a href="feedback">envieu-nos un comentari</a> per ajudar-nos a millorar l\'aplicació.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Afegeix un contacte llunyà</string>
|
||||
<string name="add_contact_nearby_title">Afegeix un contacte proper</string>
|
||||
<string name="add_contact_remotely_title">Afegeix un contacte llunyà</string>
|
||||
<string name="contact_name_hint">Doneu un sobrenom al contacte</string>
|
||||
<string name="contact_link_intro">Escriviu l\'enllaç del vostre contacte</string>
|
||||
<string name="contact_link_hint">Enllaç del contacte</string>
|
||||
<string name="paste_button">Enganxa</string>
|
||||
<string name="add_contact_button">Afegiu un contacte</string>
|
||||
<string name="paste_button">Pegar</string>
|
||||
<string name="copy_button">Copia</string>
|
||||
<string name="share_button">Comparteix</string>
|
||||
<string name="send_link_title">Intercanvieu els enllaços</string>
|
||||
<string name="add_contact_choose_nickname">Trieu un sobrenom</string>
|
||||
<string name="add_contact_choose_a_nickname">Escriviu un sobrenom</string>
|
||||
<string name="nickname_intro">Doneu un sobrenom al contacte. Només vós podreu veure\'l.</string>
|
||||
<string name="your_link">Lliureu aquest enllaç al contacte que voleu afegir</string>
|
||||
<string name="link_clip_label">Enllaç de Briar</string>
|
||||
<string name="link_copied_toast">L\'enllaç s\'ha copiat</string>
|
||||
<string name="adding_contact_error">Hi ha hagut un error en afegir el contacte</string>
|
||||
<string name="pending_contact_requests_snackbar">Hi ha sol·licituds de contacte pendents</string>
|
||||
<string name="pending_contact_requests">Sol·licituds de contacte pendents</string>
|
||||
<string name="no_pending_contacts">No hi ha sol·licituds de contacte pendents</string>
|
||||
<string name="share_button">Compartir</string>
|
||||
<string name="add_contact_remote_connecting">S\'està connectant...</string>
|
||||
<string name="waiting_for_contact_to_come_online">S\'està esperant que el contacte estigui connectat…</string>
|
||||
<string name="connecting">S\'està connectant...</string>
|
||||
<string name="adding_contact">S\'està afegint el contacte…</string>
|
||||
<string name="adding_contact_failed">No s\'ha pogut afegir el contacte</string>
|
||||
<string name="dialog_title_remove_pending_contact">Confirmació de la supressió</string>
|
||||
<string name="dialog_message_remove_pending_contact">Aquest contacte encara està essent afegit. Si el suprimiu ara ja no s\'acabarà d\'afegir.</string>
|
||||
<string name="own_link_error">Escriviu l\'enllaç del vostre contacte i no el vostre enllaç</string>
|
||||
<string name="nickname_missing">Escriviu un sobrenom</string>
|
||||
<string name="invalid_link">Enllaç erroni</string>
|
||||
<string name="unsupported_link">Aquest enllaç prové d\'una versió de Biar més nova que la vostra.\n\nActualitzeu la vostra aplicació i torneu a provar-ho.</string>
|
||||
<string name="intent_own_link">Heu accedit al vostre propi enllaç. Feu servir el del contacte que voleu afegir!</string>
|
||||
<string name="missing_link">Escriviu un enllaç</string>
|
||||
<!--This is a numeral indicating the first step in a series of screens-->
|
||||
<string name="step_1">1</string>
|
||||
<!--This is a numeral indicating the second step in a series of screens-->
|
||||
<string name="step_2">2</string>
|
||||
<plurals name="contact_added_notification_text">
|
||||
<item quantity="one">S\'ha afegit el nou contacte </item>
|
||||
<item quantity="other">S\'han afegit %d nous contactes.</item>
|
||||
</plurals>
|
||||
<string name="adding_contact_slow_warning">Afegir aquest contacte està trigant més que de costum…</string>
|
||||
<string name="adding_contact_slow_title">No pot connectar-se al contacte</string>
|
||||
<string name="adding_contact_slow_text">Afegir aquest contacte està trigant més que de costum.\n\nComproveu que el contacte ha rebut el vostre enllaç i també us ha afegit.</string>
|
||||
<string name="offline_state">No hi ha connexió a Internet</string>
|
||||
<string name="duplicate_link_dialog_title">Enllaç duplicat</string>
|
||||
<string name="duplicate_link_dialog_text_1">Teniu una sol·licitud de contacte pendent amb l\'enllaç %s</string>
|
||||
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||
<string name="duplicate_link_dialog_text_2">%s i %s són la mateixa persona?</string>
|
||||
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||
string will be used in a dialog button, so if the translation of this string is longer than 20
|
||||
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
||||
<string name="same_person_button">Mateixa persona</string>
|
||||
<!--This is a button for answering that two nicknames refer to different people. This string
|
||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||
<string name="different_person_button">Persones diferents</string>
|
||||
<string name="duplicate_link_dialog_text_3">%s i %s us han enviat el mateix enllaç.\n\nUn d\'ells pot estar provant de descobrir quins són els vostres contactes.\n\nNo els hi digueu que heu rebut el mateix enllaç d\'algú altre.</string>
|
||||
<string name="pending_contact_updated_toast">S\'ha actualitzat la sol·licitud de contacte pendent</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">Presenteu als vostres contactes</string>
|
||||
<string name="introduction_onboarding_text">Podeu presentar als vostres contactes entre si, de manera que no necessitin trobar-se en persona per a relacionar-se a través de Briar.</string>
|
||||
@@ -407,12 +352,12 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<string name="bluetooth_setting_enabled">Sempre que hi hagi contactes propers</string>
|
||||
<string name="bluetooth_setting_disabled">Només quan s\'afegeixen contactes</string>
|
||||
<string name="tor_network_setting">Connecta a través d\'Internet (Tor)</string>
|
||||
<string name="tor_network_setting_automatic">Automàtic, basat en la posició</string>
|
||||
<string name="tor_network_setting_automatic">Automàtic basat en la ubicació</string>
|
||||
<string name="tor_network_setting_without_bridges">Usa Tor sense ponts</string>
|
||||
<string name="tor_network_setting_with_bridges">Usa Tor amb ponts</string>
|
||||
<string name="tor_network_setting_never">No et connectis</string>
|
||||
<!--How and when Tor will connect after Automatic: E.g. Don't connect (in China) or Use Tor with bridges (in Belarus)-->
|
||||
<string name="tor_network_setting_summary">Automàtic: %1$s (a %2$s)</string>
|
||||
<string name="tor_network_setting_summary">Automàtic: %1$s (en %2$s)</string>
|
||||
<string name="tor_mobile_data_title">Usa dades mòbils</string>
|
||||
<string name="tor_only_when_charging_title">Connecta a través d\'Internet (Tor) només quan s\'estigui carregant</string>
|
||||
<string name="tor_only_when_charging_summary">Desactiva la connexió a Internet quan el dispositiu estigui funcionant amb la bateria</string>
|
||||
@@ -421,7 +366,7 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<string name="pref_lock_title">Bloqueig de l\'aplicació</string>
|
||||
<string name="pref_lock_summary">Usa el bloqueig de pantalla del dispositiu per protegir Briar mentre inicieu la sessió</string>
|
||||
<string name="pref_lock_disabled_summary">Per utilitzar aquesta funció, configureu el bloqueig de pantalla en el vostre dispositiu</string>
|
||||
<string name="pref_lock_timeout_title">Temps d\'inactivitat abans de bloquejar-se</string>
|
||||
<string name="pref_lock_timeout_title">Temps d\'inactivitat per al bloqueig</string>
|
||||
<!--The %s placeholder is replaced with the following time spans, e.g. 5 Minutes, 1 Hour-->
|
||||
<string name="pref_lock_timeout_summary">Briar es bloquejarà automàticament després de %s de no usar-se.</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
@@ -485,7 +430,7 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<!--Link Warning-->
|
||||
<string name="link_warning_title">Avís d\'enllaç</string>
|
||||
<string name="link_warning_intro">L\'enllaç s\'obrirà amb una aplicació externa.</string>
|
||||
<string name="link_warning_text">Això podria usar-se per a identificar-vos. Penseu si us en refieu prou de la persona que us ha enviat l\'enllaç. Avalueu si us convindria obrir-lo amb un navegador que faciliti l\'anonimat com Tor Browser.</string>
|
||||
<string name="link_warning_text">Això es podria utilitzar-se per identificar-vos. Penseu si confieu en la persona que us ha enviat aquest enllaç i considereu obrir-lo amb el navegador Tor.</string>
|
||||
<string name="link_warning_open_link">Obre l\'enllaç</string>
|
||||
<!--Crash Reporter-->
|
||||
<string name="crash_report_title">Informe de fallida de Briar</string>
|
||||
@@ -512,10 +457,10 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">Permís de la càmera</string>
|
||||
<string name="permission_camera_request_body">Per escanejar el codi QR, Briar necessita accedir a la càmera.</string>
|
||||
<string name="permission_location_title">Permís d\'accés a la posició</string>
|
||||
<string name="permission_location_request_body">Per a descobrir dispositius Bluetooth, Briar necessita accedir a la vostra posició.\n\nBriar no guarda la vostra posició ni la comparteix amb ningú.</string>
|
||||
<string name="permission_camera_location_title">Permís d\'accés a la càmera i a la posició</string>
|
||||
<string name="permission_camera_location_request_body">Per escanejar el codi QR, Briar necessita accedir a la càmera.\n\nPer trobar dispositius Bluetooth, Briar necessita accedir a la vostra posició.\n\nBriar no guarda la vostra posició ni la comparteix amb ningú.</string>
|
||||
<string name="permission_location_title">Permís d\'accés a l\'ubicació</string>
|
||||
<string name="permission_location_request_body">Per trobar dispositius Bluetooth, Briar necessita la vostra ubicació.\n\nBriar no guarda la vostra ubicació ni la comparteix amb ningú.</string>
|
||||
<string name="permission_camera_location_title">Permís d\'accés a la càmera i l\'ubicació</string>
|
||||
<string name="permission_camera_location_request_body">Per escanejar el codi QR, Briar necessita accedir a la càmera.\n\nPer trobar dispositius Bluetooth, Briar necessita la vostra ubicació.\n\nBriar no guarda la vostra ubicació ni la comparteix amb ningú.</string>
|
||||
<string name="permission_camera_denied_body">Heu denegat l\'accés a la càmera però per afegir contactes cal utilitzar la càmera.\n\nRecomanem que permeteu l\'accés a la càmera.</string>
|
||||
<string name="qr_code">Codi QR</string>
|
||||
<string name="show_qr_code_fullscreen">Mostra el codi QR a pantalla completa</string>
|
||||
@@ -528,15 +473,15 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<string name="lock_tap_to_unlock">Toqueu per desbloquejar-lo</string>
|
||||
<!--Screenshots-->
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_alice">Alba</string>
|
||||
<string name="screenshot_alice">Alice</string>
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_bob">Pere</string>
|
||||
<string name="screenshot_bob">Bob</string>
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_carol">Queralt</string>
|
||||
<string name="screenshot_carol">Carol</string>
|
||||
<!--This is a message to be used in screenshots. Please use the same translation for Bob!-->
|
||||
<string name="screenshot_message_1">Hola Pere!</string>
|
||||
<string name="screenshot_message_1">Hola Bob!</string>
|
||||
<!--This is a message to be used in screenshots. Please use the same translation for Alice!-->
|
||||
<string name="screenshot_message_2">Hola Alba! Gràcies per recomanar-me Briar!</string>
|
||||
<string name="screenshot_message_2">Hola Alice! Gràcies per recomanar-me Briar!</string>
|
||||
<!--This is a message to be used in screenshots.-->
|
||||
<string name="screenshot_message_3">De res, espero que t\'agradi 😀</string>
|
||||
</resources>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<string name="passwords_do_not_match">Passwörter stimmen nicht überein</string>
|
||||
<string name="create_account_button">Konto anlegen</string>
|
||||
<string name="more_info">Weitere Informationen</string>
|
||||
<string name="don_t_ask_again">Nicht erneut nachfragen</string>
|
||||
<string name="don_t_ask_again">Frage nicht noch einmal</string>
|
||||
<string name="setup_huawei_text">Bitte tippe auf die Schaltfläche unten und stelle sicher, dass Briar unter \"Geschützte Apps\" angezeigt wird.</string>
|
||||
<string name="setup_huawei_button">Briar schützen</string>
|
||||
<string name="setup_huawei_help">Wenn Briar nicht zur Liste der geschützten Apps hinzugefügt wird, kann es nicht im Hintergrund ausgeführt werden.</string>
|
||||
@@ -44,11 +44,11 @@
|
||||
<string name="expiry_update">Das Ablaufdatum des Tests wurde verlängert. Dein Konto läuft nun in %d Tagen ab.</string>
|
||||
<string name="expiry_date_reached">Diese Software ist abgelaufen.\nDanke, dass du Briar getestet hast!</string>
|
||||
<string name="download_briar">Lade bitte Version 1.0 herunter, um Briar weiterhin zu nutzen.</string>
|
||||
<string name="create_new_account">Du wirst ein neues Konto erstellen müssen, wobei du jedoch wieder denselben Benutzernamen verwenden kannst.</string>
|
||||
<string name="create_new_account">Du wirst ein neues Konto erstellen müssen, wobei du jedoch wieder den selben Benutzernamen verwenden kannst.</string>
|
||||
<string name="download_briar_button">Lade Briar 1.0 herunter</string>
|
||||
<string name="startup_open_database">Datenbank wird entschlüsselt...</string>
|
||||
<string name="startup_migrate_database">Datenbank wird aktualisiert...</string>
|
||||
<string name="startup_compact_database">Datenbank wird komprimiert ...</string>
|
||||
<string name="startup_compact_database">Datenbank komprimieren...</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">Navigationsleiste öffnen</string>
|
||||
<string name="nav_drawer_close_description">Navigationsleiste schliessen</string>
|
||||
@@ -118,17 +118,10 @@
|
||||
<string name="message_hint">Nachricht eingeben</string>
|
||||
<string name="image_caption_hint">Überschrift hinzufügen (optional)</string>
|
||||
<string name="image_attach">Bild anhängen</string>
|
||||
<string name="image_attach_error">Bild(er) konnte(n) nicht angehängt werden</string>
|
||||
<string name="image_attach_error_too_big">Bild zu groß. Das Limit beträgt %d MB.</string>
|
||||
<string name="image_attach_error_invalid_mime_type">Nicht unterstütztes Bildformat: %s</string>
|
||||
<string name="image_attach_error">Konnte kein Bild anhängen</string>
|
||||
<string name="set_contact_alias">Kontaktnamen ändern</string>
|
||||
<string name="set_contact_alias_hint">Name des Kontakts</string>
|
||||
<string name="set_alias_button">Ändern</string>
|
||||
<string name="delete_all_messages">Alle Nachrichten löschen</string>
|
||||
<string name="dialog_title_delete_all_messages">Löschen der Nachrichten bestätigen</string>
|
||||
<string name="dialog_message_delete_all_messages">Bist Du sicher, dass Du alle Nachrichten löschen willst?</string>
|
||||
<string name="dialog_title_not_all_messages_deleted">Konnte nicht alle Nachrichten löschen</string>
|
||||
<string name="dialog_message_not_all_messages_deleted">Nachrichten mit Bezug auf offene Kontaktempfehlungen oder Einladungen können erst nach Abschluss dieser gelöscht werden.</string>
|
||||
<string name="delete_contact">Kontakt löschen</string>
|
||||
<string name="dialog_title_delete_contact">Löschen des Kontakts bestätigen</string>
|
||||
<string name="dialog_message_delete_contact">Bist du sicher, dass du diesen Kontakt und alle dazugehörigen Nachrichten löschen möchtest?</string>
|
||||
@@ -138,15 +131,13 @@
|
||||
<string name="save_image">Bild speichern</string>
|
||||
<string name="dialog_title_save_image">Bild speichern?</string>
|
||||
<string name="dialog_message_save_image">Gespeicherte Bilder können von vielen anderen Apps eingesehen werden.\n\nBist du sicher, dass du das Bild speichern möchtest?</string>
|
||||
<string name="save_image_success">Bild wurde gespeichert</string>
|
||||
<string name="save_image_error">Bild konnte nicht gespeichert werden</string>
|
||||
<string name="save_image_success">Bild wurde gespeichert.</string>
|
||||
<string name="save_image_error">Konnte Bild nicht speichern.</string>
|
||||
<string name="dialog_title_no_image_support">Bilder nicht verfügbar</string>
|
||||
<string name="dialog_message_no_image_support">Der Briar deines Kontakts unterstützt noch keine Bildanhänge. Sobald die Aktualisierung abgeschlossen ist, siehst du ein anderes Symbol.</string>
|
||||
<string name="dialog_title_image_support">Du kannst nun Bilder an diesen Kontakt senden</string>
|
||||
<string name="dialog_message_image_support">Tippe auf das Symbol, um Bilder anzuhängen.</string>
|
||||
<string name="messaging_too_many_attachments_toast">Nur die ersten %d Bilder werden übertragen werden</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Kontakt in der Nähe hinzufügen</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="try_again_button">Noch einmal versuchen</string>
|
||||
@@ -156,29 +147,23 @@
|
||||
<string name="contact_already_exists">Kontakt %s existiert bereits</string>
|
||||
<string name="qr_code_invalid">Der QR-Code ist ungültig</string>
|
||||
<string name="qr_code_too_old">Der QR-Code, den du eingescannt hast, kommt von einer älteren Version von %s.\n\nBitte deinen Kontakt, zur neuesten Version upzudaten, und probiere es erneut.</string>
|
||||
<string name="qr_code_too_new">Der QR-Code, den du gescannt hast, kommt von einer neueren Version.\n\nBitte aktualisiere %s auf die neueste Version und versuche es erneut.</string>
|
||||
<string name="qr_code_too_new">Der QR-Code, den du gescannt hast, kommt von einer neueren Version von .\n\nBitte update %s auf die neueste Version und versuche es erneut.</string>
|
||||
<string name="camera_error">Kamerafehler</string>
|
||||
<string name="connecting_to_device">Verbinde mit Gerät\u2026</string>
|
||||
<string name="authenticating_with_device">Authentifiziere Gerät\u2026</string>
|
||||
<string name="connection_error_title">Keine Verbindung zum Kontakt</string>
|
||||
<string name="connection_error_explanation">Überprüfe, ob ihr beide mit dem selben WLAN-Netzwerk verbunden seid.</string>
|
||||
<string name="connection_error_explanation">Überprüfe ob ihr beide mit dem selben Wi-Fi Netzwerk verbunden seit.</string>
|
||||
<string name="connection_error_feedback">Wenn das Problem weiterbesteht, hilf uns die App zu verbessern und <a href="feedback">schicke Feedback</a>.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Kontakt aus der Ferne hinzufügen</string>
|
||||
<string name="add_contact_nearby_title">Kontakt in der Nähe hinzufügen</string>
|
||||
<string name="add_contact_remotely_title">Kontakt aus der Ferne hinzufügen</string>
|
||||
<string name="contact_name_hint">Gib dem Kontakt einen Spitznamen</string>
|
||||
<string name="contact_link_intro">Hier den Link deines Kontakts einfügen</string>
|
||||
<string name="contact_link_hint">Kontakt Link</string>
|
||||
<string name="paste_button">Einfügen</string>
|
||||
<string name="add_contact_button">Kontakt hinzufügen</string>
|
||||
<string name="copy_button">Kopieren</string>
|
||||
<string name="share_button">Teilen</string>
|
||||
<string name="send_link_title">Links austauschen</string>
|
||||
<string name="add_contact_choose_nickname">Wähle einen Spitznamen</string>
|
||||
<string name="add_contact_choose_a_nickname">Gib einen Spitznamen ein</string>
|
||||
<string name="nickname_intro">Gib deinem Kontakt einen Spitznamen. Nur du kannst ihn sehen.</string>
|
||||
<string name="your_link">Gebe diesen Link zu dem Kontakt den du hinzufügen möchtest</string>
|
||||
<string name="nickname_intro">Gib deinem Kontakt einen Spitznamen. Nur du kannst es sehen.</string>
|
||||
<string name="link_clip_label">Briar Link</string>
|
||||
<string name="link_copied_toast">Link kopiert</string>
|
||||
<string name="adding_contact_error">Es gab einen Fehler beim Hinzufügen des Kontaktes.</string>
|
||||
@@ -186,18 +171,10 @@
|
||||
<string name="pending_contact_requests">Ausstehende Kontaktanfragen</string>
|
||||
<string name="no_pending_contacts">Keine ausstehenden Kontakte</string>
|
||||
<string name="add_contact_remote_connecting">Verbindung wird aufgebaut …</string>
|
||||
<string name="waiting_for_contact_to_come_online">Warte auf Online-Aktivität des Kontakts ...</string>
|
||||
<string name="connecting">Verbindung wird aufgebaut …</string>
|
||||
<string name="adding_contact">Kontakt hinzufügen...</string>
|
||||
<string name="adding_contact_failed">Hinzufügen von Kontakt ist fehlgeschlagen</string>
|
||||
<string name="dialog_title_remove_pending_contact">Entfernung bestätigen</string>
|
||||
<string name="dialog_message_remove_pending_contact">Dieser Kontakt befindet sich noch beim Hinzufügen. Wenn er jetzt entfernt wird, wird das Hinzufügen abgebrochen.</string>
|
||||
<string name="own_link_error">Gebe den Link deines Kontakts ein, nicht deinen eigenen.</string>
|
||||
<string name="nickname_missing">Bitte gib einen Spitznamen an</string>
|
||||
<string name="invalid_link">Ungültiger Link</string>
|
||||
<string name="unsupported_link">Dieser Link kommt von einer neueren Version von Briar. Bitte führe eine Aktualisierung auf die aktuelle Version durch und versuche es dann nochmal. </string>
|
||||
<string name="intent_own_link">Du hast deinen eigenen Link aufgerufen. Nutze einen Link von deinen Kontakten, die du hinzufügen möchtest!</string>
|
||||
<string name="missing_link">Bitte gebe einen Link ein</string>
|
||||
<!--This is a numeral indicating the first step in a series of screens-->
|
||||
<string name="step_1">1</string>
|
||||
<!--This is a numeral indicating the second step in a series of screens-->
|
||||
@@ -206,25 +183,6 @@
|
||||
<item quantity="one">Neuer Kontakt hinzugefügt.</item>
|
||||
<item quantity="other">%d neue Kontakte hinzugefügt.</item>
|
||||
</plurals>
|
||||
<string name="adding_contact_slow_warning">Das Hinzufügen des Kontakts dauert länger als normal</string>
|
||||
<string name="adding_contact_slow_title">Kann nicht zu Kontakt verbinden</string>
|
||||
<string name="adding_contact_slow_text">Das Hinzufügen des Kontakts dauert länger als normal.\n\nBitte kontrolliere, dass dein Kontakt deinen Link erhalten und eingegeben hat:</string>
|
||||
<string name="offline_state">Keine Internetverbindung</string>
|
||||
<string name="duplicate_link_dialog_title">Link duplizieren</string>
|
||||
<string name="duplicate_link_dialog_text_1">Du hast bereits einen Kontakt mit diesem Link ausstehend: %s</string>
|
||||
<string name="duplicate_link_dialog_text_1_contact">Du hast bereits einen Kontakt mit diesem Link: %s</string>
|
||||
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||
<string name="duplicate_link_dialog_text_2">Sind %s und %s dieselbe Person?</string>
|
||||
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||
string will be used in a dialog button, so if the translation of this string is longer than 20
|
||||
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
||||
<string name="same_person_button">Selbe Person</string>
|
||||
<!--This is a button for answering that two nicknames refer to different people. This string
|
||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||
<string name="different_person_button">Andere Person</string>
|
||||
<string name="duplicate_link_dialog_text_3">%s und %s haben Dir denselben Link geschickt.\n\nMöglicherweise versucht einer der beiden mehr über deine Kontakte zu erfahren.\n\nDu solltest deswegen niemandem sagen, dass Du diesen Link auch von jemand anderem erhalten hast.</string>
|
||||
<string name="pending_contact_updated_toast">Ausstehender Kontakt aktualisiert</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">Mache deine Kontakte untereinander bekannt</string>
|
||||
<string name="introduction_onboarding_text">Du kannst deine Kontakte untereinander bekannt machen. So können sie sich über Briar verbinden, ohne sich persönlich treffen zu müssen.</string>
|
||||
@@ -333,7 +291,7 @@
|
||||
<string name="forum_invitation_received">%1$s hat das Forum \"%2$s\" mit dir geteilt.</string>
|
||||
<string name="forum_invitation_sent">Du hast das Forum \"%1$s\" mit %2$s geteilt.</string>
|
||||
<string name="forum_invitations_title">Foreneinladungen</string>
|
||||
<string name="forum_invitation_exists">Du hast bereits eine Einladung zu diesem Forum angenommen.\n\nMehrere Einladungen anzunehmen wird deine Verbindung zu diesem Forum schneller und zuverlässiger machen.</string>
|
||||
<string name="forum_invitation_exists">Du hast bereits eine Einladung zu diesem Forum angenommen.\n\nMehrere Einladungen anzunehmen, wird deine Verbindung zu diesem Forum schneller und zuverlässiger machen.</string>
|
||||
<string name="forum_joined_toast">Dem Forum beigetreten</string>
|
||||
<string name="forum_declined_toast">Einladung abgelehnt</string>
|
||||
<string name="shared_by_format">Geteilt durch %s</string>
|
||||
@@ -414,8 +372,8 @@
|
||||
<string name="bluetooth_setting_disabled">Nur beim Hinzufügen von Kontakten</string>
|
||||
<string name="tor_network_setting">Über Internet (Tor) verbinden</string>
|
||||
<string name="tor_network_setting_automatic">Automatisch (standortbasiert)</string>
|
||||
<string name="tor_network_setting_without_bridges">Tor ohne Bridges nutzen</string>
|
||||
<string name="tor_network_setting_with_bridges">Tor mit Bridges nutzen</string>
|
||||
<string name="tor_network_setting_without_bridges">Tor ohne Bridges benutzen</string>
|
||||
<string name="tor_network_setting_with_bridges">Tor mit Bridges benutzen</string>
|
||||
<string name="tor_network_setting_never">Nicht verbinden</string>
|
||||
<!--How and when Tor will connect after Automatic: E.g. Don't connect (in China) or Use Tor with bridges (in Belarus)-->
|
||||
<string name="tor_network_setting_summary">Automatisch: %1$s (in %2$s)</string>
|
||||
@@ -425,11 +383,11 @@
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">Sicherheit</string>
|
||||
<string name="pref_lock_title">App-Sperre</string>
|
||||
<string name="pref_lock_summary">Systemsperrbildschirm benutzen um ein angemeldetes Briar zu schützen</string>
|
||||
<string name="pref_lock_summary">Systemsperrbildschirm benutzen um Briar zu schützen, während angemeldet</string>
|
||||
<string name="pref_lock_disabled_summary">Um dieses Feature zu benutzen, aktiviere den Systemsperrbildschirm</string>
|
||||
<string name="pref_lock_timeout_title">Zeitlimit der Inaktivität für die App-Sperr-Funktion</string>
|
||||
<!--The %s placeholder is replaced with the following time spans, e.g. 5 Minutes, 1 Hour-->
|
||||
<string name="pref_lock_timeout_summary">Bei Nichtnutzung Briar automatisch nach %s sperren</string>
|
||||
<string name="pref_lock_timeout_summary">Briar automatisch nach %s sperren, wenn unbenutzt.</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
<string name="pref_lock_timeout_1">1 Minute</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
@@ -521,7 +479,7 @@
|
||||
<string name="permission_location_title">Berechtigung Standort</string>
|
||||
<string name="permission_location_request_body">Um Bluetooth-Geräte zu finden, braucht Briar Zugriff auf deinen Standort.\n\nBriar speichert weder deinen Standort noch gibt es ihn an andere weiter.</string>
|
||||
<string name="permission_camera_location_title">Kamera und Standort</string>
|
||||
<string name="permission_camera_location_request_body">Um den QR-Code zu scannen, braucht Briar Zugriff auf die Kamera.\n\nUm Bluetooth-Geräte zu finden, braucht Briar Zugriff auf deinen Standort.\n\nBriar speichert weder deinen Standort noch gibt es ihn an andere weiter.</string>
|
||||
<string name="permission_camera_location_request_body">Um den QR-Code zu scannen, brauch Briar Zugriff auf die Kamera.\n\nUm Bluetooth-Geräte zu finden, braucht Briar Zugriff auf deinen Standort.\n\nBriar speichert weder deinen Standort noch gibt es ihn an andere weiter.</string>
|
||||
<string name="permission_camera_denied_body">Du hast den Zugriff auf die Kamera verweigert, aber das Hinzufügen von Kontakten erfordert die Verwendung der Kamera.\n\nBitte gewähre den Zugriff.</string>
|
||||
<string name="qr_code">QR-Code</string>
|
||||
<string name="show_qr_code_fullscreen">QR-Code im Vollbildmodus anzeigen</string>
|
||||
|
||||
@@ -118,17 +118,10 @@
|
||||
<string name="message_hint">Escribe un mensaje</string>
|
||||
<string name="image_caption_hint">Añade una breve descripción (opcional)</string>
|
||||
<string name="image_attach">Adjuntar imagen</string>
|
||||
<string name="image_attach_error">No se pudieron adjuntar las imágenes</string>
|
||||
<string name="image_attach_error_too_big">Imagen demasiado grande. El límite es de %d MB.</string>
|
||||
<string name="image_attach_error_invalid_mime_type">Formato de imagen no admitido: %s</string>
|
||||
<string name="image_attach_error">No se pudo adjuntar imagen</string>
|
||||
<string name="set_contact_alias">Cambiar nombre del contacto</string>
|
||||
<string name="set_contact_alias_hint">Nombre del contacto</string>
|
||||
<string name="set_alias_button">Cambiar</string>
|
||||
<string name="delete_all_messages">Eliminar todos los mensajes</string>
|
||||
<string name="dialog_title_delete_all_messages">Confirmar la eliminación del mensaje</string>
|
||||
<string name="dialog_message_delete_all_messages">¿Estás seguro de que deseas eliminar todos los mensajes?</string>
|
||||
<string name="dialog_title_not_all_messages_deleted">No se pudieron eliminar todos los mensajes.</string>
|
||||
<string name="dialog_message_not_all_messages_deleted">Los mensajes relacionados con presentaciones o invitaciones en curso no se pueden eliminar hasta que finalicen.</string>
|
||||
<string name="delete_contact">Eliminar contacto</string>
|
||||
<string name="dialog_title_delete_contact">Confirmar eliminación de contacto</string>
|
||||
<string name="dialog_message_delete_contact">¿Seguro que quieres eliminar este contacto y todos los mensajes intercambiados entre vosotros?</string>
|
||||
@@ -144,9 +137,7 @@
|
||||
<string name="dialog_message_no_image_support">El Briar de tu contacto todavía no soporta imágenes. Una vez que lo actualice verás un ícono diferente.</string>
|
||||
<string name="dialog_title_image_support">Ahora puedes enviar imágenes a este contacto.</string>
|
||||
<string name="dialog_message_image_support">Pulsa este ícono para adjuntar imágenes.</string>
|
||||
<string name="messaging_too_many_attachments_toast">Solo se enviarán las primeras %d imágenes</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Agregar Contacto Cercano</string>
|
||||
<string name="face_to_face">Debes reunirte con la persona a la que quieras añadir como contacto.\n\nHaciéndolo así prevendrás que nadie te suplante o pueda leer tus mensajes en el futuro.</string>
|
||||
<string name="continue_button">Continuar</string>
|
||||
<string name="try_again_button">Prueba de nuevo</string>
|
||||
@@ -164,67 +155,15 @@
|
||||
<string name="connection_error_explanation">Por favor comprobar que ambos estén conectados a la misma red Wi-Fi.</string>
|
||||
<string name="connection_error_feedback">Si este problema persiste, por favor <a href="feedback">envía tus comentarios</a> para ayudarnos a mejorar la aplicación.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Añadir un Contacto a Distancia</string>
|
||||
<string name="add_contact_nearby_title">Agregar un contacto cercano</string>
|
||||
<string name="add_contact_remotely_title">Añadir un contacto a distancia</string>
|
||||
<string name="contact_name_hint">Ponerle un sobrenombre al contacto</string>
|
||||
<string name="contact_link_intro">Introduzca el enlace de su contacto aquí</string>
|
||||
<string name="contact_link_hint">Enlace de contacto</string>
|
||||
<string name="paste_button">Pegar</string>
|
||||
<string name="add_contact_button">Agregar contacto</string>
|
||||
<string name="copy_button">Copiar</string>
|
||||
<string name="share_button">Compartir</string>
|
||||
<string name="send_link_title">Intercambiar enlaces</string>
|
||||
<string name="add_contact_choose_nickname">Elija un sobrenombre</string>
|
||||
<string name="add_contact_choose_a_nickname">Ingrese un sobrenombre</string>
|
||||
<string name="nickname_intro">Dele a su contacto un sobrenombre. Sólo usted puede verlo.</string>
|
||||
<string name="your_link">Déle este enlace al contacto que desea añadir</string>
|
||||
<string name="link_clip_label">Enlace Briar</string>
|
||||
<string name="link_copied_toast">Enlace copiado</string>
|
||||
<string name="adding_contact_error">Se ha producido un error al añadir el contacto.</string>
|
||||
<string name="pending_contact_requests_snackbar">Hay solicitudes de contacto pendientes</string>
|
||||
<string name="pending_contact_requests">Solicitudes de contacto pendientes</string>
|
||||
<string name="no_pending_contacts">No hay contactos pendientes</string>
|
||||
<string name="add_contact_remote_connecting">Conectando...</string>
|
||||
<string name="waiting_for_contact_to_come_online">Esperando que el contacto se ponga en línea....</string>
|
||||
<string name="connecting">Conectando...</string>
|
||||
<string name="adding_contact">Añadir contacto....</string>
|
||||
<string name="adding_contact_failed">La adición de un contacto ha fallado</string>
|
||||
<string name="dialog_title_remove_pending_contact">Confirmar la eliminación</string>
|
||||
<string name="dialog_message_remove_pending_contact">Este contacto todavía está siendo añadido. Si lo quita ahora, no se añadirá.</string>
|
||||
<string name="own_link_error">Introduzca el enlace de su contacto, no el suyo propio</string>
|
||||
<string name="nickname_missing">Por favor, introduzca un sobrenombre</string>
|
||||
<string name="invalid_link">Enlace inválido</string>
|
||||
<string name="unsupported_link">Este enlace proviene de una nueva versión de Briar. Por favor, actualice a la última versión e inténtelo de nuevo.</string>
|
||||
<string name="intent_own_link">Abrió su propio enlace. ¡Utilice el del contacto que quiere añadir!</string>
|
||||
<string name="missing_link">Por favor, introduzca un enlace</string>
|
||||
<!--This is a numeral indicating the first step in a series of screens-->
|
||||
<string name="step_1">1</string>
|
||||
<!--This is a numeral indicating the second step in a series of screens-->
|
||||
<string name="step_2">2</string>
|
||||
<plurals name="contact_added_notification_text">
|
||||
<item quantity="one">Nuevo contacto añadido.</item>
|
||||
<item quantity="other">%d nuevos contactos añadidos.</item>
|
||||
</plurals>
|
||||
<string name="adding_contact_slow_warning">Añadir este contacto tarda más de lo habitual</string>
|
||||
<string name="adding_contact_slow_title">No se puede conectar con el contacto</string>
|
||||
<string name="adding_contact_slow_text">Agregar este contacto está tomando más tiempo de lo normal.\n\nPor favor, compruebe que su contacto ha recibido su enlace y lo ha agregado:</string>
|
||||
<string name="offline_state">No hay conexión a Internet</string>
|
||||
<string name="duplicate_link_dialog_title">Duplicar enlace</string>
|
||||
<string name="duplicate_link_dialog_text_1">Ya tiene un contacto pendiente con este enlace: %s</string>
|
||||
<string name="duplicate_link_dialog_text_1_contact">Ya tienes un contacto con este enlace: %s</string>
|
||||
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||
<string name="duplicate_link_dialog_text_2">¿Son %s y %s la misma persona?</string>
|
||||
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||
string will be used in a dialog button, so if the translation of this string is longer than 20
|
||||
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
||||
<string name="same_person_button">Misma persona</string>
|
||||
<!--This is a button for answering that two nicknames refer to different people. This string
|
||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||
<string name="different_person_button">Diferente Persona</string>
|
||||
<string name="duplicate_link_dialog_text_3">%s y %s le envió el mismo enlace.\n\nUno de ellos puede estar tratando de descubrir quiénes son sus contactos.\n\nNo les diga que recibió el mismo enlace de otra persona.</string>
|
||||
<string name="pending_contact_updated_toast">Contacto pendiente actualizado</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">Presenta a tus contactos</string>
|
||||
<string name="introduction_onboarding_text">Presenta a tus contactos entre sí para ahorrarles encontrarse en persona para poder conectar mediante Briar.</string>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user