mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
65 Commits
1242-displ
...
priority-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4d0a221a3 | ||
|
|
b342759e06 | ||
|
|
93d99b0111 | ||
|
|
61e8d576d2 | ||
|
|
75c37a258e | ||
|
|
e964dae64b | ||
|
|
986d884b40 | ||
|
|
9557afabc6 | ||
|
|
ebe6b0d4c0 | ||
|
|
6e83fb7aef | ||
|
|
7a5ec2af12 | ||
|
|
ce1fde496c | ||
|
|
4b62c51fbf | ||
|
|
226ed3dd73 | ||
|
|
ab07dfb32c | ||
|
|
20c51c1aa4 | ||
|
|
232c2129a7 | ||
|
|
3620edbfc9 | ||
|
|
ad71d69149 | ||
|
|
f73f8ca7e7 | ||
|
|
16c701a71a | ||
|
|
8183b7b26a | ||
|
|
bd48c97eab | ||
|
|
925dc29a1f | ||
|
|
91777fd942 | ||
|
|
fbce8f81c7 | ||
|
|
d7c72c4d68 | ||
|
|
4faf535801 | ||
|
|
526ef7c6d8 | ||
|
|
798dff1a03 | ||
|
|
a4336776c9 | ||
|
|
418451cbd9 | ||
|
|
045fcfc5fa | ||
|
|
ef998577db | ||
|
|
a53345a3c9 | ||
|
|
ed8c09282d | ||
|
|
42197b5b5c | ||
|
|
374fc7035b | ||
|
|
9b796c7cc3 | ||
|
|
532edff642 | ||
|
|
6857252471 | ||
|
|
c229e19452 | ||
|
|
42bca09d16 | ||
|
|
9eacbfa659 | ||
|
|
f14e546dc6 | ||
|
|
684c64a1d9 | ||
|
|
6fdab959b1 | ||
|
|
c8487483ff | ||
|
|
a159b23dc0 | ||
|
|
5070a27a83 | ||
|
|
9ce73a6840 | ||
|
|
6e9928f20f | ||
|
|
b31d61afc5 | ||
|
|
5a99cb93cc | ||
|
|
d0bbebd25e | ||
|
|
4307d26606 | ||
|
|
0089c1ac6d | ||
|
|
2a7aac4930 | ||
|
|
a37b6d81ed | ||
|
|
1d09a6708a | ||
|
|
d3b6f484c8 | ||
|
|
039c6edb66 | ||
|
|
8b9f89eab2 | ||
|
|
1e2c17b170 | ||
|
|
dcd5e34c6b |
@@ -19,9 +19,7 @@ import javax.inject.Inject;
|
||||
import static android.content.Intent.ACTION_BATTERY_CHANGED;
|
||||
import static android.content.Intent.ACTION_POWER_CONNECTED;
|
||||
import static android.content.Intent.ACTION_POWER_DISCONNECTED;
|
||||
import static android.os.BatteryManager.BATTERY_STATUS_CHARGING;
|
||||
import static android.os.BatteryManager.BATTERY_STATUS_FULL;
|
||||
import static android.os.BatteryManager.EXTRA_STATUS;
|
||||
import static android.os.BatteryManager.EXTRA_PLUGGED;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
|
||||
@@ -48,9 +46,8 @@ class AndroidBatteryManager implements BatteryManager, Service {
|
||||
IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
|
||||
Intent i = appContext.registerReceiver(null, filter);
|
||||
if (i == null) return false;
|
||||
int status = i.getIntExtra(EXTRA_STATUS, -1);
|
||||
return status == BATTERY_STATUS_CHARGING ||
|
||||
status == BATTERY_STATUS_FULL;
|
||||
int status = i.getIntExtra(EXTRA_PLUGGED, 0);
|
||||
return status != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,6 +16,7 @@ public interface TorConstants {
|
||||
String PREF_TOR_NETWORK = "network2";
|
||||
String PREF_TOR_PORT = "port";
|
||||
String PREF_TOR_MOBILE = "useMobileData";
|
||||
String PREF_TOR_ONLY_WHEN_CHARGING = "onlyWhenCharging";
|
||||
|
||||
int PREF_TOR_NETWORK_AUTOMATIC = 0;
|
||||
int PREF_TOR_NETWORK_WITHOUT_BRIDGES = 1;
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.briarproject.bramble;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
|
||||
@NotNullByDefault
|
||||
public class PriorityExecutor {
|
||||
|
||||
private final Object lock = new Object();
|
||||
private final Executor delegate, high, low;
|
||||
@GuardedBy("lock")
|
||||
private final Queue<Runnable> highQueue = new LinkedList<>();
|
||||
@GuardedBy("lock")
|
||||
private final Queue<Runnable> lowQueue = new LinkedList<>();
|
||||
|
||||
@GuardedBy("lock")
|
||||
private boolean isTaskRunning = false;
|
||||
|
||||
public PriorityExecutor(Executor delegate) {
|
||||
this.delegate = delegate;
|
||||
high = r -> submit(r, true);
|
||||
low = r -> submit(r, false);
|
||||
}
|
||||
|
||||
public Executor getHighPriorityExecutor() {
|
||||
return high;
|
||||
}
|
||||
|
||||
public Executor getLowPriorityExecutor() {
|
||||
return low;
|
||||
}
|
||||
|
||||
private void submit(Runnable r, boolean isHighPriority) {
|
||||
Runnable wrapped = () -> {
|
||||
try {
|
||||
r.run();
|
||||
} finally {
|
||||
scheduleNext();
|
||||
}
|
||||
};
|
||||
synchronized (lock) {
|
||||
if (!isTaskRunning && highQueue.isEmpty() &&
|
||||
(isHighPriority || lowQueue.isEmpty())) {
|
||||
isTaskRunning = true;
|
||||
delegate.execute(wrapped);
|
||||
} else if (isHighPriority) {
|
||||
highQueue.add(wrapped);
|
||||
} else {
|
||||
lowQueue.add(wrapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleNext() {
|
||||
synchronized (lock) {
|
||||
Runnable next = highQueue.poll();
|
||||
if (next == null) next = lowQueue.poll();
|
||||
if (next == null) isTaskRunning = false;
|
||||
else delegate.execute(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,7 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_AUTOMATIC;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WITH_BRIDGES;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V2;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
|
||||
@@ -648,8 +649,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
if (s.getNamespace().equals(ID.getString())) {
|
||||
LOG.info("Tor settings updated");
|
||||
settings = s.getSettings();
|
||||
// Works around a bug introduced in Tor 0.3.4.8. Could be
|
||||
// replaced with callback.transportDisabled() when fixed.
|
||||
// Works around a bug introduced in Tor 0.3.4.8.
|
||||
// https://trac.torproject.org/projects/tor/ticket/28027
|
||||
// Could be replaced with callback.transportDisabled()
|
||||
// when fixed.
|
||||
disableNetwork();
|
||||
updateConnectionStatus(networkManager.getNetworkStatus(),
|
||||
batteryManager.isCharging());
|
||||
@@ -685,6 +688,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
int network = settings.getInt(PREF_TOR_NETWORK,
|
||||
PREF_TOR_NETWORK_AUTOMATIC);
|
||||
boolean useMobile = settings.getBoolean(PREF_TOR_MOBILE, true);
|
||||
boolean onlyWhenCharging =
|
||||
settings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING, false);
|
||||
boolean bridgesWork = circumventionProvider.doBridgesWork(country);
|
||||
boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC;
|
||||
|
||||
@@ -699,9 +704,12 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
if (!online) {
|
||||
LOG.info("Disabling network, device is offline");
|
||||
enableNetwork(false);
|
||||
} else if (!charging && onlyWhenCharging) {
|
||||
LOG.info("Disabling network, device is on battery");
|
||||
enableNetwork(false);
|
||||
} else if (network == PREF_TOR_NETWORK_NEVER ||
|
||||
(!useMobile && !wifi)) {
|
||||
LOG.info("Disabling network due to setting");
|
||||
LOG.info("Disabling network, device is using mobile data");
|
||||
enableNetwork(false);
|
||||
} else if (automatic && blocked && !bridgesWork) {
|
||||
LOG.info("Disabling network, country is blocked");
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.briarproject.bramble;
|
||||
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.concurrent.Executors.newSingleThreadExecutor;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class PriorityExecutorTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testHighPriorityTasksAreDelegatedInOrderOfSubmission()
|
||||
throws Exception {
|
||||
Executor delegate = newSingleThreadExecutor();
|
||||
PriorityExecutor priority = new PriorityExecutor(delegate);
|
||||
Executor high = priority.getHighPriorityExecutor();
|
||||
testTasksAreDelegatedInOrderOfSubmission(high);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLowPriorityTasksAreDelegatedInOrderOfSubmission()
|
||||
throws Exception {
|
||||
Executor delegate = newSingleThreadExecutor();
|
||||
PriorityExecutor priority = new PriorityExecutor(delegate);
|
||||
Executor low = priority.getLowPriorityExecutor();
|
||||
testTasksAreDelegatedInOrderOfSubmission(low);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHighPriorityTasksAreRunFirst() throws Exception {
|
||||
Executor delegate = newSingleThreadExecutor();
|
||||
PriorityExecutor priority = new PriorityExecutor(delegate);
|
||||
Executor high = priority.getHighPriorityExecutor();
|
||||
Executor low = priority.getLowPriorityExecutor();
|
||||
// Submit a task that will block, causing other tasks to be queued
|
||||
CountDownLatch cork = new CountDownLatch(1);
|
||||
low.execute(() -> {
|
||||
try {
|
||||
cork.await();
|
||||
} catch (InterruptedException e) {
|
||||
fail();
|
||||
}
|
||||
});
|
||||
// Submit alternating tasks to the high and low priority executors
|
||||
List<Integer> results = new Vector<>();
|
||||
CountDownLatch tasksFinished = new CountDownLatch(10);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
int result = i;
|
||||
Runnable task = () -> {
|
||||
results.add(result);
|
||||
tasksFinished.countDown();
|
||||
};
|
||||
if (i % 2 == 0) high.execute(task);
|
||||
else low.execute(task);
|
||||
}
|
||||
// Release the cork and wait for all tasks to finish
|
||||
cork.countDown();
|
||||
tasksFinished.await();
|
||||
// The high-priority tasks should have run before the low-priority tasks
|
||||
assertEquals(asList(0, 2, 4, 6, 8, 1, 3, 5, 7, 9), results);
|
||||
}
|
||||
|
||||
private void testTasksAreDelegatedInOrderOfSubmission(Executor e)
|
||||
throws Exception {
|
||||
List<Integer> results = new Vector<>();
|
||||
CountDownLatch tasksFinished = new CountDownLatch(10);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
int result = i;
|
||||
e.execute(() -> {
|
||||
results.add(result);
|
||||
tasksFinished.countDown();
|
||||
});
|
||||
}
|
||||
// Wait for all the tasks to finish
|
||||
tasksFinished.await();
|
||||
// The tasks should have run in the order they were submitted
|
||||
assertEquals(asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), results);
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,6 @@ dependencies {
|
||||
implementation "com.android.support:cardview-v7:$supportVersion"
|
||||
implementation "com.android.support:support-annotations:$supportVersion"
|
||||
implementation "com.android.support:exifinterface:$supportVersion"
|
||||
implementation "com.android.support:palette-v7:$supportVersion"
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
implementation "android.arch.lifecycle:extensions:1.1.1"
|
||||
|
||||
@@ -117,7 +116,7 @@ dependencies {
|
||||
implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
|
||||
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
||||
implementation 'com.google.zxing:core:3.3.3'
|
||||
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.12.4'
|
||||
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.14.0'
|
||||
implementation 'com.vanniktech:emoji-google:0.5.1'
|
||||
def glideVersion = '4.8.0'
|
||||
implementation("com.github.bumptech.glide:glide:$glideVersion") {
|
||||
@@ -135,7 +134,7 @@ dependencies {
|
||||
testImplementation project(path: ':bramble-core', configuration: 'testOutput')
|
||||
testImplementation 'org.robolectric:robolectric:4.0.1'
|
||||
testImplementation 'org.robolectric:shadows-support-v4:3.3.2'
|
||||
testImplementation 'org.mockito:mockito-core:2.13.0'
|
||||
testImplementation 'org.mockito:mockito-core:2.19.0'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation "org.jmock:jmock:2.8.2"
|
||||
testImplementation "org.jmock:jmock-junit4:2.8.2"
|
||||
|
||||
BIN
briar-android/src/androidTestOfficial/assets/animated.gif
Normal file
BIN
briar-android/src/androidTestOfficial/assets/animated.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 B |
BIN
briar-android/src/androidTestOfficial/assets/animated2.gif
Normal file
BIN
briar-android/src/androidTestOfficial/assets/animated2.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 317 B |
BIN
briar-android/src/androidTestOfficial/assets/error_high.jpg
Normal file
BIN
briar-android/src/androidTestOfficial/assets/error_high.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
BIN
briar-android/src/androidTestOfficial/assets/error_large.gif
Normal file
BIN
briar-android/src/androidTestOfficial/assets/error_large.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 B |
BIN
briar-android/src/androidTestOfficial/assets/error_wide.jpg
Normal file
BIN
briar-android/src/androidTestOfficial/assets/error_wide.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,279 @@
|
||||
package org.briarproject.briar.android.conversation;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.messaging.Attachment;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AttachmentControllerIntegrationTest {
|
||||
|
||||
private static final String smallKitten =
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/0/06/Kitten_in_Rizal_Park%2C_Manila.jpg/160px-Kitten_in_Rizal_Park%2C_Manila.jpg";
|
||||
private static final String originalKitten =
|
||||
"https://upload.wikimedia.org/wikipedia/commons/0/06/Kitten_in_Rizal_Park%2C_Manila.jpg";
|
||||
private static final String pngKitten =
|
||||
"https://upload.wikimedia.org/wikipedia/commons/c/c8/Young_cat.png";
|
||||
private static final String uberGif =
|
||||
"https://raw.githubusercontent.com/fuzzdb-project/fuzzdb/master/attack/file-upload/malicious-images/uber.gif";
|
||||
private static final String lottaPixel =
|
||||
"https://raw.githubusercontent.com/fuzzdb-project/fuzzdb/master/attack/file-upload/malicious-images/lottapixel.jpg";
|
||||
private static final String imageIoCrash =
|
||||
"https://www.landaire.net/img/crasher.png";
|
||||
private static final String gimpCrash =
|
||||
"https://gitlab.gnome.org/GNOME/gimp/uploads/75f5b7ed3b09b3f1c13f1f65bffe784f/31153c919d3aa634e8e6cff82219fe7352dd8a37.png";
|
||||
private static final String optiPngAfl =
|
||||
"https://sourceforge.net/p/optipng/bugs/64/attachment/test.gif";
|
||||
private static final String librawError =
|
||||
"https://www.libraw.org/sites/libraw.org/files/P1010671.JPG";
|
||||
|
||||
private final AttachmentDimensions dimensions = new AttachmentDimensions(
|
||||
100, 50, 200, 75, 300
|
||||
);
|
||||
private final MessageId msgId = new MessageId(getRandomId());
|
||||
|
||||
private final AttachmentController controller =
|
||||
new AttachmentController(null, dimensions);
|
||||
|
||||
@Test
|
||||
public void testSmallJpegImage() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(smallKitten);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
assertEquals(msgId, item.getMessageId());
|
||||
assertEquals(160, item.getWidth());
|
||||
assertEquals(240, item.getHeight());
|
||||
assertEquals(160, item.getThumbnailWidth());
|
||||
assertEquals(240, item.getThumbnailHeight());
|
||||
assertEquals("image/jpeg", item.getMimeType());
|
||||
assertEquals("jpg", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBigJpegImage() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(originalKitten);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
assertEquals(msgId, item.getMessageId());
|
||||
assertEquals(1728, item.getWidth());
|
||||
assertEquals(2592, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
assertEquals(dimensions.maxHeight, item.getThumbnailHeight());
|
||||
assertEquals("image/jpeg", item.getMimeType());
|
||||
assertEquals("jpg", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmallPngImage() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
|
||||
InputStream is = getUrlInputStream(pngKitten);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
assertEquals(msgId, item.getMessageId());
|
||||
assertEquals(737, item.getWidth());
|
||||
assertEquals(510, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
assertEquals(138, item.getThumbnailHeight());
|
||||
assertEquals("image/png", item.getMimeType());
|
||||
assertEquals("png", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUberGif() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(uberGif);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
assertEquals(1, item.getWidth());
|
||||
assertEquals(1, item.getHeight());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
|
||||
assertEquals("image/gif", item.getMimeType());
|
||||
assertEquals("gif", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLottaPixels() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(lottaPixel);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
assertEquals(64250, item.getWidth());
|
||||
assertEquals(64250, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailHeight());
|
||||
assertEquals("image/jpeg", item.getMimeType());
|
||||
assertEquals("jpg", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImageIoCrash() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(imageIoCrash);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
assertEquals(1184, item.getWidth());
|
||||
assertEquals(448, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
|
||||
assertEquals("image/png", item.getMimeType());
|
||||
assertEquals("png", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGimpCrash() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(gimpCrash);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
assertEquals(1, item.getWidth());
|
||||
assertEquals(1, item.getHeight());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
|
||||
assertEquals("image/gif", item.getMimeType());
|
||||
assertEquals("gif", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptiPngAfl() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(optiPngAfl);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
assertEquals(32, item.getWidth());
|
||||
assertEquals(32, item.getHeight());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
|
||||
assertEquals("image/gif", item.getMimeType());
|
||||
assertEquals("gif", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLibrawError() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(librawError);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
assertTrue(item.hasError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmallAnimatedGifMaxDimensions() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||
InputStream is = getAssetInputStream("animated.gif");
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
assertEquals(65535, item.getWidth());
|
||||
assertEquals(65535, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailHeight());
|
||||
assertEquals("image/gif", item.getMimeType());
|
||||
assertEquals("gif", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmallAnimatedGifHugeDimensions() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||
InputStream is = getAssetInputStream("animated2.gif");
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
assertEquals(10000, item.getWidth());
|
||||
assertEquals(10000, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailHeight());
|
||||
assertEquals("image/gif", item.getMimeType());
|
||||
assertEquals("gif", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmallGifLargeDimensions() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||
InputStream is = getAssetInputStream("error_large.gif");
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
assertEquals(16384, item.getWidth());
|
||||
assertEquals(16384, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailHeight());
|
||||
assertEquals("image/gif", item.getMimeType());
|
||||
assertEquals("gif", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHighError() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getAssetInputStream("error_high.jpg");
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
assertEquals(1, item.getWidth());
|
||||
assertEquals(10000, item.getHeight());
|
||||
assertEquals(dimensions.minWidth, item.getThumbnailWidth());
|
||||
assertEquals(dimensions.maxHeight, item.getThumbnailHeight());
|
||||
assertEquals("image/jpeg", item.getMimeType());
|
||||
assertEquals("jpg", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWideError() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getAssetInputStream("error_wide.jpg");
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
assertEquals(1920, item.getWidth());
|
||||
assertEquals(1, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
|
||||
assertEquals("image/jpeg", item.getMimeType());
|
||||
assertEquals("jpg", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
}
|
||||
|
||||
private InputStream getUrlInputStream(String url) throws IOException {
|
||||
return new URL(url).openStream();
|
||||
}
|
||||
|
||||
private InputStream getAssetInputStream(String name) throws IOException {
|
||||
AssetManager assets = InstrumentationRegistry.getContext().getAssets();
|
||||
return assets.open(name);
|
||||
}
|
||||
|
||||
public static byte[] getRandomBytes(int length) {
|
||||
byte[] b = new byte[length];
|
||||
new Random().nextBytes(b);
|
||||
return b;
|
||||
}
|
||||
|
||||
public static byte[] getRandomId() {
|
||||
return getRandomBytes(UniqueId.LENGTH);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.briarproject.briar.android.conversation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static android.support.test.InstrumentationRegistry.getInstrumentation;
|
||||
import static android.support.test.espresso.Espresso.onView;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.briarproject.briar.android.ViewActions.waitUntilMatches;
|
||||
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ConversationActivityNotSignedInTest {
|
||||
|
||||
@Rule
|
||||
public ActivityTestRule<ConversationActivity> testRule =
|
||||
new ActivityTestRule<>(ConversationActivity.class, false, false);
|
||||
|
||||
@Test
|
||||
public void openWithoutSignedIn() {
|
||||
Context targetContext = getInstrumentation().getTargetContext();
|
||||
Intent intent = new Intent(targetContext, ConversationActivity.class);
|
||||
intent.putExtra(CONTACT_ID, 1);
|
||||
testRule.launchActivity(intent);
|
||||
|
||||
onView(withText(R.string.sign_in_button))
|
||||
.perform(waitUntilMatches(isDisplayed()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
package="org.briarproject.briar"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
|
||||
@@ -29,7 +30,8 @@
|
||||
android:label="@string/app_name"
|
||||
android:logo="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/BriarTheme">
|
||||
android:theme="@style/BriarTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
|
||||
<receiver
|
||||
android:name="org.briarproject.briar.android.login.SignInReminderReceiver"
|
||||
@@ -117,7 +119,7 @@
|
||||
<activity
|
||||
android:name=".android.conversation.ImageActivity"
|
||||
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity"
|
||||
android:theme="@style/BriarTheme.Transparent.NoActionBar">
|
||||
android:theme="@style/BriarTheme.ActionBarOverlay">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.conversation.ConversationActivity"/>
|
||||
@@ -155,7 +157,7 @@
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
<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"
|
||||
@@ -398,7 +400,7 @@
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.panic.PanicResponderActivity"
|
||||
android:noHistory="true"
|
||||
android:theme="@style/Theme.AppCompat.NoActionBar">
|
||||
android:theme="@style/TranslucentTheme">
|
||||
<!-- this can never have launchMode singleTask or singleInstance! -->
|
||||
<intent-filter>
|
||||
<action android:name="info.guardianproject.panic.action.TRIGGER"/>
|
||||
@@ -408,12 +410,12 @@
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.logout.ExitActivity"
|
||||
android:theme="@style/Theme.AppCompat.NoActionBar">
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".android.logout.HideUiActivity"
|
||||
android:theme="@style/Theme.AppCompat.NoActionBar">
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -16,4 +16,6 @@ public interface BriarApplication {
|
||||
AndroidComponent getApplicationComponent();
|
||||
|
||||
SharedPreferences getDefaultSharedPreferences();
|
||||
|
||||
boolean isRunningInBackground();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.briarproject.briar.android;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManager.RunningAppProcessInfo;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
@@ -30,6 +32,8 @@ import java.util.logging.Handler;
|
||||
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 org.acra.ReportField.ANDROID_VERSION;
|
||||
@@ -79,6 +83,7 @@ public class BriarApplicationImpl extends Application
|
||||
Logger.getLogger(BriarApplicationImpl.class.getName());
|
||||
|
||||
private final CachingLogHandler logHandler = new CachingLogHandler();
|
||||
private final BackgroundMonitor backgroundMonitor = new BackgroundMonitor();
|
||||
|
||||
private AndroidComponent applicationComponent;
|
||||
private volatile SharedPreferences prefs;
|
||||
@@ -115,6 +120,9 @@ public class BriarApplicationImpl extends Application
|
||||
|
||||
applicationComponent = createApplicationComponent();
|
||||
EmojiManager.install(new GoogleEmojiProvider());
|
||||
|
||||
if (SDK_INT < 16)
|
||||
registerActivityLifecycleCallbacks(backgroundMonitor);
|
||||
}
|
||||
|
||||
protected AndroidComponent createApplicationComponent() {
|
||||
@@ -173,4 +181,15 @@ public class BriarApplicationImpl extends Application
|
||||
public SharedPreferences getDefaultSharedPreferences() {
|
||||
return prefs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunningInBackground() {
|
||||
if (SDK_INT >= 16) {
|
||||
RunningAppProcessInfo info = new RunningAppProcessInfo();
|
||||
ActivityManager.getMyMemoryState(info);
|
||||
return (info.importance != IMPORTANCE_FOREGROUND);
|
||||
} else {
|
||||
return backgroundMonitor.isRunningInBackground();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.briarproject.briar.android;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManager.RunningAppProcessInfo;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
@@ -35,7 +33,6 @@ import java.util.logging.Logger;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
||||
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||
import static android.app.NotificationManager.IMPORTANCE_NONE;
|
||||
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
@@ -74,6 +71,7 @@ public class BriarService extends Service {
|
||||
|
||||
@Nullable
|
||||
private BroadcastReceiver receiver = null;
|
||||
private BriarApplication app;
|
||||
|
||||
@Inject
|
||||
AndroidNotificationManager notificationManager;
|
||||
@@ -93,8 +91,8 @@ public class BriarService extends Service {
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
BriarApplication application = (BriarApplication) getApplication();
|
||||
application.getApplicationComponent().inject(this);
|
||||
app = (BriarApplication) getApplication();
|
||||
app.getApplicationComponent().inject(this);
|
||||
|
||||
LOG.info("Created");
|
||||
if (created.getAndSet(true)) {
|
||||
@@ -220,8 +218,8 @@ public class BriarService extends Service {
|
||||
public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
LOG.warning("Memory is low");
|
||||
// Clear the UI - this is done in onTrimMemory() if SDK_INT >= 16
|
||||
if (SDK_INT < 16) hideUi();
|
||||
// If we're not in the foreground, clear the UI to save memory
|
||||
if (app.isRunningInBackground()) hideUi();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -235,20 +233,16 @@ public class BriarService extends Service {
|
||||
LOG.info("Trim memory: near middle of LRU list");
|
||||
} else if (level == TRIM_MEMORY_COMPLETE) {
|
||||
LOG.info("Trim memory: near end of LRU list");
|
||||
} else if (SDK_INT >= 16) {
|
||||
if (level == TRIM_MEMORY_RUNNING_MODERATE) {
|
||||
LOG.info("Trim memory: running moderately low");
|
||||
} else if (level == TRIM_MEMORY_RUNNING_LOW) {
|
||||
LOG.info("Trim memory: running low");
|
||||
} else if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
|
||||
LOG.info("Trim memory: running critically low");
|
||||
// If we're not in the foreground, clear the UI to save memory
|
||||
RunningAppProcessInfo info = new RunningAppProcessInfo();
|
||||
ActivityManager.getMyMemoryState(info);
|
||||
if (info.importance != IMPORTANCE_FOREGROUND) hideUi();
|
||||
} else if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Trim memory: unknown level " + level);
|
||||
}
|
||||
} else if (level == TRIM_MEMORY_RUNNING_MODERATE) {
|
||||
LOG.info("Trim memory: running moderately low");
|
||||
} 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();
|
||||
} else if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Trim memory: unknown level " + level);
|
||||
}
|
||||
|
||||
@@ -3,22 +3,29 @@ package org.briarproject.briar.android;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||
import org.briarproject.briar.android.fragment.ErrorFragment;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
|
||||
import static org.briarproject.briar.android.BriarService.EXTRA_NOTIFICATION_ID;
|
||||
import static org.briarproject.briar.android.BriarService.EXTRA_START_RESULT;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class StartupFailureActivity extends BaseActivity implements
|
||||
BaseFragmentListener {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
setContentView(R.layout.activity_fragment_container);
|
||||
@@ -38,7 +45,7 @@ public class StartupFailureActivity extends BaseActivity implements
|
||||
// cancel notification
|
||||
if (notificationId > -1) {
|
||||
Object o = getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
NotificationManager nm = (NotificationManager) requireNonNull(o);
|
||||
nm.cancel(notificationId);
|
||||
}
|
||||
|
||||
@@ -66,7 +73,7 @@ public class StartupFailureActivity extends BaseActivity implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runOnDbThread(Runnable runnable) {
|
||||
public void runOnDbThread(@NonNull Runnable runnable) {
|
||||
throw new AssertionError("Deprecated and should not be used");
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ import org.briarproject.briar.android.introduction.IntroductionActivity;
|
||||
import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
|
||||
import org.briarproject.briar.android.keyagreement.ContactExchangeActivity;
|
||||
import org.briarproject.briar.android.keyagreement.ContactExchangeErrorFragment;
|
||||
import org.briarproject.briar.android.keyagreement.IntroFragment;
|
||||
import org.briarproject.briar.android.keyagreement.KeyAgreementActivity;
|
||||
import org.briarproject.briar.android.keyagreement.KeyAgreementFragment;
|
||||
import org.briarproject.briar.android.login.AuthorNameFragment;
|
||||
@@ -49,7 +48,6 @@ import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
|
||||
import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
|
||||
import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity;
|
||||
import org.briarproject.briar.android.privategroup.creation.CreateGroupFragment;
|
||||
import org.briarproject.briar.android.privategroup.creation.CreateGroupMessageFragment;
|
||||
import org.briarproject.briar.android.privategroup.creation.CreateGroupModule;
|
||||
import org.briarproject.briar.android.privategroup.creation.GroupInviteActivity;
|
||||
import org.briarproject.briar.android.privategroup.creation.GroupInviteFragment;
|
||||
@@ -70,10 +68,8 @@ import org.briarproject.briar.android.sharing.ForumInvitationActivity;
|
||||
import org.briarproject.briar.android.sharing.ForumSharingStatusActivity;
|
||||
import org.briarproject.briar.android.sharing.ShareBlogActivity;
|
||||
import org.briarproject.briar.android.sharing.ShareBlogFragment;
|
||||
import org.briarproject.briar.android.sharing.ShareBlogMessageFragment;
|
||||
import org.briarproject.briar.android.sharing.ShareForumActivity;
|
||||
import org.briarproject.briar.android.sharing.ShareForumFragment;
|
||||
import org.briarproject.briar.android.sharing.ShareForumMessageFragment;
|
||||
import org.briarproject.briar.android.sharing.SharingModule;
|
||||
import org.briarproject.briar.android.splash.SplashScreenActivity;
|
||||
import org.briarproject.briar.android.test.TestDataActivity;
|
||||
@@ -183,8 +179,6 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(CreateGroupFragment fragment);
|
||||
|
||||
void inject(CreateGroupMessageFragment fragment);
|
||||
|
||||
void inject(GroupListFragment fragment);
|
||||
|
||||
void inject(GroupInviteFragment fragment);
|
||||
@@ -195,20 +189,14 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(FeedFragment fragment);
|
||||
|
||||
void inject(IntroFragment fragment);
|
||||
|
||||
void inject(KeyAgreementFragment fragment);
|
||||
|
||||
void inject(ContactChooserFragment fragment);
|
||||
|
||||
void inject(ShareForumFragment fragment);
|
||||
|
||||
void inject(ShareForumMessageFragment fragment);
|
||||
|
||||
void inject(ShareBlogFragment fragment);
|
||||
|
||||
void inject(ShareBlogMessageFragment fragment);
|
||||
|
||||
void inject(IntroductionMessageFragment fragment);
|
||||
|
||||
void inject(SettingsFragment fragment);
|
||||
|
||||
@@ -15,6 +15,8 @@ import android.view.ViewGroup.LayoutParams;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.AndroidComponent;
|
||||
import org.briarproject.briar.android.BriarApplication;
|
||||
@@ -51,6 +53,8 @@ import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOT
|
||||
* Warning: Some activities don't extend {@link BaseActivity}.
|
||||
* E.g. {@link DevReportActivity}
|
||||
*/
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public abstract class BaseActivity extends AppCompatActivity
|
||||
implements DestroyableContext, OnTapFilteredListener {
|
||||
|
||||
@@ -77,6 +81,17 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
// create the ActivityComponent *before* calling super.onCreate()
|
||||
// because it already attaches fragments which need access
|
||||
// to the component for their own injection
|
||||
AndroidComponent applicationComponent =
|
||||
((BriarApplication) getApplication()).getApplicationComponent();
|
||||
activityComponent = DaggerActivityComponent.builder()
|
||||
.androidComponent(applicationComponent)
|
||||
.activityModule(getActivityModule())
|
||||
.forumModule(getForumModule())
|
||||
.build();
|
||||
injectActivity(activityComponent);
|
||||
super.onCreate(state);
|
||||
|
||||
// WARNING: When removing this or making it possible to turn it off,
|
||||
@@ -86,17 +101,6 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
// unlock screen is shown.
|
||||
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
|
||||
|
||||
AndroidComponent applicationComponent =
|
||||
((BriarApplication) getApplication()).getApplicationComponent();
|
||||
|
||||
activityComponent = DaggerActivityComponent.builder()
|
||||
.androidComponent(applicationComponent)
|
||||
.activityModule(getActivityModule())
|
||||
.forumModule(getForumModule())
|
||||
.build();
|
||||
|
||||
injectActivity(activityComponent);
|
||||
|
||||
for (ActivityLifecycleController alc : lifecycleControllers) {
|
||||
alc.onActivityCreate(this);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.briar.android.activity;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v7.app.ActionBar;
|
||||
@@ -10,6 +9,8 @@ import android.transition.Transition;
|
||||
import android.view.Window;
|
||||
import android.widget.CheckBox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.controller.BriarController;
|
||||
import org.briarproject.briar.android.controller.DbController;
|
||||
@@ -36,7 +37,8 @@ import static org.briarproject.briar.android.util.UiUtils.excludeSystemUi;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getDozeWhitelistingIntent;
|
||||
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
|
||||
|
||||
@SuppressLint("Registered")
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public abstract class BriarActivity extends BaseActivity {
|
||||
|
||||
public static final String GROUP_ID = "briar.GROUP_ID";
|
||||
@@ -60,7 +62,8 @@ public abstract class BriarActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int request, int result, Intent data) {
|
||||
protected void onActivityResult(int request, int result,
|
||||
@Nullable Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
if (request == REQUEST_PASSWORD) {
|
||||
// The result can be RESULT_CANCELED if there's no account
|
||||
@@ -89,7 +92,7 @@ public abstract class BriarActivity extends BaseActivity {
|
||||
} else if (lockManager.isLocked() && !isFinishing()) {
|
||||
// Also check that the activity isn't finishing already.
|
||||
// This is possible if we finished in onActivityResult().
|
||||
// Lauching another UnlockActivity would cause a loop.
|
||||
// Launching another UnlockActivity would cause a loop.
|
||||
Intent i = new Intent(this, UnlockActivity.class);
|
||||
startActivityForResult(i, REQUEST_UNLOCK);
|
||||
} else if (SDK_INT >= 23) {
|
||||
@@ -111,6 +114,10 @@ public abstract class BriarActivity extends BaseActivity {
|
||||
lockManager.onActivityStop();
|
||||
}
|
||||
|
||||
protected boolean signedIn() {
|
||||
return briarController.accountSignedIn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the transition animations.
|
||||
* @param enterTransition used to move views into initial positions
|
||||
|
||||
@@ -24,6 +24,8 @@ import javax.annotation.Nullable;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
|
||||
|
||||
@@ -35,7 +37,7 @@ abstract class BasePostFragment extends BaseFragment {
|
||||
static final String POST_ID = "briar.POST_ID";
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BasePostFragment.class.getName());
|
||||
getLogger(BasePostFragment.class.getName());
|
||||
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
@@ -52,7 +54,7 @@ abstract class BasePostFragment extends BaseFragment {
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
// retrieve MessageId of blog post from arguments
|
||||
byte[] p = getArguments().getByteArray(POST_ID);
|
||||
byte[] p = requireNonNull(getArguments()).getByteArray(POST_ID);
|
||||
if (p == null) throw new IllegalStateException("No post ID in args");
|
||||
postId = new MessageId(p);
|
||||
|
||||
@@ -68,6 +70,7 @@ abstract class BasePostFragment extends BaseFragment {
|
||||
|
||||
@Override
|
||||
public void onAuthorClick(BlogPostItem post) {
|
||||
if (getContext() == null) return;
|
||||
Intent i = new Intent(getContext(), BlogActivity.class);
|
||||
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
|
||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
@@ -43,6 +43,7 @@ import javax.inject.Inject;
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_BLOG;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
|
||||
@@ -69,7 +70,6 @@ public class BlogFragment extends BaseFragment
|
||||
private boolean isMyBlog = false, canDeleteBlog = false;
|
||||
|
||||
static BlogFragment newInstance(GroupId groupId) {
|
||||
|
||||
BlogFragment f = new BlogFragment();
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
@@ -79,20 +79,27 @@ public class BlogFragment extends BaseFragment
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
blogController.setBlogSharingListener(this);
|
||||
sharingController.setSharingListener(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
Bundle args = getArguments();
|
||||
Bundle args = requireNonNull(getArguments());
|
||||
byte[] b = args.getByteArray(GROUP_ID);
|
||||
if (b == null) throw new IllegalStateException("No group ID in args");
|
||||
groupId = new GroupId(b);
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
||||
|
||||
adapter =
|
||||
new BlogPostAdapter(getActivity(), this, getFragmentManager());
|
||||
adapter = new BlogPostAdapter(requireNonNull(getActivity()), this,
|
||||
getFragmentManager());
|
||||
list = v.findViewById(R.id.postList);
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
list.setAdapter(adapter);
|
||||
@@ -102,13 +109,6 @@ public class BlogFragment extends BaseFragment
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
blogController.setBlogSharingListener(this);
|
||||
sharingController.setSharingListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
@@ -218,7 +218,10 @@ public class BlogFragment extends BaseFragment
|
||||
|
||||
@Override
|
||||
public void onAuthorClick(BlogPostItem post) {
|
||||
if (post.getGroupId().equals(groupId)) return; // We're already there
|
||||
if (post.getGroupId().equals(groupId) || getContext() == null) {
|
||||
// We're already there
|
||||
return;
|
||||
}
|
||||
Intent i = new Intent(getContext(), BlogActivity.class);
|
||||
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
|
||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
@@ -35,6 +35,7 @@ import javax.inject.Inject;
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
import static android.support.design.widget.Snackbar.LENGTH_LONG;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
|
||||
|
||||
@@ -64,13 +65,18 @@ public class FeedFragment extends BaseFragment implements
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
feedController.setFeedListener(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
getActivity().setTitle(R.string.blogs_button);
|
||||
requireNonNull(getActivity()).setTitle(R.string.blogs_button);
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
||||
|
||||
@@ -88,12 +94,6 @@ public class FeedFragment extends BaseFragment implements
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
feedController.setFeedListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.briarproject.briar.android.controller.handler.UiResultExceptionHandle
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
|
||||
@UiThread
|
||||
@@ -42,13 +43,17 @@ public class FeedPostFragment extends BasePostFragment {
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
Bundle args = getArguments();
|
||||
Bundle args = requireNonNull(getArguments());
|
||||
byte[] b = args.getByteArray(GROUP_ID);
|
||||
if (b == null) throw new IllegalStateException("No group ID in args");
|
||||
blogId = new GroupId(b);
|
||||
@@ -61,11 +66,6 @@ public class FeedPostFragment extends BasePostFragment {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
@@ -31,6 +31,7 @@ import static android.view.View.FOCUS_DOWN;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.briar.android.blog.BasePostFragment.POST_ID;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_LENGTH;
|
||||
@@ -42,8 +43,6 @@ public class ReblogFragment extends BaseFragment implements SendListener {
|
||||
public static final String TAG = ReblogFragment.class.getName();
|
||||
|
||||
private ViewHolder ui;
|
||||
private GroupId blogId;
|
||||
private MessageId postId;
|
||||
private BlogPostItem item;
|
||||
|
||||
@Inject
|
||||
@@ -75,9 +74,11 @@ public class ReblogFragment extends BaseFragment implements SendListener {
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
Bundle args = getArguments();
|
||||
blogId = new GroupId(args.getByteArray(GROUP_ID));
|
||||
postId = new MessageId(args.getByteArray(POST_ID));
|
||||
Bundle args = requireNonNull(getArguments());
|
||||
GroupId blogId =
|
||||
new GroupId(requireNonNull(args.getByteArray(GROUP_ID)));
|
||||
MessageId postId =
|
||||
new MessageId(requireNonNull(args.getByteArray(POST_ID)));
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_reblog, container, false);
|
||||
ui = new ViewHolder(v);
|
||||
@@ -89,14 +90,6 @@ public class ReblogFragment extends BaseFragment implements SendListener {
|
||||
ui.input.setMaxTextLength(MAX_BLOG_POST_TEXT_LENGTH);
|
||||
showProgressBar();
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
// TODO: Load blog post when fragment is created. #631
|
||||
feedController.loadBlogPost(blogId, postId,
|
||||
new UiResultExceptionHandler<BlogPostItem, DbException>(
|
||||
this) {
|
||||
@@ -111,6 +104,8 @@ public class ReblogFragment extends BaseFragment implements SendListener {
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
private void bindViewHolder() {
|
||||
|
||||
@@ -51,11 +51,13 @@ import javax.inject.Inject;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
|
||||
import static android.support.v4.view.ViewCompat.getTransitionName;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
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.conversation.ConversationActivity.CONTACT_ID;
|
||||
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -102,8 +104,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
getActivity().setTitle(R.string.contact_list_button);
|
||||
requireNonNull(getActivity()).setTitle(R.string.contact_list_button);
|
||||
|
||||
View contentView = inflater.inflate(R.layout.list, container, false);
|
||||
|
||||
@@ -114,7 +115,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
|
||||
ContactId contactId = item.getContact().getId();
|
||||
i.putExtra(CONTACT_ID, contactId.getInt());
|
||||
|
||||
if (SDK_INT >= 23) {
|
||||
if (SDK_INT >= 23 && !isSamsung7()) {
|
||||
ContactListItemViewHolder holder =
|
||||
(ContactListItemViewHolder) list
|
||||
.getRecyclerView()
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.Collection;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.briar.android.contactselection.ContactSelectorActivity.CONTACTS;
|
||||
import static org.briarproject.briar.android.contactselection.ContactSelectorActivity.getContactsFromIds;
|
||||
@@ -50,10 +51,10 @@ public abstract class BaseContactSelectorFragment<I extends SelectableContactIte
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = getArguments();
|
||||
Bundle args = requireNonNull(getArguments());
|
||||
byte[] b = args.getByteArray(GROUP_ID);
|
||||
if (b == null) throw new IllegalStateException("No GroupId");
|
||||
groupId = new GroupId(b);
|
||||
@@ -72,7 +73,7 @@ public abstract class BaseContactSelectorFragment<I extends SelectableContactIte
|
||||
list.setEmptyImage(R.drawable.ic_empty_state_contact_list);
|
||||
list.setEmptyText(getString(R.string.no_contacts_selector));
|
||||
list.setEmptyAction(getString(R.string.no_contacts_selector_action));
|
||||
adapter = getAdapter(getContext(), this);
|
||||
adapter = getAdapter(requireNonNull(getContext()), this);
|
||||
list.setAdapter(adapter);
|
||||
|
||||
// restore selected contacts if available
|
||||
|
||||
@@ -39,8 +39,8 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
|
||||
|
||||
setStyle(STYLE_NO_TITLE, R.style.BriarDialogTheme);
|
||||
|
||||
if (getActivity() == null) return;
|
||||
((BriarActivity) getActivity()).getActivityComponent().inject(this);
|
||||
BriarActivity a = (BriarActivity) requireNonNull(getActivity());
|
||||
a.getActivityComponent().inject(this);
|
||||
viewModel = ViewModelProviders.of(getActivity(), viewModelFactory)
|
||||
.get(ConversationViewModel.class);
|
||||
}
|
||||
@@ -48,7 +48,6 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_alias_dialog, container,
|
||||
false);
|
||||
|
||||
@@ -69,4 +68,5 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
package org.briarproject.briar.android.conversation;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.BitmapFactory.Options;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.media.ExifInterface;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import com.bumptech.glide.util.MarkEnforcingInputStream;
|
||||
|
||||
import org.briarproject.bramble.api.Pair;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
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.R;
|
||||
import org.briarproject.briar.android.conversation.ImageHelper.DecodeResult;
|
||||
import org.briarproject.briar.api.messaging.Attachment;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
@@ -44,8 +46,10 @@ class AttachmentController {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(AttachmentController.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;
|
||||
@@ -53,18 +57,38 @@ class AttachmentController {
|
||||
private final Map<MessageId, List<AttachmentItem>> attachmentCache =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
AttachmentController(MessagingManager messagingManager, Resources res) {
|
||||
AttachmentController(MessagingManager messagingManager,
|
||||
AttachmentDimensions dimensions, ImageHelper imageHelper) {
|
||||
this.messagingManager = messagingManager;
|
||||
defaultSize =
|
||||
res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
|
||||
minWidth = res.getDimensionPixelSize(
|
||||
R.dimen.message_bubble_image_min_width);
|
||||
maxWidth = res.getDimensionPixelSize(
|
||||
R.dimen.message_bubble_image_max_width);
|
||||
minHeight = res.getDimensionPixelSize(
|
||||
R.dimen.message_bubble_image_min_height);
|
||||
maxHeight = res.getDimensionPixelSize(
|
||||
R.dimen.message_bubble_image_max_height);
|
||||
this.imageHelper = imageHelper;
|
||||
defaultSize = dimensions.defaultSize;
|
||||
minWidth = dimensions.minWidth;
|
||||
maxWidth = dimensions.maxWidth;
|
||||
minHeight = dimensions.minHeight;
|
||||
maxHeight = dimensions.maxHeight;
|
||||
}
|
||||
|
||||
AttachmentController(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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void put(MessageId messageId, List<AttachmentItem> attachments) {
|
||||
@@ -83,8 +107,7 @@ class AttachmentController {
|
||||
List<Pair<AttachmentHeader, Attachment>> attachments =
|
||||
new ArrayList<>(headers.size());
|
||||
for (AttachmentHeader h : headers) {
|
||||
Attachment a =
|
||||
messagingManager.getAttachment(h.getMessageId());
|
||||
Attachment a = messagingManager.getAttachment(h.getMessageId());
|
||||
attachments.add(new Pair<>(h, a));
|
||||
}
|
||||
logDuration(LOG, "Loading attachment", start);
|
||||
@@ -93,7 +116,7 @@ class AttachmentController {
|
||||
|
||||
/**
|
||||
* Creates {@link AttachmentItem}s from the passed headers and Attachments.
|
||||
*
|
||||
* <p>
|
||||
* Note: This closes the {@link Attachment}'s {@link InputStream}.
|
||||
*/
|
||||
List<AttachmentItem> getAttachmentItems(
|
||||
@@ -117,7 +140,7 @@ class AttachmentController {
|
||||
MessageId messageId = h.getMessageId();
|
||||
if (!needsSize) {
|
||||
String mimeType = h.getContentType();
|
||||
String extension = getExtensionFromMimeType(mimeType);
|
||||
String extension = imageHelper.getExtensionFromMimeType(mimeType);
|
||||
boolean hasError = false;
|
||||
if (extension == null) {
|
||||
extension = "";
|
||||
@@ -128,8 +151,9 @@ class AttachmentController {
|
||||
}
|
||||
|
||||
Size size = new Size();
|
||||
InputStream is = new BufferedInputStream(a.getStream());
|
||||
is.mark(Integer.MAX_VALUE);
|
||||
InputStream is = new MarkEnforcingInputStream(
|
||||
new BufferedInputStream(a.getStream()));
|
||||
is.mark(READ_LIMIT);
|
||||
try {
|
||||
// use exif to get size
|
||||
if (h.getContentType().equals("image/jpeg")) {
|
||||
@@ -142,6 +166,8 @@ class AttachmentController {
|
||||
// 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) {
|
||||
@@ -157,26 +183,18 @@ class AttachmentController {
|
||||
getThumbnailSize(size.width, size.height, size.mimeType);
|
||||
}
|
||||
// get file extension
|
||||
String extension = getExtensionFromMimeType(size.mimeType);
|
||||
if (extension == null) {
|
||||
return new AttachmentItem(messageId, 0, 0, "", "", 0, 0, true);
|
||||
}
|
||||
String extension = imageHelper.getExtensionFromMimeType(size.mimeType);
|
||||
boolean hasError = extension == null || size.error;
|
||||
if (extension == null) extension = "";
|
||||
return new AttachmentItem(messageId, size.width, size.height,
|
||||
size.mimeType, extension, thumbnailSize.width,
|
||||
thumbnailSize.height, size.error);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getExtensionFromMimeType(String mimeType) {
|
||||
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
||||
return mimeTypeMap.getExtensionFromMimeType(mimeType);
|
||||
thumbnailSize.height, hasError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of a JPEG {@link InputStream} if EXIF info is available.
|
||||
*/
|
||||
private static Size getSizeFromExif(InputStream is)
|
||||
throws IOException {
|
||||
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);
|
||||
@@ -196,14 +214,10 @@ class AttachmentController {
|
||||
/**
|
||||
* Gets the size of any image {@link InputStream}.
|
||||
*/
|
||||
private static Size getSizeFromBitmap(InputStream is) {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeStream(is, null, options);
|
||||
if (options.outWidth < 1 || options.outHeight < 1)
|
||||
return new Size();
|
||||
return new Size(options.outWidth, options.outHeight,
|
||||
options.outMimeType);
|
||||
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) {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.briarproject.briar.android.conversation;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
class AttachmentDimensions {
|
||||
|
||||
final int defaultSize;
|
||||
final int minWidth, maxWidth;
|
||||
final int minHeight, maxHeight;
|
||||
|
||||
@VisibleForTesting
|
||||
AttachmentDimensions(int defaultSize, int minWidth, int maxWidth,
|
||||
int minHeight, int maxHeight) {
|
||||
this.defaultSize = defaultSize;
|
||||
this.minWidth = minWidth;
|
||||
this.maxWidth = maxWidth;
|
||||
this.minHeight = minHeight;
|
||||
this.maxHeight = maxHeight;
|
||||
}
|
||||
|
||||
static AttachmentDimensions getAttachmentDimensions(Resources res) {
|
||||
int defaultSize =
|
||||
res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
|
||||
int minWidth = res.getDimensionPixelSize(
|
||||
R.dimen.message_bubble_image_min_width);
|
||||
int maxWidth = res.getDimensionPixelSize(
|
||||
R.dimen.message_bubble_image_max_width);
|
||||
int minHeight = res.getDimensionPixelSize(
|
||||
R.dimen.message_bubble_image_min_height);
|
||||
int maxHeight = res.getDimensionPixelSize(
|
||||
R.dimen.message_bubble_image_max_height);
|
||||
return new AttachmentDimensions(defaultSize, minWidth, maxWidth,
|
||||
minHeight, minHeight);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import android.support.annotation.Nullable;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@@ -18,6 +20,7 @@ public class AttachmentItem implements Parcelable {
|
||||
private final String mimeType, extension;
|
||||
private final int thumbnailWidth, thumbnailHeight;
|
||||
private final boolean hasError;
|
||||
private final long instanceId;
|
||||
|
||||
public static final Creator<AttachmentItem> CREATOR =
|
||||
new Creator<AttachmentItem>() {
|
||||
@@ -32,6 +35,8 @@ public class AttachmentItem implements Parcelable {
|
||||
}
|
||||
};
|
||||
|
||||
private static final AtomicLong NEXT_INSTANCE_ID = new AtomicLong(0);
|
||||
|
||||
AttachmentItem(MessageId messageId, int width, int height, String mimeType,
|
||||
String extension, int thumbnailWidth, int thumbnailHeight,
|
||||
boolean hasError) {
|
||||
@@ -43,6 +48,7 @@ public class AttachmentItem implements Parcelable {
|
||||
this.thumbnailWidth = thumbnailWidth;
|
||||
this.thumbnailHeight = thumbnailHeight;
|
||||
this.hasError = hasError;
|
||||
instanceId = NEXT_INSTANCE_ID.getAndIncrement();
|
||||
}
|
||||
|
||||
protected AttachmentItem(Parcel in) {
|
||||
@@ -56,6 +62,7 @@ public class AttachmentItem implements Parcelable {
|
||||
thumbnailWidth = in.readInt();
|
||||
thumbnailHeight = in.readInt();
|
||||
hasError = in.readByte() != 0;
|
||||
instanceId = in.readLong();
|
||||
}
|
||||
|
||||
public MessageId getMessageId() {
|
||||
@@ -90,9 +97,8 @@ public class AttachmentItem implements Parcelable {
|
||||
return hasError;
|
||||
}
|
||||
|
||||
// TODO use counter instead, because in theory one attachment can appear in more than one messages
|
||||
String getTransitionName() {
|
||||
return String.valueOf(messageId.hashCode());
|
||||
return String.valueOf(instanceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,12 +116,13 @@ public class AttachmentItem implements Parcelable {
|
||||
dest.writeInt(thumbnailWidth);
|
||||
dest.writeInt(thumbnailHeight);
|
||||
dest.writeByte((byte) (hasError ? 1 : 0));
|
||||
dest.writeLong(instanceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
return o instanceof AttachmentItem &&
|
||||
messageId.equals(((AttachmentItem) o).messageId);
|
||||
instanceId == ((AttachmentItem) o).instanceId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.design.widget.Snackbar;
|
||||
@@ -45,7 +46,6 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
|
||||
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
@@ -102,12 +102,12 @@ import im.delight.android.identicons.IdenticonDrawable;
|
||||
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
|
||||
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptStateChangeListener;
|
||||
|
||||
import static android.arch.lifecycle.Lifecycle.State.STARTED;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
|
||||
import static android.support.v4.view.ViewCompat.setTransitionName;
|
||||
import static android.support.v7.util.SortedList.INVALID_POSITION;
|
||||
import static android.view.Gravity.RIGHT;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.sort;
|
||||
@@ -121,11 +121,10 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
import static org.briarproject.briar.android.TestingConstants.FEATURE_FLAG_IMAGE_ATTACHMENTS;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ATTACH_IMAGE;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRODUCTION;
|
||||
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENTS;
|
||||
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENT_POSITION;
|
||||
import static org.briarproject.briar.android.conversation.ImageActivity.DATE;
|
||||
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENTS;
|
||||
import static org.briarproject.briar.android.conversation.ImageActivity.NAME;
|
||||
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
|
||||
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
||||
@@ -143,8 +142,9 @@ public class ConversationActivity extends BriarActivity
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ConversationActivity.class.getName());
|
||||
private static final String SHOW_ONBOARDING_INTRODUCTION =
|
||||
"showOnboardingIntroduction";
|
||||
|
||||
private static final int TRANSITION_DURATION_MS = 500;
|
||||
private static final int ONBOARDING_DELAY_MS = 250;
|
||||
|
||||
@Inject
|
||||
AndroidNotificationManager notificationManager;
|
||||
@@ -153,21 +153,8 @@ public class ConversationActivity extends BriarActivity
|
||||
@Inject
|
||||
@CryptoExecutor
|
||||
Executor cryptoExecutor;
|
||||
|
||||
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
|
||||
private AttachmentController attachmentController;
|
||||
|
||||
private ConversationViewModel viewModel;
|
||||
private ConversationVisitor visitor;
|
||||
private ConversationAdapter adapter;
|
||||
private Toolbar toolbar;
|
||||
private CircleImageView toolbarAvatar;
|
||||
private ImageView toolbarStatus;
|
||||
private TextView toolbarTitle;
|
||||
private BriarRecyclerView list;
|
||||
private LinearLayoutManager layoutManager;
|
||||
private TextInputView textInputView;
|
||||
private TextSendController sendController;
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject
|
||||
@@ -190,22 +177,37 @@ public class ConversationActivity extends BriarActivity
|
||||
volatile BlogSharingManager blogSharingManager;
|
||||
@Inject
|
||||
volatile GroupInvitationManager groupInvitationManager;
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
private volatile ContactId contactId;
|
||||
|
||||
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
|
||||
private final Observer<String> contactNameObserver = name -> {
|
||||
requireNonNull(name);
|
||||
loadMessages();
|
||||
};
|
||||
|
||||
private AttachmentController attachmentController;
|
||||
private ConversationViewModel viewModel;
|
||||
private ConversationVisitor visitor;
|
||||
private ConversationAdapter adapter;
|
||||
private Toolbar toolbar;
|
||||
private CircleImageView toolbarAvatar;
|
||||
private ImageView toolbarStatus;
|
||||
private TextView toolbarTitle;
|
||||
private BriarRecyclerView list;
|
||||
private LinearLayoutManager layoutManager;
|
||||
private TextInputView textInputView;
|
||||
private TextSendController sendController;
|
||||
@Nullable
|
||||
private Parcelable layoutManagerState;
|
||||
|
||||
private volatile ContactId contactId;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
if (SDK_INT >= 21) {
|
||||
// Spurious lint warning - using END causes a crash
|
||||
@SuppressLint("RtlHardcoded")
|
||||
Transition slide = new Slide(RIGHT);
|
||||
slide.setDuration(TRANSITION_DURATION_MS);
|
||||
setSceneTransitionAnimation(slide, null, slide);
|
||||
}
|
||||
super.onCreate(state);
|
||||
@@ -217,7 +219,6 @@ public class ConversationActivity extends BriarActivity
|
||||
|
||||
viewModel = ViewModelProviders.of(this, viewModelFactory)
|
||||
.get(ConversationViewModel.class);
|
||||
viewModel.setContactId(contactId);
|
||||
attachmentController = viewModel.getAttachmentController();
|
||||
|
||||
setContentView(R.layout.activity_conversation);
|
||||
@@ -241,8 +242,8 @@ public class ConversationActivity extends BriarActivity
|
||||
requireNonNull(deleted);
|
||||
if (deleted) finish();
|
||||
});
|
||||
viewModel.getAddedPrivateMessage()
|
||||
.observe(this, this::onAddedPrivateMessage);
|
||||
viewModel.getAddedPrivateMessage().observe(this,
|
||||
this::onAddedPrivateMessage);
|
||||
|
||||
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
|
||||
setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
|
||||
@@ -261,12 +262,25 @@ public class ConversationActivity extends BriarActivity
|
||||
ImagePreview imagePreview = findViewById(R.id.imagePreview);
|
||||
sendController = new TextAttachmentController(textInputView,
|
||||
imagePreview, this, this);
|
||||
observeOnce(viewModel.hasImageSupport(), this, hasSupport -> {
|
||||
if (hasSupport != null && hasSupport) {
|
||||
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
|
||||
((TextAttachmentController) sendController)
|
||||
.setImagesSupported();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
sendController = new TextSendController(textInputView, this, false);
|
||||
}
|
||||
textInputView.setSendController(sendController);
|
||||
textInputView.setMaxTextLength(MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
||||
textInputView.setEnabled(false);
|
||||
textInputView.addOnKeyboardShownListener(this::scrollToBottom);
|
||||
}
|
||||
|
||||
private void scrollToBottom() {
|
||||
int items = adapter.getItemCount();
|
||||
if (items > 0) list.scrollToPosition(items - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -275,7 +289,8 @@ public class ConversationActivity extends BriarActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int request, int result, Intent data) {
|
||||
protected void onActivityResult(int request, int result,
|
||||
@Nullable Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
|
||||
if (request == REQUEST_INTRODUCTION && result == RESULT_OK) {
|
||||
@@ -300,6 +315,16 @@ public class ConversationActivity extends BriarActivity
|
||||
list.startPeriodicUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// Trigger loading of contact data, noop if data was loaded already.
|
||||
//
|
||||
// We can only start loading data *after* we are sure
|
||||
// the user has signed in. After sign-in, onCreate() isn't run again.
|
||||
if (signedIn()) viewModel.setContactId(contactId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
@@ -309,16 +334,39 @@ public class ConversationActivity extends BriarActivity
|
||||
list.stopPeriodicUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (layoutManager != null) {
|
||||
layoutManagerState = layoutManager.onSaveInstanceState();
|
||||
outState.putParcelable("layoutManager", layoutManagerState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
layoutManagerState = savedInstanceState.getParcelable("layoutManager");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Inflate the menu items for use in the action bar
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.conversation_actions, menu);
|
||||
|
||||
enableIntroductionActionIfAvailable(
|
||||
menu.findItem(R.id.action_introduction));
|
||||
enableAliasActionIfAvailable(
|
||||
menu.findItem(R.id.action_set_alias));
|
||||
// enable introduction action if available
|
||||
observeOnce(viewModel.showIntroductionAction(), this, enable -> {
|
||||
if (enable != null && enable) {
|
||||
menu.findItem(R.id.action_introduction).setEnabled(true);
|
||||
// show introduction onboarding, if needed
|
||||
observeOnce(viewModel.showIntroductionOnboarding(), this,
|
||||
this::showIntroductionOnboarding);
|
||||
}
|
||||
});
|
||||
// enable alias action if available
|
||||
observeOnce(viewModel.getContact(), this, contact ->
|
||||
menu.findItem(R.id.action_set_alias).setEnabled(true));
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
@@ -381,33 +429,10 @@ public class ConversationActivity extends BriarActivity
|
||||
Long.compare(b.getTimestamp(), a.getTimestamp()));
|
||||
if (!sorted.isEmpty()) {
|
||||
// If the latest header is a private message, eagerly load
|
||||
// its text so we can set the scroll position correctly
|
||||
// its size so we can set the scroll position correctly
|
||||
ConversationMessageHeader latest = sorted.get(0);
|
||||
if (latest instanceof PrivateMessageHeader) {
|
||||
MessageId id = latest.getId();
|
||||
PrivateMessageHeader h = (PrivateMessageHeader) latest;
|
||||
if (h.hasText()) {
|
||||
String text = textCache.get(id);
|
||||
if (text == null) {
|
||||
LOG.info(
|
||||
"Eagerly loading text of latest message");
|
||||
text = messagingManager.getMessageText(id);
|
||||
textCache.put(id, text);
|
||||
}
|
||||
}
|
||||
if (h.getAttachmentHeaders().size() == 1) {
|
||||
List<AttachmentItem> items =
|
||||
attachmentController.get(id);
|
||||
if (items == null) {
|
||||
LOG.info(
|
||||
"Eagerly loading image size for latest message");
|
||||
items = attachmentController.getAttachmentItems(
|
||||
attachmentController
|
||||
.getMessageAttachments(
|
||||
h.getAttachmentHeaders()));
|
||||
attachmentController.put(id, items);
|
||||
}
|
||||
}
|
||||
eagerlyLoadMessageSize((PrivateMessageHeader) latest);
|
||||
}
|
||||
}
|
||||
displayMessages(revision, sorted);
|
||||
@@ -419,17 +444,51 @@ public class ConversationActivity extends BriarActivity
|
||||
});
|
||||
}
|
||||
|
||||
private void eagerlyLoadMessageSize(PrivateMessageHeader h)
|
||||
throws DbException {
|
||||
MessageId id = h.getId();
|
||||
// If the message has text, load it
|
||||
if (h.hasText()) {
|
||||
String text = textCache.get(id);
|
||||
if (text == null) {
|
||||
LOG.info("Eagerly loading text for latest message");
|
||||
text = messagingManager.getMessageText(id);
|
||||
textCache.put(id, text);
|
||||
}
|
||||
}
|
||||
// If the message has a single image, load its size - for multiple
|
||||
// images we use a grid so the size is fixed
|
||||
if (h.getAttachmentHeaders().size() == 1) {
|
||||
List<AttachmentItem> items = attachmentController.get(id);
|
||||
if (items == null) {
|
||||
LOG.info("Eagerly loading image size for latest message");
|
||||
items = attachmentController.getAttachmentItems(
|
||||
attachmentController.getMessageAttachments(
|
||||
h.getAttachmentHeaders()));
|
||||
attachmentController.put(id, items);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void displayMessages(int revision,
|
||||
Collection<ConversationMessageHeader> headers) {
|
||||
runOnUiThreadUnlessDestroyed(() -> {
|
||||
if (revision == adapter.getRevision()) {
|
||||
adapter.incrementRevision();
|
||||
textInputView.setEnabled(true);
|
||||
// start observing onboarding after enabling (only once, because
|
||||
// we only update this when an onboarding should be shown)
|
||||
observeOnce(viewModel.showImageOnboarding(), this,
|
||||
this::showImageOnboarding);
|
||||
List<ConversationItem> items = createItems(headers);
|
||||
adapter.addAll(items);
|
||||
list.showData();
|
||||
// Scroll to the bottom
|
||||
list.scrollToPosition(adapter.getItemCount() - 1);
|
||||
if (layoutManagerState == null) {
|
||||
scrollToBottom();
|
||||
} else {
|
||||
// Restore the previous scroll position
|
||||
layoutManager.onRestoreInstanceState(layoutManagerState);
|
||||
}
|
||||
} else {
|
||||
LOG.info("Concurrent update, reloading");
|
||||
loadMessages();
|
||||
@@ -470,14 +529,21 @@ public class ConversationActivity extends BriarActivity
|
||||
Pair<Integer, ConversationMessageItem> pair =
|
||||
adapter.getMessageItem(m);
|
||||
if (pair != null) {
|
||||
boolean scroll = shouldScrollWhenUpdatingMessage();
|
||||
pair.getSecond().setText(text);
|
||||
boolean bottom = adapter.isScrolledToBottom(layoutManager);
|
||||
adapter.notifyItemChanged(pair.getFirst());
|
||||
if (bottom) list.scrollToPosition(adapter.getItemCount() - 1);
|
||||
if (scroll) scrollToBottom();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// When a message's text or attachments are loaded, scroll to the bottom
|
||||
// if the conversation is visible and we were previously at the bottom
|
||||
private boolean shouldScrollWhenUpdatingMessage() {
|
||||
return getLifecycle().getCurrentState().isAtLeast(STARTED)
|
||||
&& adapter.isScrolledToBottom(layoutManager);
|
||||
}
|
||||
|
||||
private void loadMessageAttachments(MessageId messageId,
|
||||
List<AttachmentHeader> headers) {
|
||||
runOnDbThread(() -> {
|
||||
@@ -501,10 +567,10 @@ public class ConversationActivity extends BriarActivity
|
||||
Pair<Integer, ConversationMessageItem> pair =
|
||||
adapter.getMessageItem(m);
|
||||
if (pair != null) {
|
||||
boolean scroll = shouldScrollWhenUpdatingMessage();
|
||||
pair.getSecond().setAttachments(items);
|
||||
boolean bottom = adapter.isScrolledToBottom(layoutManager);
|
||||
adapter.notifyItemChanged(pair.getFirst());
|
||||
if (bottom) list.scrollToPosition(adapter.getItemCount() - 1);
|
||||
if (scroll) scrollToBottom();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -553,10 +619,13 @@ public class ConversationActivity extends BriarActivity
|
||||
|
||||
private void addConversationItem(ConversationItem item) {
|
||||
runOnUiThreadUnlessDestroyed(() -> {
|
||||
boolean bottom = adapter.isScrolledToBottom(layoutManager);
|
||||
adapter.incrementRevision();
|
||||
adapter.add(item);
|
||||
if (bottom) list.scrollToPosition(adapter.getItemCount() - 1);
|
||||
// When adding a new message, scroll to the bottom if the
|
||||
// conversation is visible, even if we're not currently at
|
||||
// the bottom
|
||||
if (getLifecycle().getCurrentState().isAtLeast(STARTED))
|
||||
scrollToBottom();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -652,74 +721,70 @@ public class ConversationActivity extends BriarActivity
|
||||
});
|
||||
}
|
||||
|
||||
private void enableIntroductionActionIfAvailable(MenuItem item) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
if (contactManager.getActiveContacts().size() > 1) {
|
||||
enableIntroductionAction(item);
|
||||
Settings settings =
|
||||
settingsManager.getSettings(SETTINGS_NAMESPACE);
|
||||
if (settings.getBoolean(SHOW_ONBOARDING_INTRODUCTION,
|
||||
true)) {
|
||||
showIntroductionOnboarding();
|
||||
}
|
||||
}
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
});
|
||||
private void showImageOnboarding(@Nullable Boolean show) {
|
||||
if (show == null || !show) return;
|
||||
if (SDK_INT >= 21) {
|
||||
// show onboarding only after the enter transition has ended
|
||||
// otherwise the tap target animation won't play
|
||||
textInputView.postDelayed(this::showImageOnboarding,
|
||||
TRANSITION_DURATION_MS + ONBOARDING_DELAY_MS);
|
||||
} else {
|
||||
showImageOnboarding();
|
||||
}
|
||||
}
|
||||
|
||||
private void enableAliasActionIfAvailable(MenuItem item) {
|
||||
observeOnce(viewModel.getContact(), this, c -> item.setEnabled(true));
|
||||
private void showImageOnboarding() {
|
||||
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
|
||||
((TextAttachmentController) sendController)
|
||||
.showImageOnboarding(this, () ->
|
||||
viewModel.onImageOnboardingSeen());
|
||||
}
|
||||
|
||||
private void enableIntroductionAction(MenuItem item) {
|
||||
runOnUiThreadUnlessDestroyed(() -> item.setEnabled(true));
|
||||
private void showIntroductionOnboarding(@Nullable Boolean show) {
|
||||
if (show == null || !show) return;
|
||||
if (SDK_INT >= 21) {
|
||||
// show onboarding only after the enter transition has ended
|
||||
// otherwise the tap target animation won't play
|
||||
textInputView.postDelayed(this::showIntroductionOnboarding,
|
||||
TRANSITION_DURATION_MS + ONBOARDING_DELAY_MS);
|
||||
} else {
|
||||
showIntroductionOnboarding();
|
||||
}
|
||||
}
|
||||
|
||||
private void showIntroductionOnboarding() {
|
||||
runOnUiThreadUnlessDestroyed(() -> {
|
||||
// find view of overflow icon
|
||||
View target = null;
|
||||
for (int i = 0; i < toolbar.getChildCount(); i++) {
|
||||
if (toolbar.getChildAt(i) instanceof ActionMenuView) {
|
||||
ActionMenuView menu =
|
||||
(ActionMenuView) toolbar.getChildAt(i);
|
||||
target = menu.getChildAt(menu.getChildCount() - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (target == null) {
|
||||
LOG.warning("No Overflow Icon found!");
|
||||
return;
|
||||
// find view of overflow icon
|
||||
View target = null;
|
||||
for (int i = 0; i < toolbar.getChildCount(); i++) {
|
||||
if (toolbar.getChildAt(i) instanceof ActionMenuView) {
|
||||
ActionMenuView menu = (ActionMenuView) toolbar.getChildAt(i);
|
||||
// The overflow icon should be the last child of the menu
|
||||
target = menu.getChildAt(menu.getChildCount() - 1);
|
||||
// If the menu hasn't been populated yet, use the menu itself
|
||||
// as the target
|
||||
if (target == null) target = menu;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (target == null) {
|
||||
LOG.warning("No Overflow Icon found!");
|
||||
return;
|
||||
}
|
||||
|
||||
PromptStateChangeListener listener = (prompt, state) -> {
|
||||
if (state == STATE_DISMISSED || state == STATE_FINISHED) {
|
||||
introductionOnboardingSeen();
|
||||
}
|
||||
};
|
||||
new MaterialTapTargetPrompt.Builder(ConversationActivity.this,
|
||||
R.style.OnboardingDialogTheme).setTarget(target)
|
||||
.setPrimaryText(R.string.introduction_onboarding_title)
|
||||
.setSecondaryText(R.string.introduction_onboarding_text)
|
||||
.setIcon(R.drawable.ic_more_vert_accent)
|
||||
.setPromptStateChangeListener(listener)
|
||||
.show();
|
||||
});
|
||||
}
|
||||
|
||||
private void introductionOnboardingSeen() {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
Settings settings = new Settings();
|
||||
settings.putBoolean(SHOW_ONBOARDING_INTRODUCTION, false);
|
||||
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
PromptStateChangeListener listener = (prompt, state) -> {
|
||||
if (state == STATE_DISMISSED || state == STATE_FINISHED) {
|
||||
viewModel.onIntroductionOnboardingSeen();
|
||||
}
|
||||
});
|
||||
};
|
||||
new MaterialTapTargetPrompt.Builder(ConversationActivity.this,
|
||||
R.style.OnboardingDialogTheme).setTarget(target)
|
||||
.setPrimaryText(R.string.introduction_onboarding_title)
|
||||
.setSecondaryText(R.string.introduction_onboarding_text)
|
||||
.setIcon(R.drawable.ic_more_vert_accent)
|
||||
.setBackgroundColour(
|
||||
ContextCompat.getColor(this, R.color.briar_primary))
|
||||
.setPromptStateChangeListener(listener)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -820,15 +885,11 @@ public class ConversationActivity extends BriarActivity
|
||||
i.putExtra(ATTACHMENT_POSITION, attachments.indexOf(item));
|
||||
i.putExtra(NAME, name);
|
||||
i.putExtra(DATE, messageItem.getTime());
|
||||
if (SDK_INT >= 23) {
|
||||
String transitionName = item.getTransitionName();
|
||||
ActivityOptionsCompat options =
|
||||
makeSceneTransitionAnimation(this, view, transitionName);
|
||||
ActivityCompat.startActivity(this, i, options.toBundle());
|
||||
} else {
|
||||
// work-around for android bug #224270
|
||||
startActivity(i);
|
||||
}
|
||||
// restoring list position should not trigger android bug #224270
|
||||
String transitionName = item.getTransitionName();
|
||||
ActivityOptionsCompat options =
|
||||
makeSceneTransitionAnimation(this, view, transitionName);
|
||||
ActivityCompat.startActivity(this, i, options.toBundle());
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
|
||||
@@ -16,11 +16,14 @@ import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.briar.android.util.UiUtils;
|
||||
@@ -34,6 +37,7 @@ import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
@@ -47,6 +51,8 @@ import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||
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.conversation.AttachmentDimensions.getAttachmentDimensions;
|
||||
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
||||
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
||||
|
||||
@NotNullByDefault
|
||||
@@ -54,13 +60,20 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
|
||||
private static Logger LOG =
|
||||
getLogger(ConversationViewModel.class.getName());
|
||||
private static final String SHOW_ONBOARDING_IMAGE =
|
||||
"showOnboardingImage";
|
||||
private static final String SHOW_ONBOARDING_INTRODUCTION =
|
||||
"showOnboardingIntroduction";
|
||||
|
||||
@DatabaseExecutor
|
||||
private final Executor dbExecutor;
|
||||
@CryptoExecutor
|
||||
private final Executor cryptoExecutor;
|
||||
// TODO replace with TransactionManager once it exists
|
||||
private final DatabaseComponent db;
|
||||
private final MessagingManager messagingManager;
|
||||
private final ContactManager contactManager;
|
||||
private final SettingsManager settingsManager;
|
||||
private final PrivateMessageFactory privateMessageFactory;
|
||||
private final AttachmentController attachmentController;
|
||||
|
||||
@@ -71,6 +84,14 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
Transformations.map(contact, c -> c.getAuthor().getId());
|
||||
private final LiveData<String> contactName =
|
||||
Transformations.map(contact, UiUtils::getContactDisplayName);
|
||||
private final MutableLiveData<Boolean> imageSupport =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> showImageOnboarding =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> showIntroductionOnboarding =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> showIntroductionAction =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> contactDeleted =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<GroupId> messagingGroupId =
|
||||
@@ -81,38 +102,46 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
@Inject
|
||||
ConversationViewModel(Application application,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
@CryptoExecutor Executor cryptoExecutor,
|
||||
MessagingManager messagingManager,
|
||||
ContactManager contactManager,
|
||||
@CryptoExecutor Executor cryptoExecutor, DatabaseComponent db,
|
||||
MessagingManager messagingManager, ContactManager contactManager,
|
||||
SettingsManager settingsManager,
|
||||
PrivateMessageFactory privateMessageFactory) {
|
||||
super(application);
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.cryptoExecutor = cryptoExecutor;
|
||||
this.db = db;
|
||||
this.messagingManager = messagingManager;
|
||||
this.contactManager = contactManager;
|
||||
this.settingsManager = settingsManager;
|
||||
this.privateMessageFactory = privateMessageFactory;
|
||||
this.attachmentController = new AttachmentController(messagingManager,
|
||||
application.getResources());
|
||||
getAttachmentDimensions(application.getResources()));
|
||||
contactDeleted.setValue(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting the {@link ContactId} automatically triggers loading of other
|
||||
* data.
|
||||
*/
|
||||
void setContactId(ContactId contactId) {
|
||||
if (this.contactId == null) {
|
||||
this.contactId = contactId;
|
||||
loadContact();
|
||||
loadContact(contactId);
|
||||
} else if (!contactId.equals(this.contactId)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadContact() {
|
||||
private void loadContact(ContactId contactId) {
|
||||
dbExecutor.execute(() -> {
|
||||
try {
|
||||
long start = now();
|
||||
Contact c =
|
||||
contactManager.getContact(requireNonNull(contactId));
|
||||
Contact c = contactManager.getContact(contactId);
|
||||
contact.postValue(c);
|
||||
logDuration(LOG, "Loading contact", start);
|
||||
start = now();
|
||||
checkFeaturesAndOnboarding(contactId);
|
||||
logDuration(LOG, "Checking for image support", start);
|
||||
} catch (NoSuchContactException e) {
|
||||
contactDeleted.postValue(true);
|
||||
} catch (DbException e) {
|
||||
@@ -126,7 +155,7 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
try {
|
||||
contactManager.setContactAlias(requireNonNull(contactId),
|
||||
alias.isEmpty() ? null : alias);
|
||||
loadContact();
|
||||
loadContact(contactId);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
@@ -154,6 +183,59 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void checkFeaturesAndOnboarding(ContactId c) throws DbException {
|
||||
// check if images are supported
|
||||
boolean imagesSupported = db.transactionWithResult(true, txn ->
|
||||
messagingManager.contactSupportsImages(txn, c));
|
||||
imageSupport.postValue(imagesSupported);
|
||||
|
||||
// check if introductions are supported
|
||||
Collection<Contact> contacts = contactManager.getActiveContacts();
|
||||
boolean introductionSupported = contacts.size() > 1;
|
||||
showIntroductionAction.postValue(introductionSupported);
|
||||
|
||||
Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
|
||||
if (imagesSupported &&
|
||||
settings.getBoolean(SHOW_ONBOARDING_IMAGE, true)) {
|
||||
// check if we should show onboarding, only if images are supported
|
||||
showImageOnboarding.postValue(true);
|
||||
// allow observer to stop listening for changes
|
||||
showIntroductionOnboarding.postValue(false);
|
||||
} else {
|
||||
// allow observer to stop listening for changes
|
||||
showImageOnboarding.postValue(false);
|
||||
// we only show one onboarding dialog at a time
|
||||
if (introductionSupported &&
|
||||
settings.getBoolean(SHOW_ONBOARDING_INTRODUCTION, true)) {
|
||||
showIntroductionOnboarding.postValue(true);
|
||||
} else {
|
||||
// allow observer to stop listening for changes
|
||||
showIntroductionOnboarding.postValue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onImageOnboardingSeen() {
|
||||
onOnboardingSeen(SHOW_ONBOARDING_IMAGE);
|
||||
}
|
||||
|
||||
void onIntroductionOnboardingSeen() {
|
||||
onOnboardingSeen(SHOW_ONBOARDING_INTRODUCTION);
|
||||
}
|
||||
|
||||
private void onOnboardingSeen(String key) {
|
||||
dbExecutor.execute(() -> {
|
||||
try {
|
||||
Settings settings = new Settings();
|
||||
settings.putBoolean(key, false);
|
||||
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void storeAttachments(GroupId groupId, @Nullable String text,
|
||||
List<Uri> uris, long timestamp) {
|
||||
dbExecutor.execute(() -> {
|
||||
@@ -262,6 +344,22 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
return contactName;
|
||||
}
|
||||
|
||||
LiveData<Boolean> hasImageSupport() {
|
||||
return imageSupport;
|
||||
}
|
||||
|
||||
LiveData<Boolean> showImageOnboarding() {
|
||||
return showImageOnboarding;
|
||||
}
|
||||
|
||||
LiveData<Boolean> showIntroductionOnboarding() {
|
||||
return showIntroductionOnboarding;
|
||||
}
|
||||
|
||||
LiveData<Boolean> showIntroductionAction() {
|
||||
return showIntroductionAction;
|
||||
}
|
||||
|
||||
LiveData<Boolean> isContactDeleted() {
|
||||
return contactDeleted;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||
import android.view.Window;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
@@ -55,6 +57,8 @@ import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SAVE_ATTACHMENT;
|
||||
import static org.briarproject.briar.android.util.UiUtils.formatDateAbsolute;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class ImageActivity extends BriarActivity
|
||||
implements PullDownLayout.Callback, OnGlobalLayoutListener {
|
||||
|
||||
@@ -82,7 +86,7 @@ public class ImageActivity extends BriarActivity
|
||||
super.onCreate(state);
|
||||
|
||||
// Transitions
|
||||
supportPostponeEnterTransition();
|
||||
if (state == null) supportPostponeEnterTransition();
|
||||
Window window = getWindow();
|
||||
if (SDK_INT >= 21) {
|
||||
Transition transition = new Fade();
|
||||
@@ -97,7 +101,6 @@ public class ImageActivity extends BriarActivity
|
||||
// inflate layout
|
||||
setContentView(R.layout.activity_image);
|
||||
layout = findViewById(R.id.layout);
|
||||
layout.getBackground().setAlpha(255);
|
||||
layout.setCallback(this);
|
||||
layout.getViewTreeObserver().addOnGlobalLayoutListener(this);
|
||||
|
||||
@@ -174,9 +177,11 @@ public class ImageActivity extends BriarActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int request, int result, Intent data) {
|
||||
protected void onActivityResult(int request, int result,
|
||||
@Nullable Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
if (request == REQUEST_SAVE_ATTACHMENT && result == RESULT_OK) {
|
||||
if (request == REQUEST_SAVE_ATTACHMENT && result == RESULT_OK &&
|
||||
data != null) {
|
||||
viewModel.saveImage(getVisibleAttachment(), data.getData());
|
||||
}
|
||||
}
|
||||
@@ -190,7 +195,6 @@ public class ImageActivity extends BriarActivity
|
||||
|
||||
@Override
|
||||
public void onPull(float progress) {
|
||||
layout.getBackground().setAlpha(Math.round((1 - progress) * 255));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -298,13 +302,18 @@ public class ImageActivity extends BriarActivity
|
||||
|
||||
private class ImagePagerAdapter extends FragmentStatePagerAdapter {
|
||||
|
||||
private boolean isFirst = true;
|
||||
|
||||
private ImagePagerAdapter(FragmentManager fm) {
|
||||
super(fm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return ImageFragment.newInstance(attachments.get(position));
|
||||
Fragment f = ImageFragment
|
||||
.newInstance(attachments.get(position), isFirst);
|
||||
isFirst = false;
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -35,17 +35,21 @@ import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHME
|
||||
@ParametersAreNonnullByDefault
|
||||
public class ImageFragment extends Fragment {
|
||||
|
||||
private final static String IS_FIRST = "isFirst";
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
private AttachmentItem attachment;
|
||||
private boolean isFirst;
|
||||
private ImageViewModel viewModel;
|
||||
private PhotoView photoView;
|
||||
|
||||
static ImageFragment newInstance(AttachmentItem a) {
|
||||
static ImageFragment newInstance(AttachmentItem a, boolean isFirst) {
|
||||
ImageFragment f = new ImageFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ATTACHMENT_POSITION, a);
|
||||
args.putBoolean(IS_FIRST, isFirst);
|
||||
f.setArguments(args);
|
||||
return f;
|
||||
}
|
||||
@@ -63,6 +67,7 @@ public class ImageFragment extends Fragment {
|
||||
|
||||
Bundle args = requireNonNull(getArguments());
|
||||
attachment = requireNonNull(args.getParcelable(ATTACHMENT_POSITION));
|
||||
isFirst = args.getBoolean(IS_FIRST);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -77,6 +82,7 @@ public class ImageFragment extends Fragment {
|
||||
viewModelFactory).get(ImageViewModel.class);
|
||||
|
||||
photoView = v.findViewById(R.id.photoView);
|
||||
photoView.setScaleLevels(1, 2, 4);
|
||||
photoView.setOnClickListener(view -> viewModel.clickImage());
|
||||
|
||||
// Request Listener
|
||||
@@ -85,7 +91,7 @@ public class ImageFragment extends Fragment {
|
||||
public boolean onLoadFailed(@Nullable GlideException e,
|
||||
Object model, Target<Drawable> target,
|
||||
boolean isFirstResource) {
|
||||
if (getActivity() != null)
|
||||
if (getActivity() != null && isFirst)
|
||||
getActivity().supportStartPostponedEnterTransition();
|
||||
return false;
|
||||
}
|
||||
@@ -104,8 +110,9 @@ public class ImageFragment extends Fragment {
|
||||
if (viewModel.isOverlappingToolbar(photoView, resource)) {
|
||||
photoView.setScaleType(FIT_START);
|
||||
}
|
||||
if (getActivity() != null)
|
||||
if (getActivity() != null && isFirst) {
|
||||
getActivity().supportStartPostponedEnterTransition();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -113,9 +120,10 @@ public class ImageFragment extends Fragment {
|
||||
// Load Image
|
||||
GlideApp.with(this)
|
||||
.load(attachment)
|
||||
// TODO allow if size < maxTextureSize ?
|
||||
// .override(SIZE_ORIGINAL)
|
||||
.diskCacheStrategy(NONE)
|
||||
.error(R.drawable.ic_image_broken)
|
||||
.dontTransform()
|
||||
.addListener(listener)
|
||||
.into(photoView);
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.briarproject.briar.android.conversation;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
@NotNullByDefault
|
||||
interface ImageHelper {
|
||||
|
||||
DecodeResult decodeStream(InputStream is);
|
||||
|
||||
@Nullable
|
||||
String getExtensionFromMimeType(String mimeType);
|
||||
|
||||
class DecodeResult {
|
||||
|
||||
final int width;
|
||||
final int height;
|
||||
final String mimeType;
|
||||
|
||||
DecodeResult(int width, int height, String mimeType) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.mimeType = mimeType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,12 @@ import android.view.View;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.util.UiUtils;
|
||||
|
||||
import static org.briarproject.briar.android.conversation.ImageAdapter.isBottomRow;
|
||||
import static org.briarproject.briar.android.conversation.ImageAdapter.isLeft;
|
||||
import static org.briarproject.briar.android.conversation.ImageAdapter.isTopRow;
|
||||
import static org.briarproject.briar.android.conversation.ImageAdapter.singleInRow;
|
||||
import static org.briarproject.briar.android.util.UiUtils.isRtl;
|
||||
|
||||
@NotNullByDefault
|
||||
class ImageItemDecoration extends ItemDecoration {
|
||||
@@ -35,7 +35,7 @@ class ImageItemDecoration extends ItemDecoration {
|
||||
border = realBorderSize / 2;
|
||||
|
||||
// find out if we are showing a RTL language
|
||||
isRtl = UiUtils.isRtl(ctx);
|
||||
isRtl = isRtl(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.briarproject.briar.android.conversation.glide.BriarImageTransformatio
|
||||
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||
import org.briarproject.briar.android.conversation.glide.Radii;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
|
||||
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
|
||||
|
||||
@@ -42,6 +43,9 @@ class ImageViewHolder extends ViewHolder {
|
||||
} else {
|
||||
setImageViewDimensions(attachment, single, needsStretch);
|
||||
loadImage(attachment, r);
|
||||
if (SDK_INT >= 21) {
|
||||
imageView.setTransitionName(attachment.getTransitionName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,9 +50,12 @@ public class ImageViewModel extends AndroidViewModel {
|
||||
@IoExecutor
|
||||
private final Executor ioExecutor;
|
||||
|
||||
/**
|
||||
* true means there was an error saving the image, false if image was saved.
|
||||
*/
|
||||
private final MutableLiveData<Boolean> saveState = new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> imageClicked =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> saveState = new MutableLiveData<>();
|
||||
private int toolbarTop, toolbarBottom;
|
||||
|
||||
@Inject
|
||||
@@ -106,8 +109,9 @@ public class ImageViewModel extends AndroidViewModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* A LiveData that is true if the image was saved,
|
||||
* false if there was an error and null otherwise.
|
||||
* A LiveData that is true if there was an error
|
||||
* and false if the image was saved.
|
||||
* It can be null otherwise, if no image was saved recently.
|
||||
*
|
||||
* Call {@link #onSaveStateSeen()} after consuming an update.
|
||||
*/
|
||||
@@ -126,7 +130,7 @@ public class ImageViewModel extends AndroidViewModel {
|
||||
@UiThread
|
||||
void saveImage(AttachmentItem attachment, @Nullable Uri uri) {
|
||||
if (uri == null) {
|
||||
saveState.setValue(false);
|
||||
saveState.setValue(true);
|
||||
} else {
|
||||
saveImage(attachment, () -> getOutputStream(uri), null);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ class BriarDataFetcher implements DataFetcher<InputStream> {
|
||||
private volatile boolean cancel = false;
|
||||
|
||||
@Inject
|
||||
public BriarDataFetcher(MessagingManager messagingManager,
|
||||
BriarDataFetcher(MessagingManager messagingManager,
|
||||
@DatabaseExecutor Executor dbExecutor, AttachmentItem attachment) {
|
||||
this.messagingManager = messagingManager;
|
||||
this.dbExecutor = dbExecutor;
|
||||
|
||||
@@ -22,7 +22,7 @@ public final class BriarModelLoader
|
||||
@Inject
|
||||
BriarDataFetcherFactory dataFetcherFactory;
|
||||
|
||||
public BriarModelLoader(BriarApplication app) {
|
||||
BriarModelLoader(BriarApplication app) {
|
||||
app.getApplicationComponent().inject(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ class BriarModelLoaderFactory
|
||||
|
||||
private final BriarApplication app;
|
||||
|
||||
public BriarModelLoaderFactory(BriarApplication app) {
|
||||
BriarModelLoaderFactory(BriarApplication app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,13 +21,14 @@ import static android.graphics.Shader.TileMode.CLAMP;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class CustomCornersTransformation extends BitmapTransformation {
|
||||
class CustomCornersTransformation extends BitmapTransformation {
|
||||
|
||||
private static final String ID = CustomCornersTransformation.class.getName();
|
||||
private static final String ID =
|
||||
CustomCornersTransformation.class.getName();
|
||||
|
||||
private final Radii radii;
|
||||
|
||||
public CustomCornersTransformation(Radii radii) {
|
||||
CustomCornersTransformation(Radii radii) {
|
||||
this.radii = radii;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
@@ -80,13 +81,18 @@ public class ForumListFragment extends BaseEventFragment implements
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
getActivity().setTitle(R.string.forums_button);
|
||||
requireNonNull(getActivity()).setTitle(R.string.forums_button);
|
||||
|
||||
View contentView =
|
||||
inflater.inflate(R.layout.fragment_forum_list, container,
|
||||
@@ -102,7 +108,7 @@ public class ForumListFragment extends BaseEventFragment implements
|
||||
snackbar.getView().setBackgroundResource(R.color.briar_primary);
|
||||
snackbar.setAction(R.string.show, this);
|
||||
snackbar.setActionTextColor(ContextCompat
|
||||
.getColor(getContext(), R.color.briar_button_text_positive));
|
||||
.getColor(getActivity(), R.color.briar_button_text_positive));
|
||||
|
||||
return contentView;
|
||||
}
|
||||
@@ -112,11 +118,6 @@ public class ForumListFragment extends BaseEventFragment implements
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
@@ -10,11 +10,15 @@ import android.support.v4.app.FragmentActivity;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.android.DestroyableContext;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public abstract class BaseFragment extends Fragment
|
||||
implements DestroyableContext {
|
||||
|
||||
@@ -22,12 +26,15 @@ public abstract class BaseFragment extends Fragment
|
||||
|
||||
public abstract String getUniqueTag();
|
||||
|
||||
public abstract void injectFragment(ActivityComponent component);
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
listener = (BaseFragmentListener) context;
|
||||
injectFragment(listener.getActivityComponent());
|
||||
}
|
||||
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
// fragments that need to inject, can override this method
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -38,12 +45,6 @@ public abstract class BaseFragment extends Fragment
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
injectFragment(listener.getActivityComponent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
@@ -10,7 +10,6 @@ import android.widget.TextView;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@@ -57,9 +56,4 @@ public class ErrorFragment extends BaseFragment {
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
// not necessary
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package org.briarproject.briar.android.introduction;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -11,6 +12,8 @@ import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
@@ -28,10 +31,14 @@ import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
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.conversation.ConversationActivity.CONTACT_ID;
|
||||
|
||||
@UiThread
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class ContactChooserFragment extends BaseFragment {
|
||||
|
||||
public static final String TAG = ContactChooserFragment.class.getName();
|
||||
@@ -51,7 +58,6 @@ public class ContactChooserFragment extends BaseFragment {
|
||||
volatile ConnectionRegistry connectionRegistry;
|
||||
|
||||
public static ContactChooserFragment newInstance(ContactId id) {
|
||||
|
||||
Bundle args = new Bundle();
|
||||
|
||||
ContactChooserFragment fragment = new ContactChooserFragment();
|
||||
@@ -61,13 +67,13 @@ public class ContactChooserFragment extends BaseFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
View contentView = inflater.inflate(R.layout.list, container, false);
|
||||
|
||||
@@ -77,14 +83,16 @@ public class ContactChooserFragment extends BaseFragment {
|
||||
Contact c2 = item.getContact();
|
||||
showMessageScreen(c1, c2);
|
||||
};
|
||||
adapter = new ContactListAdapter(getActivity(), onContactClickListener);
|
||||
adapter = new ContactListAdapter(requireNonNull(getActivity()),
|
||||
onContactClickListener);
|
||||
|
||||
list = contentView.findViewById(R.id.list);
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
list.setAdapter(adapter);
|
||||
list.setEmptyText(R.string.no_contacts);
|
||||
|
||||
contactId = new ContactId(getArguments().getInt(CONTACT_ID));
|
||||
contactId = new ContactId(
|
||||
requireNonNull(getArguments()).getInt(CONTACT_ID));
|
||||
|
||||
return contentView;
|
||||
}
|
||||
@@ -107,11 +115,6 @@ public class ContactChooserFragment extends BaseFragment {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
private void loadContacts() {
|
||||
listener.runOnDbThread(() -> {
|
||||
try {
|
||||
|
||||
@@ -39,6 +39,7 @@ import static android.app.Activity.RESULT_OK;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
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;
|
||||
@@ -77,17 +78,17 @@ public class IntroductionMessageFragment extends BaseFragment
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
introductionActivity = (IntroductionActivity) context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@@ -99,6 +100,14 @@ public class IntroductionMessageFragment extends BaseFragment
|
||||
actionBar.setTitle(R.string.introduction_message_title);
|
||||
}
|
||||
|
||||
// get contact IDs from fragment arguments
|
||||
Bundle args = requireNonNull(getArguments());
|
||||
int contactId1 = args.getInt(CONTACT_ID_1, -1);
|
||||
int contactId2 = args.getInt(CONTACT_ID_2, -1);
|
||||
if (contactId1 == -1 || contactId2 == -1) {
|
||||
throw new AssertionError("Use newInstance() to instantiate");
|
||||
}
|
||||
|
||||
// inflate view
|
||||
View v = inflater.inflate(R.layout.introduction_message, container,
|
||||
false);
|
||||
@@ -109,22 +118,15 @@ public class IntroductionMessageFragment extends BaseFragment
|
||||
ui.message.setMaxTextLength(MAX_INTRODUCTION_TEXT_LENGTH);
|
||||
ui.message.setEnabled(false);
|
||||
|
||||
// get contacts and then show view
|
||||
prepareToSetUpViews(contactId1, contactId2);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
// get contact IDs from fragment arguments
|
||||
int contactId1 = getArguments().getInt(CONTACT_ID_1, -1);
|
||||
int contactId2 = getArguments().getInt(CONTACT_ID_2, -1);
|
||||
if (contactId1 == -1 || contactId2 == -1) {
|
||||
throw new java.lang.InstantiationError(
|
||||
"You need to use newInstance() to instantiate");
|
||||
}
|
||||
// get contacts and then show view
|
||||
prepareToSetUpViews(contactId1, contactId2);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -44,6 +44,11 @@ public class ContactExchangeErrorFragment extends BaseFragment {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@@ -74,11 +79,6 @@ public class ContactExchangeErrorFragment extends BaseFragment {
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
private void triggerFeedback() {
|
||||
finish();
|
||||
UiUtils.triggerFeedback(androidExecutor);
|
||||
|
||||
@@ -10,7 +10,6 @@ import android.widget.ScrollView;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -39,11 +38,6 @@ public class IntroFragment extends BaseFragment {
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
|
||||
@@ -53,6 +53,7 @@ import static android.view.View.VISIBLE;
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
import static android.widget.LinearLayout.HORIZONTAL;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
@@ -136,7 +137,8 @@ public class KeyAgreementFragment extends BaseEventFragment
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
|
||||
requireNonNull(getActivity())
|
||||
.setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
|
||||
cameraView.setPreviewConsumer(new QrCodeDecoder(this));
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ 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.briar.android.util.UiUtils.setError;
|
||||
|
||||
@@ -35,11 +36,16 @@ public class AuthorNameFragment extends SetupFragment {
|
||||
return new AuthorNameFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
getActivity().setTitle(getString(R.string.setup_title));
|
||||
requireNonNull(getActivity()).setTitle(getString(R.string.setup_title));
|
||||
View v = inflater.inflate(R.layout.fragment_setup_author_name,
|
||||
container, false);
|
||||
authorNameWrapper = v.findViewById(R.id.nickname_entry_wrapper);
|
||||
@@ -57,11 +63,6 @@ public class AuthorNameFragment extends SetupFragment {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHelpText() {
|
||||
return getString(R.string.setup_name_explanation);
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.briarproject.briar.android.util.UiUtils;
|
||||
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
|
||||
|
||||
@@ -39,11 +40,16 @@ public class DozeFragment extends SetupFragment
|
||||
return new DozeFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
getActivity().setTitle(getString(R.string.setup_doze_title));
|
||||
requireNonNull(getActivity()).setTitle(getString(R.string.setup_doze_title));
|
||||
setHasOptionsMenu(false);
|
||||
View v = inflater.inflate(R.layout.fragment_setup_doze, container,
|
||||
false);
|
||||
@@ -65,11 +71,6 @@ public class DozeFragment extends SetupFragment
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHelpText() {
|
||||
return getString(R.string.setup_doze_explanation);
|
||||
|
||||
@@ -23,6 +23,7 @@ import static android.content.Context.INPUT_METHOD_SERVICE;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@@ -43,11 +44,16 @@ public class PasswordFragment extends SetupFragment {
|
||||
return new PasswordFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
getActivity().setTitle(getString(R.string.setup_password_intro));
|
||||
requireNonNull(getActivity()).setTitle(getString(R.string.setup_password_intro));
|
||||
View v = inflater.inflate(R.layout.fragment_setup_password, container,
|
||||
false);
|
||||
|
||||
@@ -64,6 +70,11 @@ public class PasswordFragment extends SetupFragment {
|
||||
passwordConfirmation.addTextChangedListener(this);
|
||||
nextButton.setOnClickListener(this);
|
||||
|
||||
if (!setupController.needToShowDozeFragment()) {
|
||||
nextButton.setText(R.string.create_account_button);
|
||||
passwordConfirmation.setImeOptions(IME_ACTION_DONE);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -72,17 +83,6 @@ public class PasswordFragment extends SetupFragment {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
|
||||
// the controller is not yet available in onCreateView()
|
||||
if (!setupController.needToShowDozeFragment()) {
|
||||
nextButton.setText(R.string.create_account_button);
|
||||
passwordConfirmation.setImeOptions(IME_ACTION_DONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHelpText() {
|
||||
return getString(R.string.setup_password_explanation);
|
||||
|
||||
@@ -11,7 +11,8 @@ import android.view.View.OnClickListener;
|
||||
import android.widget.TextView;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
|
||||
@@ -22,7 +23,8 @@ import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT;
|
||||
import static org.briarproject.briar.android.util.UiUtils.enterPressed;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
|
||||
|
||||
@NotNullByDefault
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
abstract class SetupFragment extends BaseFragment implements TextWatcher,
|
||||
OnEditorActionListener, OnClickListener {
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package org.briarproject.briar.android.logout;
|
||||
|
||||
import android.os.Build;
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class ExitActivity extends BaseActivity {
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
|
||||
public class ExitActivity extends Activity {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ExitActivity.class.getName());
|
||||
@@ -16,14 +15,9 @@ public class ExitActivity extends BaseActivity {
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
if (Build.VERSION.SDK_INT >= 21) finishAndRemoveTask();
|
||||
if (SDK_INT >= 21) finishAndRemoveTask();
|
||||
else finish();
|
||||
LOG.info("Exiting");
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,13 @@
|
||||
package org.briarproject.briar.android.logout;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
|
||||
public class HideUiActivity extends BaseActivity {
|
||||
public class HideUiActivity extends Activity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,19 +5,21 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class SignOutFragment extends BaseFragment {
|
||||
|
||||
public static final String TAG = SignOutFragment.class.getName();
|
||||
|
||||
@Override
|
||||
public View onCreateView(@Nonnull LayoutInflater inflater,
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_sign_out, container, false);
|
||||
@@ -27,9 +29,4 @@ public class SignOutFragment extends BaseFragment {
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
// no need to inject
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.NavigationView;
|
||||
import android.support.design.widget.NavigationView.OnNavigationItemSelectedListener;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
@@ -12,6 +13,7 @@ import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBarDrawerToggle;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -25,6 +27,8 @@ import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.BluetoothConstants;
|
||||
import org.briarproject.bramble.api.plugin.LanTcpConstants;
|
||||
import org.briarproject.bramble.api.plugin.TorConstants;
|
||||
@@ -54,6 +58,7 @@ import static android.support.v4.view.GravityCompat.START;
|
||||
import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||
import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
||||
@@ -61,6 +66,8 @@ import static org.briarproject.briar.android.navdrawer.NavDrawerController.Expir
|
||||
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.UPDATE;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getDaysUntilExpiry;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class NavDrawerActivity extends BriarActivity implements
|
||||
BaseFragmentListener, TransportStateListener,
|
||||
OnNavigationItemSelectedListener {
|
||||
@@ -112,9 +119,8 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
super.onCreate(state);
|
||||
exitIfStartupFailed(getIntent());
|
||||
setContentView(R.layout.activity_nav_drawer);
|
||||
@@ -125,8 +131,9 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
GridView transportsView = findViewById(R.id.transportsView);
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
ActionBar actionBar = requireNonNull(getSupportActionBar());
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setHomeButtonEnabled(true);
|
||||
|
||||
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar,
|
||||
R.string.nav_drawer_open_description,
|
||||
@@ -165,7 +172,8 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int request, int result, Intent data) {
|
||||
protected void onActivityResult(int request, int result,
|
||||
@Nullable Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
if (request == REQUEST_PASSWORD && result == RESULT_OK) {
|
||||
controller.shouldAskForDozeWhitelisting(this,
|
||||
@@ -253,7 +261,7 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostCreate(Bundle savedInstanceState) {
|
||||
public void onPostCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
drawerToggle.syncState();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.briarproject.briar.android.panic;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.preference.PreferenceManager;
|
||||
|
||||
@@ -20,6 +19,7 @@ import info.guardianproject.panic.Panic;
|
||||
import info.guardianproject.panic.PanicResponder;
|
||||
import info.guardianproject.trustedintents.TrustedIntents;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static org.briarproject.briar.android.panic.PanicPreferencesFragment.KEY_LOCK;
|
||||
import static org.briarproject.briar.android.panic.PanicPreferencesFragment.KEY_PURGE;
|
||||
|
||||
@@ -69,7 +69,7 @@ public class PanicResponderActivity extends BriarActivity {
|
||||
}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
if (SDK_INT >= 21) {
|
||||
finishAndRemoveTask();
|
||||
} else {
|
||||
finish();
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.briar.android.privategroup.creation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
@@ -12,6 +13,8 @@ import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
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;
|
||||
@@ -23,6 +26,8 @@ import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
|
||||
import static org.briarproject.briar.android.util.UiUtils.enterPressed;
|
||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class CreateGroupFragment extends BaseFragment {
|
||||
|
||||
public final static String TAG = CreateGroupFragment.class.getName();
|
||||
@@ -40,8 +45,13 @@ public class CreateGroupFragment extends BaseFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_create_group, container,
|
||||
false);
|
||||
@@ -87,11 +97,6 @@ public class CreateGroupFragment extends BaseFragment {
|
||||
listener.showSoftKeyboard(nameEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
|
||||
@@ -2,10 +2,13 @@ package org.briarproject.briar.android.privategroup.creation;
|
||||
|
||||
import android.support.annotation.StringRes;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.sharing.BaseMessageFragment;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class CreateGroupMessageFragment extends BaseMessageFragment {
|
||||
|
||||
private final static String TAG =
|
||||
@@ -28,9 +31,4 @@ public class CreateGroupMessageFragment extends BaseMessageFragment {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.briar.android.privategroup.creation;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
@@ -13,6 +14,7 @@ import org.briarproject.briar.android.contactselection.SelectableContactItem;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@@ -33,14 +35,14 @@ public class GroupInviteFragment extends ContactSelectorFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getActivity().setTitle(R.string.groups_invite_members);
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
requireNonNull(getActivity()).setTitle(R.string.groups_invite_members);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -37,6 +37,7 @@ import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -57,17 +58,23 @@ public class GroupListFragment extends BaseFragment implements
|
||||
private GroupListAdapter adapter;
|
||||
private Snackbar snackbar;
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
controller.setGroupListListener(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
getActivity().setTitle(R.string.groups_button);
|
||||
requireNonNull(getActivity()).setTitle(R.string.groups_button);
|
||||
|
||||
View v = inflater.inflate(R.layout.list, container, false);
|
||||
|
||||
adapter = new GroupListAdapter(getContext(), this);
|
||||
adapter = new GroupListAdapter(getActivity(), this);
|
||||
list = v.findViewById(R.id.list);
|
||||
list.setEmptyImage(R.drawable.ic_empty_state_group_list);
|
||||
list.setEmptyText(R.string.groups_list_empty);
|
||||
@@ -79,17 +86,11 @@ public class GroupListFragment extends BaseFragment implements
|
||||
snackbar.getView().setBackgroundResource(R.color.briar_primary);
|
||||
snackbar.setAction(R.string.show, this);
|
||||
snackbar.setActionTextColor(ContextCompat
|
||||
.getColor(getContext(), R.color.briar_button_text_positive));
|
||||
.getColor(getActivity(), R.color.briar_button_text_positive));
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
controller.setGroupListListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
@@ -75,6 +75,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENA
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_AUTOMATIC;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
@@ -111,6 +112,8 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
public static final String NOTIFY_SIGN_IN = "pref_key_notify_sign_in";
|
||||
public static final String TOR_NETWORK = "pref_key_tor_network";
|
||||
public static final String TOR_MOBILE = "pref_key_tor_mobile_data";
|
||||
public static final String TOR_ONLY_WHEN_CHARGING =
|
||||
"pref_key_tor_only_when_charging";
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(SettingsFragment.class.getName());
|
||||
@@ -120,6 +123,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
private ListPreference enableBluetooth;
|
||||
private ListPreference torNetwork;
|
||||
private SwitchPreference torMobile;
|
||||
private SwitchPreference torOnlyWhenCharging;
|
||||
private SwitchPreference screenLock;
|
||||
private ListPreference screenLockTimeout;
|
||||
private SwitchPreference notifyPrivateMessages;
|
||||
@@ -165,6 +169,8 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
enableBluetooth = (ListPreference) findPreference("pref_key_bluetooth");
|
||||
torNetwork = (ListPreference) findPreference(TOR_NETWORK);
|
||||
torMobile = (SwitchPreference) findPreference(TOR_MOBILE);
|
||||
torOnlyWhenCharging =
|
||||
(SwitchPreference) findPreference(TOR_ONLY_WHEN_CHARGING);
|
||||
screenLock = (SwitchPreference) findPreference(PREF_SCREEN_LOCK);
|
||||
screenLockTimeout =
|
||||
(ListPreference) findPreference(PREF_SCREEN_LOCK_TIMEOUT);
|
||||
@@ -202,6 +208,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
enableBluetooth.setOnPreferenceChangeListener(this);
|
||||
torNetwork.setOnPreferenceChangeListener(this);
|
||||
torMobile.setOnPreferenceChangeListener(this);
|
||||
torOnlyWhenCharging.setOnPreferenceChangeListener(this);
|
||||
screenLock.setOnPreferenceChangeListener(this);
|
||||
screenLockTimeout.setOnPreferenceChangeListener(this);
|
||||
|
||||
@@ -362,6 +369,10 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
torSettings.getBoolean(PREF_TOR_MOBILE, true);
|
||||
torMobile.setChecked(torMobileSetting);
|
||||
|
||||
boolean torChargingSetting =
|
||||
torSettings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING, false);
|
||||
torOnlyWhenCharging.setChecked(torChargingSetting);
|
||||
|
||||
displayScreenLockSetting();
|
||||
|
||||
if (SDK_INT < 26) {
|
||||
@@ -423,6 +434,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
enableBluetooth.setEnabled(enabled);
|
||||
torNetwork.setEnabled(enabled);
|
||||
torMobile.setEnabled(enabled);
|
||||
torOnlyWhenCharging.setEnabled(enabled);
|
||||
if (!enabled) screenLock.setEnabled(false);
|
||||
notifyPrivateMessages.setEnabled(enabled);
|
||||
notifyGroupMessages.setEnabled(enabled);
|
||||
@@ -519,6 +531,9 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
} else if (preference == torMobile) {
|
||||
boolean torMobileSetting = (Boolean) newValue;
|
||||
storeTorMobileSetting(torMobileSetting);
|
||||
} else if (preference == torOnlyWhenCharging) {
|
||||
boolean torChargingSetting = (Boolean) newValue;
|
||||
storeTorChargingSetting(torChargingSetting);
|
||||
} else if (preference == screenLock) {
|
||||
Settings s = new Settings();
|
||||
s.putBoolean(PREF_SCREEN_LOCK, (Boolean) newValue);
|
||||
@@ -585,6 +600,12 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
mergeSettings(s, TOR_NAMESPACE);
|
||||
}
|
||||
|
||||
private void storeTorChargingSetting(boolean torChargingSetting) {
|
||||
Settings s = new Settings();
|
||||
s.putBoolean(PREF_TOR_ONLY_WHEN_CHARGING, torChargingSetting);
|
||||
mergeSettings(s, TOR_NAMESPACE);
|
||||
}
|
||||
|
||||
private void storeBluetoothSettings(boolean btSetting) {
|
||||
Settings s = new Settings();
|
||||
s.putBoolean(PREF_BT_ENABLE, btSetting);
|
||||
|
||||
@@ -37,7 +37,7 @@ public abstract class BaseMessageFragment extends BaseFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@Nullable LayoutInflater inflater,
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package org.briarproject.briar.android.sharing;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class ShareBlogMessageFragment extends BaseMessageFragment {
|
||||
|
||||
public final static String TAG = ShareBlogMessageFragment.class.getName();
|
||||
@@ -18,9 +22,9 @@ public class ShareBlogMessageFragment extends BaseMessageFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
setTitle(R.string.blogs_sharing_share);
|
||||
return super.onCreateView(inflater, container, savedInstanceState);
|
||||
}
|
||||
@@ -37,11 +41,6 @@ public class ShareBlogMessageFragment extends BaseMessageFragment {
|
||||
return R.string.forum_share_message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package org.briarproject.briar.android.sharing;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class ShareForumMessageFragment extends BaseMessageFragment {
|
||||
|
||||
public final static String TAG = ShareForumMessageFragment.class.getName();
|
||||
@@ -18,8 +22,9 @@ public class ShareForumMessageFragment extends BaseMessageFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
setTitle(R.string.forum_share_button);
|
||||
return super.onCreateView(inflater, container, savedInstanceState);
|
||||
@@ -37,11 +42,6 @@ public class ShareForumMessageFragment extends BaseMessageFragment {
|
||||
return R.string.forum_share_message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
|
||||
@@ -258,7 +258,8 @@ public class UiUtils {
|
||||
}
|
||||
|
||||
public static boolean isSamsung7() {
|
||||
return SDK_INT == 24 && MANUFACTURER.equalsIgnoreCase("Samsung");
|
||||
return (SDK_INT == 24 || SDK_INT == 25) &&
|
||||
MANUFACTURER.equalsIgnoreCase("Samsung");
|
||||
}
|
||||
|
||||
public static void setFilterTouchesWhenObscured(View v, boolean filter) {
|
||||
|
||||
@@ -1,43 +1,27 @@
|
||||
package org.briarproject.briar.android.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.constraint.ConstraintLayout;
|
||||
import android.support.v7.graphics.Palette;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.bumptech.glide.load.DataSource;
|
||||
import com.bumptech.glide.load.engine.GlideException;
|
||||
import com.bumptech.glide.request.RequestListener;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
|
||||
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
||||
import static android.graphics.Color.BLACK;
|
||||
import static android.graphics.Color.WHITE;
|
||||
import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_YES;
|
||||
import static android.support.v7.app.AppCompatDelegate.getDefaultNightMode;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
|
||||
import static com.bumptech.glide.load.resource.bitmap.DownsampleStrategy.FIT_CENTER;
|
||||
import static android.support.v4.content.ContextCompat.getColor;
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@NotNullByDefault
|
||||
public class ImagePreview extends ConstraintLayout {
|
||||
|
||||
private final ImageView imageView;
|
||||
private final int backgroundColor =
|
||||
getDefaultNightMode() == MODE_NIGHT_YES ? BLACK : WHITE;
|
||||
private final RecyclerView imageList;
|
||||
|
||||
@Nullable
|
||||
private ImagePreviewListener listener;
|
||||
@@ -59,9 +43,12 @@ public class ImagePreview extends ConstraintLayout {
|
||||
context.getSystemService(LAYOUT_INFLATER_SERVICE));
|
||||
inflater.inflate(R.layout.image_preview, this, true);
|
||||
|
||||
// find image view and set background color
|
||||
imageView = findViewById(R.id.imageView);
|
||||
imageView.setBackgroundColor(backgroundColor);
|
||||
// set background color
|
||||
setBackgroundColor(getColor(context, R.color.card_background));
|
||||
|
||||
// find list
|
||||
imageList = findViewById(R.id.imageList);
|
||||
imageList.addItemDecoration(new ImagePreviewDecoration(context));
|
||||
|
||||
// set cancel listener
|
||||
findViewById(R.id.imageCancelButton).setOnClickListener(view -> {
|
||||
@@ -73,46 +60,27 @@ public class ImagePreview extends ConstraintLayout {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
void showPreview(List<Uri> imageUris) {
|
||||
void showPreview(Collection<Uri> imageUris) {
|
||||
if (listener == null) throw new IllegalStateException();
|
||||
if (imageUris.size() == 1) {
|
||||
LayoutParams params = (LayoutParams) imageList.getLayoutParams();
|
||||
params.width = MATCH_PARENT;
|
||||
imageList.setLayoutParams(params);
|
||||
}
|
||||
setVisibility(VISIBLE);
|
||||
GlideApp.with(imageView)
|
||||
.asBitmap()
|
||||
.load(imageUris.get(0)) // TODO show more than the first
|
||||
.diskCacheStrategy(NONE)
|
||||
.downsample(FIT_CENTER)
|
||||
.addListener(new RequestListener<Bitmap>() {
|
||||
@Override
|
||||
public boolean onLoadFailed(@Nullable GlideException e,
|
||||
Object model, Target<Bitmap> target,
|
||||
boolean isFirstResource) {
|
||||
if (listener != null) listener.onCancel();
|
||||
Toast.makeText(imageView.getContext(),
|
||||
R.string.image_attach_error, LENGTH_LONG)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onResourceReady(Bitmap resource,
|
||||
Object model, Target<Bitmap> target,
|
||||
DataSource dataSource, boolean isFirstResource) {
|
||||
Palette.from(resource).generate(
|
||||
ImagePreview.this::onPaletteGenerated);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.into(imageView);
|
||||
imageList.setAdapter(new ImagePreviewAdapter(imageUris, listener));
|
||||
}
|
||||
|
||||
void onPaletteGenerated(@Nullable Palette palette) {
|
||||
if (palette == null) return;
|
||||
int color = getDefaultNightMode() == MODE_NIGHT_YES ?
|
||||
palette.getDarkMutedColor(backgroundColor) :
|
||||
palette.getLightMutedColor(backgroundColor);
|
||||
imageView.setBackgroundColor(color);
|
||||
void removeUri(Uri uri) {
|
||||
ImagePreviewAdapter adapter =
|
||||
(ImagePreviewAdapter) imageList.getAdapter();
|
||||
requireNonNull(adapter).removeUri(uri);
|
||||
}
|
||||
|
||||
interface ImagePreviewListener {
|
||||
|
||||
void onUriError(Uri uri);
|
||||
|
||||
void onCancel();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.briarproject.briar.android.view;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.v7.widget.RecyclerView.Adapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@NotNullByDefault
|
||||
class ImagePreviewAdapter extends Adapter<ImagePreviewViewHolder> {
|
||||
|
||||
private final List<Uri> items;
|
||||
private final ImagePreviewListener listener;
|
||||
@LayoutRes
|
||||
private final int layout;
|
||||
|
||||
ImagePreviewAdapter(Collection<Uri> items, ImagePreviewListener listener) {
|
||||
this.items = new ArrayList<>(items);
|
||||
this.listener = listener;
|
||||
this.layout = items.size() == 1 ?
|
||||
R.layout.list_item_image_preview_single :
|
||||
R.layout.list_item_image_preview;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImagePreviewViewHolder onCreateViewHolder(ViewGroup viewGroup,
|
||||
int type) {
|
||||
View v = LayoutInflater.from(viewGroup.getContext())
|
||||
.inflate(layout, viewGroup, false);
|
||||
return new ImagePreviewViewHolder(v, requireNonNull(listener));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ImagePreviewViewHolder viewHolder,
|
||||
int position) {
|
||||
viewHolder.bind(items.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
void removeUri(Uri uri) {
|
||||
int pos = items.indexOf(uri);
|
||||
items.remove(uri);
|
||||
notifyItemRemoved(pos);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.briarproject.briar.android.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Rect;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.RecyclerView.ItemDecoration;
|
||||
import android.support.v7.widget.RecyclerView.State;
|
||||
import android.view.View;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
@NotNullByDefault
|
||||
class ImagePreviewDecoration extends ItemDecoration {
|
||||
|
||||
private final int border;
|
||||
|
||||
ImagePreviewDecoration(Context ctx) {
|
||||
Resources res = ctx.getResources();
|
||||
border = res.getDimensionPixelSize(R.dimen.message_bubble_border);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
|
||||
State state) {
|
||||
if (state.getItemCount() == parent.getChildAdapterPosition(view) + 1) {
|
||||
// no decoration for last item in the list
|
||||
return;
|
||||
}
|
||||
outRect.right = border;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.briarproject.briar.android.view;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView.ViewHolder;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import com.bumptech.glide.load.DataSource;
|
||||
import com.bumptech.glide.load.engine.GlideException;
|
||||
import com.bumptech.glide.request.RequestListener;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||
import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener;
|
||||
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
|
||||
import static com.bumptech.glide.load.resource.bitmap.DownsampleStrategy.FIT_CENTER;
|
||||
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
|
||||
|
||||
@NotNullByDefault
|
||||
class ImagePreviewViewHolder extends ViewHolder {
|
||||
|
||||
@DrawableRes
|
||||
private static final int ERROR_RES = R.drawable.ic_image_broken;
|
||||
|
||||
private final ImagePreviewListener listener;
|
||||
|
||||
private final ImageView imageView;
|
||||
private final ProgressBar progressBar;
|
||||
|
||||
ImagePreviewViewHolder(View v, ImagePreviewListener listener) {
|
||||
super(v);
|
||||
this.listener = listener;
|
||||
this.imageView = v.findViewById(R.id.imageView);
|
||||
this.progressBar = v.findViewById(R.id.progressBar);
|
||||
}
|
||||
|
||||
void bind(Uri uri) {
|
||||
GlideApp.with(imageView)
|
||||
.load(uri)
|
||||
.diskCacheStrategy(NONE)
|
||||
.error(ERROR_RES)
|
||||
.downsample(FIT_CENTER)
|
||||
.transition(withCrossFade())
|
||||
.addListener(new RequestListener<Drawable>() {
|
||||
@Override
|
||||
public boolean onLoadFailed(@Nullable GlideException e,
|
||||
Object model, Target<Drawable> target,
|
||||
boolean isFirstResource) {
|
||||
listener.onUriError(uri);
|
||||
progressBar.setVisibility(INVISIBLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onResourceReady(Drawable resource,
|
||||
Object model, Target<Drawable> target,
|
||||
DataSource dataSource, boolean isFirstResource) {
|
||||
progressBar.setVisibility(INVISIBLE);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +1,48 @@
|
||||
package org.briarproject.briar.android.view;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.view.AbsSavedState;
|
||||
import android.support.v7.app.AlertDialog.Builder;
|
||||
import android.support.v7.widget.AppCompatImageButton;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
|
||||
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptStateChangeListener;
|
||||
|
||||
import static android.content.Intent.ACTION_GET_CONTENT;
|
||||
import static android.content.Intent.ACTION_OPEN_DOCUMENT;
|
||||
import static android.content.Intent.CATEGORY_OPENABLE;
|
||||
import static android.content.Intent.EXTRA_ALLOW_MULTIPLE;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.support.v4.content.ContextCompat.getColor;
|
||||
import static android.support.v4.view.AbsSavedState.EMPTY_STATE;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
|
||||
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED;
|
||||
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
public class TextAttachmentController extends TextSendController
|
||||
implements ImagePreviewListener {
|
||||
|
||||
@@ -40,6 +52,7 @@ public class TextAttachmentController extends TextSendController
|
||||
private final AttachImageListener imageListener;
|
||||
|
||||
private CharSequence textHint;
|
||||
private boolean hasImageSupport = false;
|
||||
private List<Uri> imageUris = emptyList();
|
||||
|
||||
public TextAttachmentController(TextInputView v, ImagePreview imagePreview,
|
||||
@@ -76,20 +89,41 @@ public class TextAttachmentController extends TextSendController
|
||||
return !imageUris.isEmpty();
|
||||
}
|
||||
|
||||
/***
|
||||
* By default, image support is disabled.
|
||||
* Once you know that it is supported in the current context,
|
||||
* call this method to enable it.
|
||||
*/
|
||||
public void setImagesSupported() {
|
||||
hasImageSupport = true;
|
||||
imageButton.setImageResource(R.drawable.ic_image);
|
||||
}
|
||||
|
||||
private void onImageButtonClicked() {
|
||||
if (!hasImageSupport) {
|
||||
Context ctx = imageButton.getContext();
|
||||
Builder builder = new Builder(ctx, R.style.OnboardingDialogTheme);
|
||||
builder.setTitle(
|
||||
ctx.getString(R.string.dialog_title_no_image_support));
|
||||
builder.setMessage(
|
||||
ctx.getString(R.string.dialog_message_no_image_support));
|
||||
builder.setPositiveButton(R.string.got_it, null);
|
||||
builder.show();
|
||||
return;
|
||||
}
|
||||
Intent intent = new Intent(SDK_INT >= 19 ?
|
||||
ACTION_OPEN_DOCUMENT : ACTION_GET_CONTENT);
|
||||
intent.addCategory(CATEGORY_OPENABLE);
|
||||
intent.setType("image/*");
|
||||
if (SDK_INT >= 18) // TODO set true to allow attaching multiple images
|
||||
intent.putExtra(EXTRA_ALLOW_MULTIPLE, false);
|
||||
if (SDK_INT >= 18) intent.putExtra(EXTRA_ALLOW_MULTIPLE, true);
|
||||
requireNonNull(imageListener).onAttachImage(intent);
|
||||
}
|
||||
|
||||
public void onImageReceived(@Nullable Intent resultData) {
|
||||
if (resultData == null) return;
|
||||
if (resultData.getData() != null) {
|
||||
imageUris = singletonList(resultData.getData());
|
||||
imageUris = new ArrayList<>(1);
|
||||
imageUris.add(resultData.getData());
|
||||
onNewUris();
|
||||
} else if (SDK_INT >= 18 && resultData.getClipData() != null) {
|
||||
ClipData clipData = resultData.getClipData();
|
||||
@@ -163,20 +197,50 @@ public class TextAttachmentController extends TextSendController
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Parcelable onRestoreInstanceState(@NonNull Parcelable inState) {
|
||||
public Parcelable onRestoreInstanceState(Parcelable inState) {
|
||||
SavedState state = (SavedState) inState;
|
||||
imageUris = state.imageUris;
|
||||
imageUris = requireNonNull(state.imageUris);
|
||||
onNewUris();
|
||||
return state.getSuperState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUriError(Uri uri) {
|
||||
imageUris.remove(uri);
|
||||
imagePreview.removeUri(uri);
|
||||
if (imageUris.isEmpty()) onCancel();
|
||||
Toast.makeText(textInput.getContext(), R.string.image_attach_error,
|
||||
LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
textInput.clearText();
|
||||
reset();
|
||||
}
|
||||
|
||||
public void showImageOnboarding(Activity activity,
|
||||
Runnable onOnboardingSeen) {
|
||||
PromptStateChangeListener listener = (prompt, state) -> {
|
||||
if (state == STATE_DISMISSED || state == STATE_FINISHED) {
|
||||
onOnboardingSeen.run();
|
||||
}
|
||||
};
|
||||
int color = resolveColorAttribute(activity, R.attr.colorControlNormal);
|
||||
new MaterialTapTargetPrompt.Builder(activity,
|
||||
R.style.OnboardingDialogTheme).setTarget(imageButton)
|
||||
.setPrimaryText(R.string.dialog_title_image_support)
|
||||
.setSecondaryText(R.string.dialog_message_image_support)
|
||||
.setBackgroundColour(getColor(activity, R.color.briar_primary))
|
||||
.setIcon(R.drawable.ic_image)
|
||||
.setIconDrawableColourFilter(color)
|
||||
.setPromptStateChangeListener(listener)
|
||||
.show();
|
||||
}
|
||||
|
||||
private static class SavedState extends AbsSavedState {
|
||||
|
||||
@Nullable
|
||||
private List<Uri> imageUris;
|
||||
|
||||
private SavedState(Parcelable superState) {
|
||||
@@ -195,16 +259,18 @@ public class TextAttachmentController extends TextSendController
|
||||
out.writeList(imageUris);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<SavedState> CREATOR
|
||||
= new Parcelable.Creator<SavedState>() {
|
||||
public SavedState createFromParcel(Parcel in) {
|
||||
return new SavedState(in);
|
||||
}
|
||||
public static final Creator<SavedState> CREATOR =
|
||||
new Creator<SavedState>() {
|
||||
@Override
|
||||
public SavedState createFromParcel(Parcel in) {
|
||||
return new SavedState(in);
|
||||
}
|
||||
|
||||
public SavedState[] newArray(int size) {
|
||||
return new SavedState[size];
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public SavedState[] newArray(int size) {
|
||||
return new SavedState[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public interface AttachImageListener {
|
||||
|
||||
11
briar-android/src/main/res/drawable/ic_image_off.xml
Normal file
11
briar-android/src/main/res/drawable/ic_image_off.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:alpha="0.56"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M2.28,3L1,4.27L3,6.27V19A2,2 0 0,0 5,21H17.73L19.73,23L21,21.72L2.28,3M4.83,3L21,19.17V5C21,3.89 20.1,3 19,3H4.83M8.5,13.5L11,16.5L12,15.25L14.73,18H5L8.5,13.5Z"/>
|
||||
</vector>
|
||||
@@ -34,7 +34,7 @@
|
||||
android:layout_marginLeft="@dimen/margin_medium"
|
||||
android:layout_marginStart="@dimen/margin_medium"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/action_bar_text"
|
||||
tools:text="Contact Name of someone who chose a long name"/>
|
||||
@@ -49,7 +49,8 @@
|
||||
android:id="@+id/conversationView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="2"/>
|
||||
android:layout_weight="2"
|
||||
app:scrollToEnd="false"/>
|
||||
|
||||
<org.briarproject.briar.android.view.ImagePreview
|
||||
android:id="@+id/imagePreview"
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
android:layout_weight="1"
|
||||
android:background="@color/thread_item_background">
|
||||
|
||||
<org.briarproject.briar.android.view.BriarRecyclerView
|
||||
android:id="@+id/list"
|
||||
|
||||
@@ -13,21 +13,23 @@
|
||||
android:id="@+id/divider"
|
||||
style="@style/Divider.Horizontal"
|
||||
android:layout_alignParentTop="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/imageView"
|
||||
app:layout_constraintBottom_toTopOf="@+id/imageList"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="match_parent"
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/imageList"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/divider"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:srcCompat="@tools:sample/avatars"/>
|
||||
tools:listitem="@layout/list_item_image"/>
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
android:id="@+id/imageCancelButton"
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
tools:layout_height="200dp">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:srcCompat="@tools:sample/avatars"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:layout_height="200dp">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:srcCompat="@tools:sample/backgrounds/scenic"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
@@ -40,7 +40,7 @@
|
||||
android:focusable="true"
|
||||
android:padding="4dp"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_image"
|
||||
android:src="@drawable/ic_image_off"
|
||||
android:visibility="invisible"
|
||||
app:tint="?attr/colorControlNormal"/>
|
||||
|
||||
|
||||
@@ -141,6 +141,8 @@
|
||||
<string name="dialog_title_delete_contact">تأكيد حذف جهة الإتصال</string>
|
||||
<string name="dialog_message_delete_contact">هل أنت متأكد/ة من أنك تريد حذف جهة الإتصال هذه وكل الرسائل المتبادلة بينكما؟</string>
|
||||
<string name="contact_deleted_toast">تم حذف جهة اتصال</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">أنتَ/أنتِ</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">إضافة جهة اتصال</string>
|
||||
<string name="face_to_face">لا بد من مقابلة الشخص الذي تريد/ين إضافته كجهة اتصال.\n\nهذا سيمنع أي شخص من انتحال شخصيتك أو قراءة رسائلك في المستقبل.</string>
|
||||
|
||||
@@ -48,6 +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">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>
|
||||
@@ -115,12 +116,14 @@
|
||||
<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="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>
|
||||
<string name="delete_contact">Suprimeix aquest contacte</string>
|
||||
<string name="dialog_title_delete_contact">Confirmeu la supressió del contacte</string>
|
||||
<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-->
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Afegiu un contacte</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>
|
||||
@@ -131,6 +134,8 @@
|
||||
<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 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>
|
||||
@@ -340,8 +345,10 @@
|
||||
<string name="tor_mobile_data_title">Usa dades mòbils</string>
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">Seguretat</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 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"-->
|
||||
@@ -370,6 +377,7 @@
|
||||
<string name="panic_app_setting_none">Cap</string>
|
||||
<string name="dialog_title_connect_panic_app">Confirmeu l\'aplicació de pànic</string>
|
||||
<string name="dialog_message_connect_panic_app">Segur que voleu permetre que %1$s desencadeni accions destructives a conseqüència del botó de pànic?</string>
|
||||
<string name="panic_setting_destructive_action">Accions destructives</string>
|
||||
<string name="panic_setting_signout_title">Tanca la sessió</string>
|
||||
<string name="panic_setting_signout_summary">Tanca la sessió de Briar si es prem un botó de pànic</string>
|
||||
<string name="purge_setting_title">Esborreu el compte</string>
|
||||
@@ -431,6 +439,10 @@
|
||||
<!--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 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>
|
||||
@@ -443,9 +455,15 @@
|
||||
<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">Alice</string>
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<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">Carol</string>
|
||||
<!--This is a message to be used in screenshots. Please use the same translation for Bob!-->
|
||||
<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 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>
|
||||
|
||||
@@ -122,6 +122,8 @@
|
||||
<string name="dialog_title_delete_contact">Potvrdit odstranění kontaktu</string>
|
||||
<string name="dialog_message_delete_contact">Jste si jisti, že chcete odstranit kontakt a všechny související zprávy s tímto kontaktem?</string>
|
||||
<string name="contact_deleted_toast">Kontakt odstraněn</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">Vy</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Přidat kontakt</string>
|
||||
<string name="face_to_face">Musíte se osobně setkat s osobou, kterou si chcete přidat jako kontakt.\n\nToto v budoucnu zabrání komukoli, aby se za vás vydával nebo četl Vaše zprávy.</string>
|
||||
@@ -334,6 +336,8 @@
|
||||
<string name="pref_lock_timeout_60">1 hodina</string>
|
||||
<string name="pref_lock_timeout_never">Nikdy</string>
|
||||
<string name="change_password">Změnit heslo</string>
|
||||
<string name="current_password">Současné heslo</string>
|
||||
<string name="choose_new_password">Nové heslo</string>
|
||||
<string name="password_changed">Heslo bylo změněno.</string>
|
||||
<string name="panic_setting">Nastavení tlačítka Paniky</string>
|
||||
<string name="panic_setting_title">Tlačítko Paniky</string>
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
<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 komprimieren...</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">Navigationsleiste öffnen</string>
|
||||
<string name="nav_drawer_close_description">Navigationsleiste schliessen</string>
|
||||
@@ -115,12 +116,23 @@
|
||||
<string name="date_no_private_messages">Keine Nachrichten.</string>
|
||||
<string name="no_private_messages">Keine Nachrichten vorhanden</string>
|
||||
<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">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_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>
|
||||
<string name="contact_deleted_toast">Kontakt gelöscht</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">Sie</string>
|
||||
<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">Konnte Bild nicht speichern.</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Kontakt 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>
|
||||
@@ -131,6 +143,8 @@
|
||||
<string name="contact_added_toast">Kontakt hinzugefügt: %s</string>
|
||||
<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 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>
|
||||
@@ -341,7 +355,7 @@
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">Sicherheit</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_disabled_summary">Um dieses Feature zu benutzen, aktiviere den Systemsperrbildschirm</string>
|
||||
<!--The %s placeholder is replaced with the following time spans, e.g. 5 Minutes, 1 Hour-->
|
||||
<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"-->
|
||||
@@ -370,6 +384,7 @@
|
||||
<string name="panic_app_setting_none">Keine</string>
|
||||
<string name="dialog_title_connect_panic_app">Bestätige die Panik-App</string>
|
||||
<string name="dialog_message_connect_panic_app">Bist du sicher, dass du %1$s erlauben willst, Panik-Button-Ereignisse auszulösen?</string>
|
||||
<string name="panic_setting_destructive_action">Zerstörerische Aktionen</string>
|
||||
<string name="panic_setting_signout_title">Abmelden</string>
|
||||
<string name="panic_setting_signout_summary">Von Briar abmelden, wenn ein Panik-Button aktiviert wird</string>
|
||||
<string name="purge_setting_title">Konto löschen</string>
|
||||
@@ -378,8 +393,8 @@
|
||||
<string name="uninstall_setting_summary">Diese Aktion benötigt eine manuelle Bestätigung im Falle eines Panik-Ereignisses</string>
|
||||
<!--Settings Notifications-->
|
||||
<string name="notification_settings_title">Benachrichtigungen</string>
|
||||
<string name="notify_sign_in_title">Anmeldeerinerung</string>
|
||||
<string name="notify_sign_in_summary">Zeige eine Erinnerung bei Systemstart oder die App geupdated wurde</string>
|
||||
<string name="notify_sign_in_title">Anmeldeerinnerung</string>
|
||||
<string name="notify_sign_in_summary">Zeigt eine Erinnerung an, wenn das Telefon startet oder die App aktualisiert wurde</string>
|
||||
<string name="notify_private_messages_setting_title">Private Nachrichten</string>
|
||||
<string name="notify_private_messages_setting_summary">Benachrichtigungen für private Nachrichten anzeigen</string>
|
||||
<string name="notify_private_messages_setting_summary_26">Benachrichtigungen für private Nachrichten konfigurieren</string>
|
||||
@@ -431,21 +446,31 @@
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">Berechtigung Kamera</string>
|
||||
<string name="permission_camera_request_body">Um den QR-Code zu scannen, benötigt Briar Zugriff auf die Kamera.</string>
|
||||
<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, 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>
|
||||
<!--App Locking-->
|
||||
<string name="lock_unlock">Briar entsperren</string>
|
||||
<string name="lock_unlock_verbose">Um Briar zu entsperren, gib deinen Systempin,-geste oder -password ein</string>
|
||||
<string name="lock_unlock_verbose">Um Briar zu entsperren, gib deine System-PIN, Muster oder Passwort ein</string>
|
||||
<string name="lock_unlock_fingerprint_description">Um fortzufahren berühre den Fingerabdrucksensor mit einem registrierten Finger</string>
|
||||
<string name="lock_unlock_password">Benutze Passwort</string>
|
||||
<string name="lock_unlock_password">Passwort benutzen</string>
|
||||
<string name="lock_is_locked">Briar ist gesperrt</string>
|
||||
<string name="lock_tap_to_unlock">Zum Entsperren antippen</string>
|
||||
<!--Screenshots-->
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<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">Bob</string>
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<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">Hi Bob!</string>
|
||||
<!--This is a message to be used in screenshots. Please use the same translation for Alice!-->
|
||||
<string name="screenshot_message_2">Hi Alice! Danke, dass du mir von Briar erzählt hast!</string>
|
||||
<!--This is a message to be used in screenshots.-->
|
||||
<string name="screenshot_message_3">Kein Problem, ich hoffe, es gefällt dir 😀</string>
|
||||
</resources>
|
||||
|
||||
@@ -116,6 +116,9 @@
|
||||
<string name="date_no_private_messages">Sin mensajes.</string>
|
||||
<string name="no_private_messages">No hay mensajes que mostrar</string>
|
||||
<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 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>
|
||||
@@ -123,6 +126,13 @@
|
||||
<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>
|
||||
<string name="contact_deleted_toast">Contacto eliminado</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">Tú</string>
|
||||
<string name="save_image">Guardar imagen</string>
|
||||
<string name="dialog_title_save_image">¿Guardar imagen?</string>
|
||||
<string name="dialog_message_save_image">Guardando esta imagen le permitirá a otras aplicaciones accederla.\n\n¿Estás seguro que quieres guardarla?</string>
|
||||
<string name="save_image_success">La imagen fue guardada</string>
|
||||
<string name="save_image_error">No se pudo guardar imagen</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Añadir un contacto</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>
|
||||
|
||||
@@ -116,6 +116,9 @@
|
||||
<string name="date_no_private_messages">Ez dago mezurik.</string>
|
||||
<string name="no_private_messages">Ez dago erakusteko mezurik</string>
|
||||
<string name="message_hint">Idatzi mezua</string>
|
||||
<string name="image_caption_hint">Gehitu epigrafea (aukerakoa)</string>
|
||||
<string name="image_attach">Erantsi irudia</string>
|
||||
<string name="image_attach_error">Ezin izan da irudia erantsi</string>
|
||||
<string name="set_contact_alias">Aldatu kontaktuaren izena</string>
|
||||
<string name="set_contact_alias_hint">Kontaktuaren izena</string>
|
||||
<string name="set_alias_button">Aldatu</string>
|
||||
@@ -123,6 +126,13 @@
|
||||
<string name="dialog_title_delete_contact">Baieztatu kontaktua ezabatzea</string>
|
||||
<string name="dialog_message_delete_contact">Ziur zaude kontaktu hau eta berarekin trukatutako mezu guztiak kendu nahi dituzula?</string>
|
||||
<string name="contact_deleted_toast">Kontaktua ezabatuta</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">Zu</string>
|
||||
<string name="save_image">Gorde irudia</string>
|
||||
<string name="dialog_title_save_image">Gorde irudia?</string>
|
||||
<string name="dialog_message_save_image">Irudi hau gordetzeak beste aplikazioei berau atzitea ahalbietuko lieke.\n\nZiur gorde nahi duzula?</string>
|
||||
<string name="save_image_success">Irudia gorde da</string>
|
||||
<string name="save_image_error">Ezin izan da irudia gorde</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Gehitu kontaktu bat</string>
|
||||
<string name="face_to_face">Kontaktu gisa gehitu nahi duzun pertsonarekin aurrez aurre egon behar duzu.\n\nHonek etorkizunean inork zure itxurak egitea edo zure mezuak irakurtzea eragotziko du.</string>
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
<string name="download_briar_button">دانلود Briar (برایر) 1.0</string>
|
||||
<string name="startup_open_database">رمزگشایی سیستم ...</string>
|
||||
<string name="startup_migrate_database">به روز رسانی سیستم ...</string>
|
||||
<string name="startup_compact_database">درحال فشردهسازی پایگاه داده...</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">باز کردن منوی برنامه</string>
|
||||
<string name="nav_drawer_close_description">بستن منوی برنامه</string>
|
||||
@@ -121,11 +122,15 @@
|
||||
<string name="date_no_private_messages">هیچ پیامی موجود نیست</string>
|
||||
<string name="no_private_messages">هیچ پیامی برای نشان دادن وجود ندارد</string>
|
||||
<string name="message_hint">نوشتن پیام</string>
|
||||
<string name="set_contact_alias">تغییر نام مخاطب</string>
|
||||
<string name="set_contact_alias_hint">نام مخاطب</string>
|
||||
<string name="set_alias_button">تغییر</string>
|
||||
<string name="delete_contact">حذف مخاطب</string>
|
||||
<string name="dialog_title_delete_contact">تایید حذف مخاطب</string>
|
||||
<string name="dialog_message_delete_contact">آیا مطمئن هستید که میخواهید این مخاطب و تمام پیام های تبادل شده با آن را حذف کنید؟</string>
|
||||
<string name="contact_deleted_toast">مخاطب حذف شد</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">شما</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">افزودن مخاطب</string>
|
||||
<string name="face_to_face">برای اضافه کردن فرد به عنوان مخاطب باید با او به صورت حضوری ملاقات کنید.
|
||||
@@ -137,12 +142,12 @@
|
||||
<string name="contact_added_toast">مخاطب اضافه شد: %s</string>
|
||||
<string name="contact_already_exists">مخاطب %s از قبل وجود دارد</string>
|
||||
<string name="qr_code_invalid">کد QR نامعتبر می باشد</string>
|
||||
<string name="qr_code_too_old">کد QR که شما اسکن کرده اید متعلق به یک نسخه قدیمی از %s می باشد.
|
||||
<string name="qr_code_too_old">کد QR اسکن شده متعلق به یک نسخه قدیمی از %s است.
|
||||
|
||||
لطفا از مخاطب خود بخواهید تا به آخرین نسخه آپگرید کرده و دوباره تلاش کنید.</string>
|
||||
<string name="qr_code_too_new">کد QR که شما اسکن کرده اید متعلق به یک نسخه جدید از %s می باشد.
|
||||
لطفا از مخاطبتان بخواهید تا به آخرین نسخه ارتقا داده و سپس دوباره تلاش کنید.</string>
|
||||
<string name="qr_code_too_new">کد QR که شما اسکن کردهاید متعلق به یک نسخه جدیدتر از %s است.
|
||||
|
||||
لطفا به آخرین نسخه آپگرید کرده و دوباره تلاش کنید.</string>
|
||||
لطفا به آخرین نسخه ارتقا داده و دوباره تلاش کنید.</string>
|
||||
<string name="camera_error">خطای دوربین</string>
|
||||
<string name="connecting_to_device">اتصال به دستگاهu2026\</string>
|
||||
<string name="authenticating_with_device">تصدیق سازی با دستگاه u2026\</string>
|
||||
@@ -472,6 +477,8 @@
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">دسترسی به دوربین</string>
|
||||
<string name="permission_camera_request_body">برای اسکن کردن کد QR دسترسی به دوربین لازم است.</string>
|
||||
<string name="permission_location_title">مجوز موقعیت محل</string>
|
||||
<string name="permission_camera_location_title">دوربین و موقعیت</string>
|
||||
<string name="permission_camera_denied_body">شما دسترسی به دوربین را رد کرده اید، اما افزودن مخاطب نیاز به دوربین دارد.
|
||||
|
||||
لطفا اجازه دسترسی را بدهید.</string>
|
||||
|
||||
@@ -121,6 +121,8 @@
|
||||
<string name="dialog_title_delete_contact">Vahvista yhteystiedon poistaminen</string>
|
||||
<string name="dialog_message_delete_contact">Oletko varma, että haluat poistaa tämän yhteyshenkilön ja kaikki hänen kanssa vaihdetut viestit?</string>
|
||||
<string name="contact_deleted_toast">Yhteystieto poistettu</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="save_image">Tallenna kuva</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Lisää yhteystieto</string>
|
||||
<string name="face_to_face">Sinun täytyy tavata käyttäjä, jonka haluat lisätä yhteystietoihisi.\n\nTämä estää sen, että joku voisi esittää olevansa sinä tai lukea viestejäsi tulevaisuudessa</string>
|
||||
|
||||
@@ -116,12 +116,23 @@
|
||||
<string name="date_no_private_messages">Aucun message.</string>
|
||||
<string name="no_private_messages">Aucun message à afficher</string>
|
||||
<string name="message_hint">Rédiger le message</string>
|
||||
<string name="image_caption_hint">Ajouter une légende (facultatif)</string>
|
||||
<string name="image_attach">Joindre une image</string>
|
||||
<string name="image_attach_error">Impossible d’attacher une image</string>
|
||||
<string name="set_contact_alias">Changer le nom du contact</string>
|
||||
<string name="set_contact_alias_hint">Nom du contact</string>
|
||||
<string name="set_alias_button">Modifier</string>
|
||||
<string name="delete_contact">Supprimer le contact</string>
|
||||
<string name="dialog_title_delete_contact">Confirmer la suppression du contact</string>
|
||||
<string name="dialog_message_delete_contact">Voulez-vous vraiment supprimer ce contact et tous les messages associés ?</string>
|
||||
<string name="contact_deleted_toast">Le contact a été supprimé</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">Vous</string>
|
||||
<string name="save_image">Enregistrer l’image</string>
|
||||
<string name="dialog_title_save_image">Enregistrer l’image ?</string>
|
||||
<string name="dialog_message_save_image">L’enregistrement de cette image permettra aux autres applis d’y accéder.\n\n Souhaitez-vous vraiment l’enregistrer ?</string>
|
||||
<string name="save_image_success">L’image a été enregistrée</string>
|
||||
<string name="save_image_error">Impossible d’enregistrer l’image</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Ajouter un contact</string>
|
||||
<string name="face_to_face">Vous devez rencontrer la personne que vous voulez ajouter comme contact, afin d’éviter que quelqu’un se fasse passer pour vous et puisse lire vos messages à l’avenir.</string>
|
||||
|
||||
@@ -116,6 +116,9 @@
|
||||
<string name="date_no_private_messages">Sen mensaxes</string>
|
||||
<string name="no_private_messages">Sen mensaxes que amosar</string>
|
||||
<string name="message_hint">Esciba unha mensaxe</string>
|
||||
<string name="image_caption_hint">Engadir un comentario (optativo)</string>
|
||||
<string name="image_attach">Anexar imaxe</string>
|
||||
<string name="image_attach_error">Non se anexou a imaxe</string>
|
||||
<string name="set_contact_alias">Cambiar o nome do contacto</string>
|
||||
<string name="set_contact_alias_hint">Nome do contacto</string>
|
||||
<string name="set_alias_button">Cambiar</string>
|
||||
@@ -123,6 +126,13 @@
|
||||
<string name="dialog_title_delete_contact">Confirme a eliminación do contacto</string>
|
||||
<string name="dialog_message_delete_contact">Segura de querer eliminar este contacto e todas as mensaxes que intercambiaron?</string>
|
||||
<string name="contact_deleted_toast">Contacto eliminado</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">Vostede</string>
|
||||
<string name="save_image">Gardar imaxe</string>
|
||||
<string name="dialog_title_save_image">Gardar imaxe?</string>
|
||||
<string name="dialog_message_save_image">Ao gardar esta imaxe permitirá que outras apps teñan acceso a ela.\n\nSeguro que a quere gardar?</string>
|
||||
<string name="save_image_success">Gardouse a imaxe</string>
|
||||
<string name="save_image_error">Non se gardou a imaxe</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Engada un contacto</string>
|
||||
<string name="face_to_face">Debe atoparse coa persoa que quere engadir como contacto.\n\Isto evitará que calquera poida suplantala ou ler as súas mensaxes no futuro.</string>
|
||||
|
||||
@@ -129,6 +129,9 @@
|
||||
<string name="date_no_private_messages">אין הודעות.</string>
|
||||
<string name="no_private_messages">אין הודעות להראות</string>
|
||||
<string name="message_hint">הקלד הודעה</string>
|
||||
<string name="image_caption_hint">הוסף כיתוב (רשותי)</string>
|
||||
<string name="image_attach">צרף תמונה</string>
|
||||
<string name="image_attach_error">לא היה ניתן לצרף תמונה</string>
|
||||
<string name="set_contact_alias">שַׁנֵּה שם איש קשר</string>
|
||||
<string name="set_contact_alias_hint">שם איש הקשר</string>
|
||||
<string name="set_alias_button">שַׁנֵּה</string>
|
||||
@@ -136,6 +139,13 @@
|
||||
<string name="dialog_title_delete_contact">אשר מחיקת איש קשר</string>
|
||||
<string name="dialog_message_delete_contact">האם אתה בטוח שאתה רוצה למחוק איש קשר זה ואת כל ההודעות שהוחלפו עם איש קשר זה?</string>
|
||||
<string name="contact_deleted_toast">איש קשר נמחק</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">אתה</string>
|
||||
<string name="save_image">שמור תמונה</string>
|
||||
<string name="dialog_title_save_image">לשמור תמונה?</string>
|
||||
<string name="dialog_message_save_image">שמירת תמונה זו תאפשר ליישומים להשיג גישה אל התמונה.\n\nהאם אתה בטוח שאתה רוצה לשמור?</string>
|
||||
<string name="save_image_success">תמונה נשמרה</string>
|
||||
<string name="save_image_error">לא היה ניתן לשמור תמונה</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">הוסף איש קשר</string>
|
||||
<string name="face_to_face">אתם חייבים להפגש עם האדם שאותו תרצו להוסיף כאיש קשר.
|
||||
|
||||
@@ -116,6 +116,9 @@
|
||||
<string name="date_no_private_messages">कोई संदेश नहीं।</string>
|
||||
<string name="no_private_messages">दिखाने के लिए कोई संदेश नहीं</string>
|
||||
<string name="message_hint">संदेश लिखें</string>
|
||||
<string name="image_caption_hint">एक कैप्शन जोड़ें (वैकल्पिक)</string>
|
||||
<string name="image_attach">छवि संलग्न करें</string>
|
||||
<string name="image_attach_error">छवि संलग्न नहीं कर सका</string>
|
||||
<string name="set_contact_alias">संपर्क नाम बदलें</string>
|
||||
<string name="set_contact_alias_hint">संपर्क नाम</string>
|
||||
<string name="set_alias_button">परिवर्तन</string>
|
||||
@@ -123,6 +126,13 @@
|
||||
<string name="dialog_title_delete_contact">संपर्क हटाने की पुष्टि करें</string>
|
||||
<string name="dialog_message_delete_contact">क्या आप निश्चित हैं कि आप इस संपर्क और सभी संदेशों को इस संपर्क से निकाला जाना चाहते हैं?</string>
|
||||
<string name="contact_deleted_toast">संपर्क हटा दिया गया</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">आप</string>
|
||||
<string name="save_image">चित्र को सेव करें</string>
|
||||
<string name="dialog_title_save_image">चित्र को सेव करें?</string>
|
||||
<string name="dialog_message_save_image">इस छवि को सहेजने से अन्य ऐप्स इसे एक्सेस कर सकेंगे। \ n\ nक्या आप सुनिश्चित हैं कि आप सहेजना चाहते हैं?</string>
|
||||
<string name="save_image_success">छवि सहेज ली गई थी</string>
|
||||
<string name="save_image_error">छवि नहीं बचा सका</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">संपर्क जोड़ना</string>
|
||||
<string name="face_to_face">आपको उस व्यक्ति के साथ मिलना चाहिए जिसे आप संपर्क के रूप में जोड़ना चाहते हैं। \ N \ n यह किसी को भविष्य में आपके प्रतिरूपण या पढ़ने के बाद से किसी को भी रोकेगा।</string>
|
||||
|
||||
@@ -121,6 +121,8 @@
|
||||
<string name="dialog_title_delete_contact">Kapcsolat törlésének megerősítése</string>
|
||||
<string name="dialog_message_delete_contact">Biztosan eltávolítja ezt a kapcsolatot és minden vele történt üzenetváltását?</string>
|
||||
<string name="contact_deleted_toast">Kapcsolat törölve</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">Te</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Kapcsolat hozzáadása</string>
|
||||
<string name="face_to_face">Találkoznia kell a személlyel akit, hozzá szeretne adni a kapcsolatokhoz.\n\nEz megelőzi azt, hogy valaki megszemélyesítse Önt, vagy elolvassa későbbi üzeneteit.</string>
|
||||
|
||||
@@ -116,6 +116,9 @@
|
||||
<string name="date_no_private_messages">Nessun messaggio.</string>
|
||||
<string name="no_private_messages">Nessun messaggio da mostrare</string>
|
||||
<string name="message_hint">Scrivi un messaggio</string>
|
||||
<string name="image_caption_hint">Aggiungi una didascalia (facoltativo)</string>
|
||||
<string name="image_attach">Allega immagine</string>
|
||||
<string name="image_attach_error">Non è stato possibile allegare l\'immagine</string>
|
||||
<string name="set_contact_alias">Cambia il nome del contatto</string>
|
||||
<string name="set_contact_alias_hint">Nome contatto</string>
|
||||
<string name="set_alias_button">Cambia</string>
|
||||
@@ -123,6 +126,13 @@
|
||||
<string name="dialog_title_delete_contact">Conferma cancellazione contatto</string>
|
||||
<string name="dialog_message_delete_contact">Sei sicuro di voler rimuovere questo contatto e tutti i messaggi scambiati con esso?</string>
|
||||
<string name="contact_deleted_toast">Contatto cancellato</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">Tu</string>
|
||||
<string name="save_image">Salva immagine</string>
|
||||
<string name="dialog_title_save_image">Salvare immagine?</string>
|
||||
<string name="dialog_message_save_image">Salvare questa immagine permetterà ad altre app di accedere ad essa.\n\nSei sicuro di volerla salvare?</string>
|
||||
<string name="save_image_success">L\'immagine è stata salvata</string>
|
||||
<string name="save_image_error">Non è stato possibile salvare l\'immagine</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Aggiungi un Contatto</string>
|
||||
<string name="face_to_face">Devi incontrarti con la persona che vuoi aggiungere come contatto.\n\nQuesto evita che qualcuno ti impersoni o legga i tuoi messaggi in futuro.</string>
|
||||
|
||||
478
briar-android/src/main/res/values-mk/strings.xml
Normal file
478
briar-android/src/main/res/values-mk/strings.xml
Normal file
@@ -0,0 +1,478 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--Setup-->
|
||||
<string name="setup_title">Добредојде во Briar</string>
|
||||
<string name="setup_name_explanation">Вашето корисничко име ќе се појави до секоја содржина која ќе ја објавите. Истото не можете да го промените откога ќе ја креирате вашата сметка.</string>
|
||||
<string name="setup_next">Следно</string>
|
||||
<string name="setup_password_intro">Одбери лозинка</string>
|
||||
<string name="setup_password_explanation">Вашата Briar сметка е зачувана енкриптирана на вашиот уред, а не во облак. Ако ја заборавите вашата лозинка или го избришете Briar, не постои начин да ја повратите вашата сметка.\n\nОдберете долга лозинка која е тешка за погодување, на пример четири произволни зборови, или десет произволни букви, бројки или симболи.</string>
|
||||
<string name="setup_doze_title">Позадински поврзувања на Интернет</string>
|
||||
<string name="setup_doze_intro">За да примате пораки, Briar мора да биде поврзан на Интернет во позадина.</string>
|
||||
<string name="setup_doze_explanation">За да примате пораки, Briar мора да биде поврзан на Интернет во позадина. Исклучете ги оптимизациите за батеријата за Briar да може да остане поврзан на Интернет.</string>
|
||||
<string name="setup_doze_button">Дозволени поврзувања</string>
|
||||
<string name="choose_nickname">Одберете корисничко име</string>
|
||||
<string name="choose_password">Одберете лозинка</string>
|
||||
<string name="confirm_password">Потврдете ја лозинката</string>
|
||||
<string name="name_too_long">Името е предолго</string>
|
||||
<string name="password_too_weak">Лозинката е многу лесна</string>
|
||||
<string name="passwords_do_not_match">Лозинките не се исти</string>
|
||||
<string name="create_account_button">Создади сметка</string>
|
||||
<string name="more_info">Повеќе информации</string>
|
||||
<string name="don_t_ask_again">Не прашувај повторно</string>
|
||||
<string name="setup_huawei_text">Допрете го копчето подолу и осигурајте се дека Briar е заштитен во \"Protected Apps\" / мак. \"Заштитени апликации\" екранот.</string>
|
||||
<string name="setup_huawei_button">Заштити го Briar</string>
|
||||
<string name="setup_huawei_help">Ако Briar не е додаден на листата со заштитени апликации, ќе биде оневозможен да работи во позадина.</string>
|
||||
<string name="warning_dozed">%s беше оневозможен да работи во позадина</string>
|
||||
<!--Login-->
|
||||
<string name="enter_password">Лозинка</string>
|
||||
<string name="try_again">Погрешна лозинка</string>
|
||||
<string name="sign_in_button">Најави се</string>
|
||||
<string name="forgotten_password">Ја заборавив лозинката</string>
|
||||
<string name="dialog_title_lost_password">Изгубена лозинка</string>
|
||||
<string name="dialog_message_lost_password">Вашата Briar сметка е зачувана енкриптирана на вашиот уред, а не на облак, со оглед на тоа ние не можеме да ја рестартираме вашата лозинка. Дали сакате да ја избришете вашата сметка и да почнете од почеток?\n\nВнимание: Вашите идентитети, контакти и пораки ќе бидат трајно изгубени.</string>
|
||||
<string name="startup_failed_notification_title">Briar не може да се стартува</string>
|
||||
<string name="startup_failed_notification_text">Допрете за повеќе информации</string>
|
||||
<string name="startup_failed_activity_title">Стартувањето на Briar не успеа</string>
|
||||
<string name="startup_failed_db_error">Поради некоја причина, вашата Briar база на податоци е оштетена повеќе од степенот за можна поправка. Вашата сметка, вашите податоци и сите ваши контакти се изгубени. За жал, ќе треба да го пре-инсталирате Briar или да отворите нова сметка преку \'Ја заборавив лозинката\' во екранот каде истата се бара.</string>
|
||||
<string name="startup_failed_data_too_old_error">Вашата сметка е создадена со стара верзија на оваа апликација и не може да биде отворена со оваа верзија. Морате или да ја пре-инсталирате старата верзија или да отворите нова сметка преку \'Ја заборавив лозинката\' во екранот каде истата се бара.</string>
|
||||
<string name="startup_failed_data_too_new_error">Оваа верзија на апликацијата е многу стара. Ве молиме ажурирајте ја апликацијата до најновата верзија и обидете се повторно.</string>
|
||||
<string name="startup_failed_service_error">Briar не е во можност да го стартува потребниот приклучок / плугин. Пре-инсталирањето на Briar најчесто го решава овој проблем. Сепак, имајте во предвид дека во тој случај ќе ја изгубите вашата сметка и сите податоци поврзани со неа, бидејќи Briar не користи централизирани сервери за складирање на вашите податоци.</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="one">Ова е тест верзија на Briar. Вашата сметка ќе истече за %d ден и не може да биде обновена.</item>
|
||||
<item quantity="other">Ова е тест верзија на Briar. Вашата сметка ќе истече за %d денови и не може да биде обновена.</item>
|
||||
</plurals>
|
||||
<string name="expiry_update">Крајниот рок за тестирање беше продолжен. Вашата сметка ќе истече за %d денови.</string>
|
||||
<string name="expiry_date_reached">Софтверот истече.\nВи благодариме за тестирањето!</string>
|
||||
<string name="download_briar">За да продолжите да го користите Briar, ве молиме преземете ја верзија 1.0.</string>
|
||||
<string name="create_new_account">Ќе треба да создадете нова сметка, но можете да го користите истото корисничко име.</string>
|
||||
<string name="download_briar_button">Преземи го Briar 1.0</string>
|
||||
<string name="startup_open_database">Декриптирање на базата на податоци...</string>
|
||||
<string name="startup_migrate_database">Ажурирање на базата на податоци...</string>
|
||||
<string name="startup_compact_database">Пакување на базата на податоци</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">Отвори ја навигационата лента</string>
|
||||
<string name="nav_drawer_close_description">Затвори ја навигационата лента</string>
|
||||
<string name="contact_list_button">Контакти</string>
|
||||
<string name="groups_button">Приватни групи</string>
|
||||
<string name="forums_button">Форуми</string>
|
||||
<string name="blogs_button">Блогови</string>
|
||||
<!--This is part of the main menu. The app will be locked when this is tapped.-->
|
||||
<string name="lock_button">Заклучи ја апликацијата</string>
|
||||
<string name="settings_button">Поставки</string>
|
||||
<string name="sign_out_button">Одјави се</string>
|
||||
<!--Transports-->
|
||||
<string name="transport_tor">Интернет</string>
|
||||
<string name="transport_bt">Bluetooth</string>
|
||||
<string name="transport_lan">Wi-Fi</string>
|
||||
<!--Notifications-->
|
||||
<string name="reminder_notification_title">Одјави се од Briar</string>
|
||||
<string name="reminder_notification_text">Допрете за да се најавите.</string>
|
||||
<string name="reminder_notification_channel_title">Briar потсетување за најавување</string>
|
||||
<string name="reminder_notification_dismiss">Отфрли</string>
|
||||
<string name="ongoing_notification_title">Најавен во Briar</string>
|
||||
<string name="ongoing_notification_text">Допрете за да го отворите Briar</string>
|
||||
<plurals name="private_message_notification_text">
|
||||
<item quantity="one">Нова приватна порака.</item>
|
||||
<item quantity="other">%d нови приватни пораки.</item>
|
||||
</plurals>
|
||||
<plurals name="group_message_notification_text">
|
||||
<item quantity="one">Нова порака во група.</item>
|
||||
<item quantity="other">%d нови пораки во група.</item>
|
||||
</plurals>
|
||||
<plurals name="forum_post_notification_text">
|
||||
<item quantity="one">Нова објава на форум.</item>
|
||||
<item quantity="other">%d нови објави на форум.</item>
|
||||
</plurals>
|
||||
<plurals name="blog_post_notification_text">
|
||||
<item quantity="one">Нов пост на блог.</item>
|
||||
<item quantity="other">%d нови објави на блог.</item>
|
||||
</plurals>
|
||||
<!--Misc-->
|
||||
<string name="now">сега</string>
|
||||
<string name="show">Покажи</string>
|
||||
<string name="hide">Сокриј</string>
|
||||
<string name="ok">ОК</string>
|
||||
<string name="cancel">Исклучи</string>
|
||||
<string name="got_it">Разбирам</string>
|
||||
<string name="delete">Избриши</string>
|
||||
<string name="accept">Прифаќам</string>
|
||||
<string name="decline">Одбивам</string>
|
||||
<string name="options">Опции</string>
|
||||
<string name="online">Онлајн/Вклучен</string>
|
||||
<string name="offline">Офлајн/Исклучен</string>
|
||||
<string name="send">Испрати</string>
|
||||
<string name="allow">Дозволи</string>
|
||||
<string name="open">Отвори</string>
|
||||
<string name="no_data">Нема податоци</string>
|
||||
<string name="ellipsis">…</string>
|
||||
<string name="text_too_long">Внесениот текст е предолг</string>
|
||||
<string name="show_onboarding">Покажи го диjалогот за помош</string>
|
||||
<string name="fix">Поправи</string>
|
||||
<string name="help">Помош</string>
|
||||
<string name="sorry">Жал ни е</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">Нема контакти за прикажување</string>
|
||||
<string name="no_contacts_action">Допрете ја + иконата за да додадете контакт</string>
|
||||
<string name="date_no_private_messages">Нема пораки.</string>
|
||||
<string name="no_private_messages">Нема пораки за прикажување</string>
|
||||
<string name="message_hint">Напиши порака</string>
|
||||
<string name="image_caption_hint">Додај наслов (опционално)</string>
|
||||
<string name="image_attach">Прикачи слика</string>
|
||||
<string name="image_attach_error">Сликата не може да се прикачи</string>
|
||||
<string name="set_contact_alias">Промена на име на контакт</string>
|
||||
<string name="set_contact_alias_hint">Име на контакт</string>
|
||||
<string name="set_alias_button">Промени</string>
|
||||
<string name="delete_contact">Избриши контакт</string>
|
||||
<string name="dialog_title_delete_contact">Потврди бришење на контакт</string>
|
||||
<string name="dialog_message_delete_contact">Дали сте сигурни дека сакате да го избришете овој контакт и сите пораки поврзани со него?</string>
|
||||
<string name="contact_deleted_toast">Избришан контакт</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">Вие</string>
|
||||
<string name="save_image">Зачувај слика</string>
|
||||
<string name="dialog_title_save_image">Зачувај слика?</string>
|
||||
<string name="dialog_message_save_image">Зачувувањето на сликата ќе им дозволи на други апликации да пристапат до неа.\n\nДали сте сигурни дека сакате да ја зачувате?</string>
|
||||
<string name="save_image_success">Сликата беше зачувана</string>
|
||||
<string name="save_image_error">Сликата не може да се зачува</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Додади контакт</string>
|
||||
<string name="face_to_face">Морате да се сретнете со лицето кое сакате да го додадете за контакт.\n\nОва спречува во иднина било кој да се преставува како вас или да ги чита вашите пораки во иднина.</string>
|
||||
<string name="continue_button">Продолжи</string>
|
||||
<string name="try_again_button">Обиди се повторно</string>
|
||||
<string name="waiting_for_contact_to_scan">Почекај за контактот да скенира и да се поврзе\u2026</string>
|
||||
<string name="exchanging_contact_details">Разменување детали за контакт\u2026</string>
|
||||
<string name="contact_added_toast">Додадени контакти: %s</string>
|
||||
<string name="contact_already_exists">Контактот %s веќе постои</string>
|
||||
<string name="qr_code_invalid">QR кодот е невалиден</string>
|
||||
<string name="qr_code_too_old">QR кодот што го скениравте доаѓа од постара верзија на %s.\n\nВе молиме прашајте го вашиот контакт за надградба во последната верзија и обидете се повторно. </string>
|
||||
<string name="qr_code_too_new">QR кодот кој го скениравте доаѓа од понова верзија на %s.\n\nВе молиме надградете во последната верзија и потоа обидете се повторно.</string>
|
||||
<string name="camera_error">Грешка во камерата</string>
|
||||
<string name="connecting_to_device">Поврзување со уред\u2026</string>
|
||||
<string name="authenticating_with_device">Автентикација со уред\u2026</string>
|
||||
<string name="connection_error_title">Не може да се поврзе со твојот контакт</string>
|
||||
<string name="connection_error_explanation">Ве молиме проверете дека и двајцата сте поврзани на истата Wi-Fi мрежа.</string>
|
||||
<string name="connection_error_feedback">Ако овој проблем останува, ве молиме <a href="feedback">испратете повратна информација</a> за да ни помогнете да ја подобриме апликацијата.</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">Запознај ги твоите контакти</string>
|
||||
<string name="introduction_onboarding_text">Можете да ги запознаете вашите контакти меѓусебно, па тие нема да имаат потреба да се запознаваат лично за да се поврзат на Briar.</string>
|
||||
<string name="introduction_menu_item">Направи запознавање</string>
|
||||
<string name="introduction_activity_title">Избери контакт</string>
|
||||
<string name="introduction_not_possible">Штотуку имате едно запознавање во тек со овие контакти. Ве молиме дозволете прво овој процес да заврши. Ако вие или вашите контакти сте ретко вклучени/онлајн, ова може да потрае некое време.</string>
|
||||
<string name="introduction_message_title">Запознај ги контактите</string>
|
||||
<string name="introduction_message_hint">Додај порака (опционално)</string>
|
||||
<string name="introduction_button">Направи запознавање</string>
|
||||
<string name="introduction_sent">Вашето запознавање беше испратено.</string>
|
||||
<string name="introduction_error">Се појави грешка за време на запознавањето.</string>
|
||||
<string name="introduction_response_error">Грешка при одговор на запознавањето</string>
|
||||
<string name="introduction_request_sent">Побаравте да се запознаат %1$s и %2$s.</string>
|
||||
<string name="introduction_request_received">%1$s побара да ве запознае со %2$s. Дали сакате да го додадете %2$s на вашата контакт листа?</string>
|
||||
<string name="introduction_request_exists_received">%1$s побара да ве запознае со %2$s, но %2$s е веќе во вашата контакт листа. Додека %1$s можеби не го знае тоа, вие сеуште можете да одговорите:</string>
|
||||
<string name="introduction_request_answered_received">%1$s побара да ве запознае со %2$s.</string>
|
||||
<string name="introduction_response_accepted_sent">Вие го прифативте запознавањето со %1$s.</string>
|
||||
<string name="introduction_response_accepted_sent_info">Пред %1$s да биде додаден во вашите контакти, тие исто така треба да го прифатат запознавањето. Ова може да потрае малку време. </string>
|
||||
<string name="introduction_response_declined_sent">Го одбивте запознавањето со %1$s.</string>
|
||||
<string name="introduction_response_accepted_received">%1$s го прифативте запознавањето со %2$s.</string>
|
||||
<string name="introduction_response_declined_received">%1$s го одбивте запознавањето со %2$s.</string>
|
||||
<string name="introduction_response_declined_received_by_introducee">%1$s вели дека %2$s го одбил запознавањето.</string>
|
||||
<plurals name="introduction_notification_text">
|
||||
<item quantity="one">Нов контакт е додаден. </item>
|
||||
<item quantity="other">%d нови контакти се додадени.</item>
|
||||
</plurals>
|
||||
<!--Private Groups-->
|
||||
<string name="groups_list_empty">Нема групи за прикажување</string>
|
||||
<string name="groups_list_empty_action">Допрете ја + иконата за да создадете група, или прашајте ги вашите контакти да споделат групи со вас</string>
|
||||
<string name="groups_created_by">Создадено од %s</string>
|
||||
<plurals name="messages">
|
||||
<item quantity="one">%dпорака</item>
|
||||
<item quantity="other">%d пораки</item>
|
||||
</plurals>
|
||||
<string name="groups_group_is_empty">Оваа група е празна</string>
|
||||
<string name="groups_group_is_dissolved">Оваа група е распуштена</string>
|
||||
<string name="groups_remove">Отстрани</string>
|
||||
<string name="groups_create_group_title">Создади приватна група</string>
|
||||
<string name="groups_create_group_button">Создади група</string>
|
||||
<string name="groups_create_group_invitation_button">Испрати покана</string>
|
||||
<string name="groups_create_group_hint">Избери има за твојата приватна група</string>
|
||||
<string name="groups_invitation_sent">Групна покана беше испратена</string>
|
||||
<string name="groups_message_sent">Пораката е испратена</string>
|
||||
<string name="groups_member_list">Листа на членови</string>
|
||||
<string name="groups_invite_members">Покани членови</string>
|
||||
<string name="groups_member_created_you">Вие ја создадовте групата</string>
|
||||
<string name="groups_member_created">%s ја содаде групата</string>
|
||||
<string name="groups_member_joined_you">Вие се приклучивте на групата</string>
|
||||
<string name="groups_member_joined">%s се приклучи на групата</string>
|
||||
<string name="groups_leave">Напушти група</string>
|
||||
<string name="groups_leave_dialog_title">Потврди напуштање на група</string>
|
||||
<string name="groups_leave_dialog_message">Дали сте сигурни дека сакате да ја напуштите оваа група?</string>
|
||||
<string name="groups_dissolve">Распуштање на група</string>
|
||||
<string name="groups_dissolve_dialog_title">Потврди распуштање на група</string>
|
||||
<string name="groups_dissolve_dialog_message">Дали сте сигурни дека сакате да ја распуштите оваа група?\n\nСите нејзини членови нема да можат да го продолжат разговорот и нема да ги допримат последните пораки.</string>
|
||||
<string name="groups_dissolve_button">Распуштање</string>
|
||||
<string name="groups_dissolved_dialog_title">Групата беше распуштена</string>
|
||||
<string name="groups_dissolved_dialog_message">Создавачот на оваа група ја распушти.\n\nВие повеќе не можете да пишувате пораки во групата и можеби нема да ги примите сите објави кои биле напишани.</string>
|
||||
<!--Private Group Invitations-->
|
||||
<string name="groups_invitations_title">Групни покани</string>
|
||||
<string name="groups_invitations_invitation_sent">Вие го / ја поканивте %1$sда се приклучи на групата \"%2$s\".</string>
|
||||
<string name="groups_invitations_invitation_received">%1$sве покани да се приклучите на групата \"%2$s\".</string>
|
||||
<string name="groups_invitations_joined">Приклучена група</string>
|
||||
<string name="groups_invitations_declined">Групната покана е одбиена</string>
|
||||
<plurals name="groups_invitations_open">
|
||||
<item quantity="one">%dотвори групна покана</item>
|
||||
<item quantity="other">%d отвори групни покани</item>
|
||||
</plurals>
|
||||
<string name="groups_invitations_response_accepted_sent">Вие ја прифативте групната покана од %s.</string>
|
||||
<string name="groups_invitations_response_declined_sent">Вие ја одбивте групната покана од %s.</string>
|
||||
<string name="groups_invitations_response_accepted_received">%s ја прифати групната покана.</string>
|
||||
<string name="groups_invitations_response_declined_received">%s ја одби групната покана.</string>
|
||||
<string name="sharing_status_groups">Само содавачот може да покани нови членови во групата. Подолу се сите моментални членови на групата.</string>
|
||||
<!--Private Groups Revealing Contacts-->
|
||||
<string name="groups_reveal_contacts">Покажи контакти</string>
|
||||
<string name="groups_reveal_dialog_message">Можете да одберете да ги покажете контактите на сите моментални и идни членови на оваа група.\n\nОткривањето на контактите го прави вашето поврзување со групата побрзо и поверодостојно, затоа што ќе можете да комуницирате со откриените контакти дури и кога создавачот на групата е исклучен.</string>
|
||||
<string name="groups_reveal_visible">Контакт врската е видлива во групата</string>
|
||||
<string name="groups_reveal_visible_revealed_by_us">Контакт врската е видлива во групата (откриена од тебе)</string>
|
||||
<string name="groups_reveal_visible_revealed_by_contact">Контакт врската е видилва во групата (открена од %s)</string>
|
||||
<string name="groups_reveal_invisible">Контакт врската не е видлива во групата</string>
|
||||
<!--Forums-->
|
||||
<string name="no_forums">Нема форуми за прикажување</string>
|
||||
<string name="no_forums_action">Допрете ја + иконата за да создадете форум, или прашајте ги вашите контакти да споделат форуми со вас</string>
|
||||
<string name="create_forum_title">Создадете форум</string>
|
||||
<string name="choose_forum_hint">Одберете име за вашиот форум</string>
|
||||
<string name="create_forum_button">Создадете форум</string>
|
||||
<string name="forum_created_toast">Форумот е содаден</string>
|
||||
<string name="no_forum_posts">Нема објави за прикажување</string>
|
||||
<string name="no_posts">Нема објави</string>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">%dпост</item>
|
||||
<item quantity="other">%dобјави</item>
|
||||
</plurals>
|
||||
<string name="forum_new_entry_posted">Објавата на форумот е објавена</string>
|
||||
<string name="forum_new_message_hint">Нова објава</string>
|
||||
<string name="forum_message_reply_hint">Нов одговор</string>
|
||||
<string name="btn_reply">Одговор</string>
|
||||
<string name="forum_leave">Напушти форум</string>
|
||||
<string name="dialog_title_leave_forum">Потврди напуштање на форум</string>
|
||||
<string name="dialog_message_leave_forum">Дали сте сигурни дека сакате да го напуштите овој форум?\n\nБило кој од контактите со кои го споделивте овој форум може да престане да добива ажурирања.</string>
|
||||
<string name="dialog_button_leave">Напушти</string>
|
||||
<string name="forum_left_toast">Напушти форум</string>
|
||||
<!--Forum Sharing-->
|
||||
<string name="forum_share_button">Сподели форум</string>
|
||||
<string name="contacts_selected">Избрани контакти</string>
|
||||
<string name="activity_share_toolbar_header">Избери контакти</string>
|
||||
<string name="no_contacts_selector">Нема контакти за прикажување</string>
|
||||
<string name="no_contacts_selector_action">Ве молиме вратете се овде после додавање на контакт</string>
|
||||
<string name="forum_shared_snackbar">Форумот е споделен со избраните контакти</string>
|
||||
<string name="forum_share_message">Додај порака (опционално)</string>
|
||||
<string name="forum_share_error">Настана грешка при споделувањето на овој форум.</string>
|
||||
<string name="forum_invitation_received">%1$sго сподели овој форум \"%2$s\" со вас.</string>
|
||||
<string name="forum_invitation_sent">Вие го споделивте овој форум \"%1$s\" со %2$s.</string>
|
||||
<string name="forum_invitations_title">Поакни за форум</string>
|
||||
<string name="forum_invitation_exists">Вие штотуку ја прифативте поканата за овој форум.\n\nПрифаќањето на повеќе покани ќе го направи вашето поврзување со форумот побрзо и поверодостојно.</string>
|
||||
<string name="forum_joined_toast">Приклучен на форум</string>
|
||||
<string name="forum_declined_toast">Поканата е одбиена</string>
|
||||
<string name="shared_by_format">Споделено со %s</string>
|
||||
<string name="forum_invitation_already_sharing">Веќе споделува</string>
|
||||
<string name="forum_invitation_response_accepted_sent">Вие ја прифативте поканата за овој форум од %s.</string>
|
||||
<string name="forum_invitation_response_declined_sent">Вие ја одбивте поканата за овој форум од %s.</string>
|
||||
<string name="forum_invitation_response_accepted_received">%s ја прифати поканата за форумот.</string>
|
||||
<string name="forum_invitation_response_declined_received">%s ја одби поканата за форумот.</string>
|
||||
<string name="sharing_status">Споделување статус</string>
|
||||
<string name="sharing_status_forum">Секој член на форум може да го сподели со неговите контакти. Вие го споделувате овој форум со следниве контакти. Исто така може да има и други членови кој вие не можете да ги видите.</string>
|
||||
<string name="shared_with">Споделено со %1$d (%2$d вклучен/и)</string>
|
||||
<plurals name="forums_shared">
|
||||
<item quantity="one">%dфорум споделен од контакти</item>
|
||||
<item quantity="other">%d форуми споделени од контакти</item>
|
||||
</plurals>
|
||||
<string name="nobody">Никој</string>
|
||||
<!--Blogs-->
|
||||
<string name="blogs_other_blog_empty_state">Нема објави за прикажување</string>
|
||||
<string name="read_more">прочитај повеќе</string>
|
||||
<string name="blogs_write_blog_post">Напиши објава на Блог </string>
|
||||
<string name="blogs_write_blog_post_body_hint">Внесете објава на блог </string>
|
||||
<string name="blogs_publish_blog_post">Објави</string>
|
||||
<string name="blogs_blog_post_created">Создадена е објава на блог</string>
|
||||
<string name="blogs_blog_post_received">Примена е нова објава на блог </string>
|
||||
<string name="blogs_blog_post_scroll_to">Излистај до</string>
|
||||
<string name="blogs_feed_empty_state">Нема објави за прикажување</string>
|
||||
<string name="blogs_feed_empty_state_action">Објавите од вашите контакти и од блоговите на кои сте претплатени ќе се појавуваат овде\n\nДопрете ја пенкало иконата за да напишете објава</string>
|
||||
<string name="blogs_remove_blog">Отстрани блог</string>
|
||||
<string name="blogs_remove_blog_dialog_message">Дали сте сигурни дека сакате да го отстраните овој блог?\n\nОбјавите ќе бидат отстранети од вашиот уред но не и од уредите на другите луѓе.\n\nБило кои од контактите со кои сте го споделиле овој блог може да престанат да добиваат ажурирања.</string>
|
||||
<string name="blogs_remove_blog_ok">Отстрани</string>
|
||||
<string name="blogs_blog_removed">Блогот е отстранет</string>
|
||||
<string name="blogs_reblog_comment_hint">Додај коментар (опционално)</string>
|
||||
<string name="blogs_reblog_button">Реблог</string>
|
||||
<!--Blog Sharing-->
|
||||
<string name="blogs_sharing_share">Сподели блог</string>
|
||||
<string name="blogs_sharing_error">Настана грешка при споделувањето на овој блог.</string>
|
||||
<string name="blogs_sharing_button">Сподели блог</string>
|
||||
<string name="blogs_sharing_snackbar">Блогот е споделен со избраните контакти</string>
|
||||
<string name="blogs_sharing_response_accepted_sent">Вие ја прифативте поканата за блог од %s.</string>
|
||||
<string name="blogs_sharing_response_declined_sent">Вие ја одбивте поканата за блог од %s.</string>
|
||||
<string name="blogs_sharing_response_accepted_received">%s ја прифати поканата за блог.</string>
|
||||
<string name="blogs_sharing_response_declined_received">%s ја одби поканата за блог.</string>
|
||||
<string name="blogs_sharing_invitation_received">%1$s го сподели блогот \"%2$s\" со тебе.</string>
|
||||
<string name="blogs_sharing_invitation_sent">Вие го споделивте блогот \"%1$s\" со %2$s.</string>
|
||||
<string name="blogs_sharing_invitations_title">Покани за блог</string>
|
||||
<string name="blogs_sharing_joined_toast">Претплатен на блог</string>
|
||||
<string name="blogs_sharing_declined_toast">Поканата е одбиена</string>
|
||||
<string name="sharing_status_blog">Било кој што е претплатен на овој блог може да го сподели со неговите контакти. Вие го споделувате овој блог со следниве контакти. Исто така може да има и други претплатници кои ти не можеш да ги видиш.</string>
|
||||
<!--RSS Feeds-->
|
||||
<string name="blogs_rss_feeds_import">Увези RSS тековник</string>
|
||||
<string name="blogs_rss_feeds_import_button">Увези</string>
|
||||
<string name="blogs_rss_feeds_import_hint">Внеси URL на RSS тековник</string>
|
||||
<string name="blogs_rss_feeds_import_error">Жал ни е! Настана гршка при увезувањето на вашиот тековник.</string>
|
||||
<string name="blogs_rss_feeds_manage">Уредување на RSS тековници</string>
|
||||
<string name="blogs_rss_feeds_manage_imported">Увезени:</string>
|
||||
<string name="blogs_rss_feeds_manage_author">Автор:</string>
|
||||
<string name="blogs_rss_feeds_manage_updated">Последен пат ажуриран:</string>
|
||||
<string name="blogs_rss_remove_feed">Отстрани тековник</string>
|
||||
<string name="blogs_rss_remove_feed_dialog_message">Дали сте сигурни дека сакате да го отстраните овој тековник?\n\nОбјавите ќе бидат отстранети од вашиот уред но не и од уредите на другите луѓе.\n\nБило кои од контактите со кои сте го споделиле овој тековник може да престанат да добиваат ажурирања.</string>
|
||||
<string name="blogs_rss_remove_feed_ok">Отстрани</string>
|
||||
<string name="blogs_rss_feeds_manage_delete_error">Тековникот не може да биде избришан!</string>
|
||||
<string name="blogs_rss_feeds_manage_empty_state">Нема RSS тековници за прикажување\n\nДопрете ја + иконата за да увезете тековник</string>
|
||||
<string name="blogs_rss_feeds_manage_error">Настана проблем при вчитувањето на вашите тековници. Ве молиме обидете се повторно подоцна.</string>
|
||||
<!--Settings Display-->
|
||||
<string name="pref_language_title">Јазик & регион</string>
|
||||
<string name="pref_language_changed">Оваа поставка ќе биде ефективна после рестартирањето на Briar. Ве молиме одјавете се и рестартирајте го Briar.</string>
|
||||
<string name="pref_language_default">Систем стандардно поставен</string>
|
||||
<string name="display_settings_title">Екран</string>
|
||||
<string name="pref_theme_title">Тема</string>
|
||||
<string name="pref_theme_light">Светло</string>
|
||||
<string name="pref_theme_dark">Темно</string>
|
||||
<string name="pref_theme_auto">Автоматски (Дневно)</string>
|
||||
<string name="pref_theme_system">Систем стандардно поставен</string>
|
||||
<!--Settings Network-->
|
||||
<string name="network_settings_title">Мрежи</string>
|
||||
<string name="bluetooth_setting">Поврзи се преку Bluetooth</string>
|
||||
<string name="bluetooth_setting_enabled">Секогаш кога контактите се во близина</string>
|
||||
<string name="bluetooth_setting_disabled">Само кога се додаваат контакти</string>
|
||||
<string name="tor_network_setting">Поврзи се преку Интернет (Tor)</string>
|
||||
<string name="tor_network_setting_automatic">Автоматски базирано на локација</string>
|
||||
<string name="tor_network_setting_without_bridges">Користи Tor без мостови</string>
|
||||
<string name="tor_network_setting_with_bridges">Користи Tor со мостови</string>
|
||||
<string name="tor_network_setting_never">Не се поврзувај</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">Автоматски: %1$s (во %2$s)</string>
|
||||
<string name="tor_mobile_data_title">Користи мобилни податоци</string>
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">Безбедност</string>
|
||||
<string name="pref_lock_title">Апликацијата е заклучена</string>
|
||||
<string name="pref_lock_summary">Користете го екранот за заклучување на уредот за да го заштитите Briar додека сте најавени</string>
|
||||
<string name="pref_lock_disabled_summary">За да ја користиш оваа карарактеристика, постави екран за заклучување на твојот уред</string>
|
||||
<string name="pref_lock_timeout_title">Неактивноста на заклучената апликација е истечена</string>
|
||||
<!--The %s placeholder is replaced with the following time spans, e.g. 5 Minutes, 1 Hour-->
|
||||
<string name="pref_lock_timeout_summary">Кога не го користите Briar, автоматски се заклучува после %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 минута</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 минути</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 минути</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 минути</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 час</string>
|
||||
<string name="pref_lock_timeout_never">Никогаш</string>
|
||||
<string name="pref_lock_timeout_never_summary">Никогаш не го заклучувај Briar автоматски</string>
|
||||
<string name="change_password">Промени лозинка</string>
|
||||
<string name="current_password">Сегашна лозинка</string>
|
||||
<string name="choose_new_password">Нова лозинка</string>
|
||||
<string name="confirm_new_password">Потврди нова лозинка</string>
|
||||
<string name="password_changed">Лозинката е променета.</string>
|
||||
<string name="panic_setting">Поставување на копчето паника</string>
|
||||
<string name="panic_setting_title">Паника копче</string>
|
||||
<string name="panic_setting_hint">Конфигурирај како Briar ќе реагира кога ќе го користиш паника копчето на апликацијата</string>
|
||||
<string name="panic_app_setting_title">Паника копче на апликација</string>
|
||||
<string name="unknown_app">непозната апликација</string>
|
||||
<string name="panic_app_setting_summary">Ниту една апликација не беше поставена</string>
|
||||
<string name="panic_app_setting_none">Ништо</string>
|
||||
<string name="dialog_title_connect_panic_app">Потврди паника апликација</string>
|
||||
<string name="dialog_message_connect_panic_app">Дали сте сигурни дека сакате да дозволите %1$s да поттикне деструктивни акции на паника копчето?</string>
|
||||
<string name="panic_setting_destructive_action">Деструктивни акции</string>
|
||||
<string name="panic_setting_signout_title">Одјави се</string>
|
||||
<string name="panic_setting_signout_summary">Одјави се од Briar ако копчето паника е притиснато</string>
|
||||
<string name="purge_setting_title">Избриши сметка</string>
|
||||
<string name="purge_setting_summary">Избриши ја твојата Briar сметка ако е притиснато копчето паника. Внимание: Ова трајно ќе ги избрише твоите идентитети, контакти и пораки</string>
|
||||
<string name="uninstall_setting_title">Отстрани го Briar</string>
|
||||
<string name="uninstall_setting_summary">Ова бара рачно потврдување во случај на паника</string>
|
||||
<!--Settings Notifications-->
|
||||
<string name="notification_settings_title">Известувања</string>
|
||||
<string name="notify_sign_in_title">Потсети ме да се најавам</string>
|
||||
<string name="notify_sign_in_summary">Покажи потсетник кога телефонот се вклучува или кога апликацијата е ажурирана</string>
|
||||
<string name="notify_private_messages_setting_title">Приватни пораки</string>
|
||||
<string name="notify_private_messages_setting_summary">Покажи предупредувања за приватни пораки</string>
|
||||
<string name="notify_private_messages_setting_summary_26">Конфигурирај предупредувања за приватни пораки</string>
|
||||
<string name="notify_group_messages_setting_title">Групни пораки</string>
|
||||
<string name="notify_group_messages_setting_summary">Покажи предупредувања за групни пораки</string>
|
||||
<string name="notify_group_messages_setting_summary_26">Конфигурирај предупредувања за групни пораки</string>
|
||||
<string name="notify_forum_posts_setting_title">Форум објави</string>
|
||||
<string name="notify_forum_posts_setting_summary">Покажи предупредувања за оваа форум објава</string>
|
||||
<string name="notify_forum_posts_setting_summary_26">Конфигурирај ги предупредувањата за објавите на форумот</string>
|
||||
<string name="notify_blog_posts_setting_title">Објави на Блог</string>
|
||||
<string name="notify_blog_posts_setting_summary">Покажи ги предупредувањата за објавите на блог</string>
|
||||
<string name="notify_blog_posts_setting_summary_26">Конфигурирај ги предупредувањата за објавите на блог</string>
|
||||
<string name="notify_vibration_setting">Вибрирање</string>
|
||||
<string name="notify_sound_setting">Звук</string>
|
||||
<string name="notify_sound_setting_default">Стандарден рингтон</string>
|
||||
<string name="notify_sound_setting_disabled">Ништо</string>
|
||||
<string name="choose_ringtone_title">Избери рингтон</string>
|
||||
<string name="cannot_load_ringtone">Не може да вчита рингтон</string>
|
||||
<!--Settings Feedback-->
|
||||
<string name="feedback_settings_title">Повратни информации</string>
|
||||
<string name="send_feedback">Испрати повратни информации</string>
|
||||
<!--Link Warning-->
|
||||
<string name="link_warning_title">Линк предупредување</string>
|
||||
<string name="link_warning_intro">Ќе го отворите следниот линк во надворешна апликација.</string>
|
||||
<string name="link_warning_text">Ова може да биде искористено за да ве идентификуваат. Размислте дали и верувате на личноста која ви го испратила овој линк и дали да го отворите со Orfox.</string>
|
||||
<string name="link_warning_open_link">Отвори линк</string>
|
||||
<!--Crash Reporter-->
|
||||
<string name="crash_report_title">Briar извештај за срушување</string>
|
||||
<string name="briar_crashed">Жал ни е, Briar се сруши.</string>
|
||||
<string name="not_your_fault">Ова не е ваша гршка.</string>
|
||||
<string name="please_send_report">Ве молиме помогнете ни да изградиме подобар Briar со испраќање на извештај за срушување. </string>
|
||||
<string name="report_is_encrypted">Ви ветуваме дека извештајот е енкриптиран и безбедно испратен.</string>
|
||||
<string name="feedback_title">Повратни информации</string>
|
||||
<string name="describe_crash">Опиши што се случи (опционално)</string>
|
||||
<string name="enter_feedback">Внеси ги твоите повратни информации</string>
|
||||
<string name="optional_contact_email">Вашата е-пошта (опционално)</string>
|
||||
<string name="include_debug_report_crash">Вклучи анонимни податоци за ова срушување</string>
|
||||
<string name="include_debug_report_feedback">Вкучи анонимни податоци за овој уред</string>
|
||||
<string name="could_not_load_report_data">Не можат да се вчитаат податоците од извештајот.</string>
|
||||
<string name="send_report">Испрати извештај</string>
|
||||
<string name="close">Затвори</string>
|
||||
<string name="dev_report_saved">Извештајот е зачуван. Ќе биде испратен следниот пат кога ќе се вклучите на Briar.</string>
|
||||
<!--Sign Out-->
|
||||
<string name="progress_title_logout">Одјавување од Briar</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">Детектиран е преклопувачки екран</string>
|
||||
<string name="screen_filter_body">Друга апликација е уклучена врз Briar. За да ја зачува вашата безбедност, Briar нема да одговори на допирите кога има друга апликација уклучена врз него.\n\nСледните апликации можат да бидат вклучени врз:\n\n%1$s</string>
|
||||
<string name="screen_filter_allow">Дозволете им на овие апликации да бидат уклучени врз</string>
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">Дозвола за камера</string>
|
||||
<string name="permission_camera_request_body">За да скенирате QR код, на Briar му треба пристап до камерата.</string>
|
||||
<string name="permission_location_title">Дозвола за локација</string>
|
||||
<string name="permission_location_request_body">За да открие Bluetooth уреди, на Briar му е потребна дозвола за пристап до вашата локација.\n\nBriar не ја зачувува вашата локација или не ја споделува со никого.</string>
|
||||
<string name="permission_camera_location_title">Камера и локација</string>
|
||||
<string name="permission_camera_location_request_body">За да го скенира QR кодот, на Briar му е потребен пристап до камерата.\n\nЗа да открие Bluetooth уреди, на Briar му е потребна дозвола за вашата локација.\n\nBriar не ја зачувува вашата локација или не ја споделува со никого.</string>
|
||||
<string name="permission_camera_denied_body">Го одбивте пристапот до камерата, но за додавање на контакти потребно е користење на камерата.\n\nВе молиме размислете за давање дозвола.</string>
|
||||
<string name="qr_code">QR код</string>
|
||||
<string name="show_qr_code_fullscreen">Покажи го QR кодот на цел екран</string>
|
||||
<!--App Locking-->
|
||||
<string name="lock_unlock">Отклучи го Briar</string>
|
||||
<string name="lock_unlock_verbose">Внесете го ПИН-от, шемата или лозинката на твојот уред за да го отклучите Briar. </string>
|
||||
<string name="lock_unlock_fingerprint_description">Допрете го сензорот за отпечаток со прстот со кој сте се регистрирале за да продложите</string>
|
||||
<string name="lock_unlock_password">Користете лозинка</string>
|
||||
<string name="lock_is_locked">Briar е заклучен</string>
|
||||
<string name="lock_tap_to_unlock">Допрете за отклучување</string>
|
||||
<!--Screenshots-->
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_alice">Жарко</string>
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_bob">Лилјана</string>
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_carol">Ана</string>
|
||||
<!--This is a message to be used in screenshots. Please use the same translation for Bob!-->
|
||||
<string name="screenshot_message_1">Здраво Лилјана!</string>
|
||||
<!--This is a message to be used in screenshots. Please use the same translation for Alice!-->
|
||||
<string name="screenshot_message_2">Здраво Жарко! Ти благодарам што ми кажа за Briar!</string>
|
||||
<!--This is a message to be used in screenshots.-->
|
||||
<string name="screenshot_message_3">Нема проблем, се надевам ти се допаѓа 😀</string>
|
||||
</resources>
|
||||
@@ -23,7 +23,6 @@
|
||||
<color name="briar_button_text_disabled">#23cccccc</color>
|
||||
|
||||
<color name="thread_indicator">@color/briar_blue</color>
|
||||
<color name="thread_item_background">@color/window_background</color>
|
||||
<color name="thread_item_highlight">@color/briar_black</color>
|
||||
|
||||
<color name="divider">@color/briar_black</color>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user