Compare commits
67 Commits
1473-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 | ||
|
|
a994966095 | ||
|
|
2bea581654 | ||
|
|
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"
|
||||
|
||||
|
Before Width: | Height: | Size: 43 B After Width: | Height: | Size: 43 B |
BIN
briar-android/src/androidTestOfficial/assets/animated2.gif
Normal file
|
After Width: | Height: | Size: 317 B |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 43 B After Width: | Height: | Size: 43 B |
|
Before Width: | Height: | Size: 1.1 KiB 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");
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.briarproject.briar.android.contact.ContactModule;
|
||||
import org.briarproject.briar.android.conversation.AliasDialogFragment;
|
||||
import org.briarproject.briar.android.conversation.ConversationActivity;
|
||||
import org.briarproject.briar.android.conversation.ImageActivity;
|
||||
import org.briarproject.briar.android.conversation.ImageFragment;
|
||||
import org.briarproject.briar.android.forum.CreateForumActivity;
|
||||
import org.briarproject.briar.android.forum.ForumActivity;
|
||||
import org.briarproject.briar.android.forum.ForumListFragment;
|
||||
@@ -30,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;
|
||||
@@ -48,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;
|
||||
@@ -69,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;
|
||||
@@ -182,8 +179,6 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(CreateGroupFragment fragment);
|
||||
|
||||
void inject(CreateGroupMessageFragment fragment);
|
||||
|
||||
void inject(GroupListFragment fragment);
|
||||
|
||||
void inject(GroupInviteFragment fragment);
|
||||
@@ -194,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);
|
||||
@@ -218,4 +207,6 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(AliasDialogFragment aliasDialogFragment);
|
||||
|
||||
void inject(ImageFragment imageFragment);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,10 +2,13 @@ package org.briarproject.briar.android.conversation;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
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
|
||||
@@ -17,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>() {
|
||||
@@ -31,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) {
|
||||
@@ -42,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) {
|
||||
@@ -55,6 +62,7 @@ public class AttachmentItem implements Parcelable {
|
||||
thumbnailWidth = in.readInt();
|
||||
thumbnailHeight = in.readInt();
|
||||
hasError = in.readByte() != 0;
|
||||
instanceId = in.readLong();
|
||||
}
|
||||
|
||||
public MessageId getMessageId() {
|
||||
@@ -89,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
|
||||
@@ -109,6 +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 &&
|
||||
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,10 +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.ATTACHMENT;
|
||||
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.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;
|
||||
@@ -142,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;
|
||||
@@ -152,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
|
||||
@@ -189,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);
|
||||
@@ -216,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);
|
||||
@@ -240,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));
|
||||
@@ -260,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
|
||||
@@ -274,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) {
|
||||
@@ -299,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();
|
||||
@@ -308,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);
|
||||
}
|
||||
@@ -380,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);
|
||||
@@ -418,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();
|
||||
@@ -469,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(() -> {
|
||||
@@ -500,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();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -552,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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -651,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
|
||||
@@ -812,19 +878,18 @@ public class ConversationActivity extends BriarActivity
|
||||
} else {
|
||||
name = getString(R.string.you);
|
||||
}
|
||||
ArrayList<AttachmentItem> attachments =
|
||||
new ArrayList<>(messageItem.getAttachments());
|
||||
Intent i = new Intent(this, ImageActivity.class);
|
||||
i.putExtra(ATTACHMENT, item);
|
||||
i.putParcelableArrayListExtra(ATTACHMENTS, attachments);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -4,15 +4,18 @@ import android.arch.lifecycle.ViewModelProvider;
|
||||
import android.arch.lifecycle.ViewModelProviders;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.design.widget.AppBarLayout;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.graphics.drawable.DrawableCompat;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.AlertDialog.Builder;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.transition.Fade;
|
||||
@@ -20,23 +23,20 @@ import android.transition.Transition;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||
import android.view.Window;
|
||||
import android.widget.TextView;
|
||||
|
||||
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 com.github.chrisbanes.photoview.PhotoView;
|
||||
|
||||
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;
|
||||
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||
import org.briarproject.briar.android.view.PullDownLayout;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -53,16 +53,17 @@ import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
|
||||
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
|
||||
import static android.widget.ImageView.ScaleType.FIT_START;
|
||||
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
|
||||
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 {
|
||||
implements PullDownLayout.Callback, OnGlobalLayoutListener {
|
||||
|
||||
final static String ATTACHMENT = "attachment";
|
||||
final static String ATTACHMENTS = "attachments";
|
||||
final static String ATTACHMENT_POSITION = "position";
|
||||
final static String NAME = "name";
|
||||
final static String DATE = "date";
|
||||
|
||||
@@ -72,8 +73,8 @@ public class ImageActivity extends BriarActivity
|
||||
private ImageViewModel viewModel;
|
||||
private PullDownLayout layout;
|
||||
private AppBarLayout appBarLayout;
|
||||
private PhotoView photoView;
|
||||
private AttachmentItem attachment;
|
||||
private ViewPager viewPager;
|
||||
private List<AttachmentItem> attachments;
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
@@ -85,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();
|
||||
@@ -100,8 +101,8 @@ 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);
|
||||
|
||||
// Status Bar
|
||||
if (SDK_INT >= 21) {
|
||||
@@ -118,59 +119,29 @@ public class ImageActivity extends BriarActivity
|
||||
TextView dateView = toolbar.findViewById(R.id.dateView);
|
||||
|
||||
// Intent Extras
|
||||
attachment = getIntent().getParcelableExtra(ATTACHMENT);
|
||||
String name = getIntent().getStringExtra(NAME);
|
||||
long time = getIntent().getLongExtra(DATE, 0);
|
||||
Intent i = getIntent();
|
||||
attachments = i.getParcelableArrayListExtra(ATTACHMENTS);
|
||||
int position = i.getIntExtra(ATTACHMENT_POSITION, -1);
|
||||
if (position == -1) throw new IllegalStateException();
|
||||
String name = i.getStringExtra(NAME);
|
||||
long time = i.getLongExtra(DATE, 0);
|
||||
String date = formatDateAbsolute(this, time);
|
||||
contactName.setText(name);
|
||||
dateView.setText(date);
|
||||
|
||||
// Image View
|
||||
photoView = findViewById(R.id.photoView);
|
||||
// Set up image ViewPager
|
||||
viewPager = findViewById(R.id.viewPager);
|
||||
ImagePagerAdapter pagerAdapter =
|
||||
new ImagePagerAdapter(getSupportFragmentManager());
|
||||
viewPager.setAdapter(pagerAdapter);
|
||||
viewPager.setCurrentItem(position);
|
||||
|
||||
if (SDK_INT >= 16) {
|
||||
photoView.setOnClickListener(view -> toggleSystemUi());
|
||||
viewModel.getOnImageClicked().observe(this, this::onImageClicked);
|
||||
window.getDecorView().setSystemUiVisibility(
|
||||
SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
||||
SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
|
||||
}
|
||||
|
||||
// Request Listener
|
||||
RequestListener<Drawable> listener = new RequestListener<Drawable>() {
|
||||
@Override
|
||||
public boolean onLoadFailed(@Nullable GlideException e,
|
||||
Object model, Target<Drawable> target,
|
||||
boolean isFirstResource) {
|
||||
supportStartPostponedEnterTransition();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onResourceReady(Drawable resource, Object model,
|
||||
Target<Drawable> target, DataSource dataSource,
|
||||
boolean isFirstResource) {
|
||||
if (SDK_INT >= 21 && !(resource instanceof Animatable)) {
|
||||
// set transition name only when not animatable,
|
||||
// because the animation won't start otherwise
|
||||
photoView.setTransitionName(
|
||||
attachment.getTransitionName());
|
||||
}
|
||||
// Move image to the top if overlapping toolbar
|
||||
if (isOverlappingToolbar(resource)) {
|
||||
photoView.setScaleType(FIT_START);
|
||||
}
|
||||
supportStartPostponedEnterTransition();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Load Image
|
||||
GlideApp.with(this)
|
||||
.load(attachment)
|
||||
.diskCacheStrategy(NONE)
|
||||
.error(R.drawable.ic_image_broken)
|
||||
.dontTransform()
|
||||
.addListener(listener)
|
||||
.into(photoView);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -194,10 +165,24 @@ public class ImageActivity extends BriarActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int request, int result, Intent data) {
|
||||
public void onGlobalLayout() {
|
||||
viewModel.setToolbarPosition(
|
||||
appBarLayout.getTop(), appBarLayout.getBottom()
|
||||
);
|
||||
if (SDK_INT >= 16) {
|
||||
layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
} else {
|
||||
layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int request, int result,
|
||||
@Nullable Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
if (request == REQUEST_SAVE_ATTACHMENT && result == RESULT_OK) {
|
||||
viewModel.saveImage(attachment, data.getData());
|
||||
if (request == REQUEST_SAVE_ATTACHMENT && result == RESULT_OK &&
|
||||
data != null) {
|
||||
viewModel.saveImage(getVisibleAttachment(), data.getData());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +195,6 @@ public class ImageActivity extends BriarActivity
|
||||
|
||||
@Override
|
||||
public void onPull(float progress) {
|
||||
layout.getBackground().setAlpha(Math.round((1 - progress) * 255));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -225,6 +209,14 @@ public class ImageActivity extends BriarActivity
|
||||
supportFinishAfterTransition();
|
||||
}
|
||||
|
||||
@RequiresApi(api = 16)
|
||||
private void onImageClicked(@Nullable Boolean clicked) {
|
||||
if (clicked != null && clicked) {
|
||||
toggleSystemUi();
|
||||
viewModel.onOnImageClickSeen();
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = 16)
|
||||
private void toggleSystemUi() {
|
||||
View decorView = getWindow().getDecorView();
|
||||
@@ -259,29 +251,13 @@ public class ImageActivity extends BriarActivity
|
||||
.start();
|
||||
}
|
||||
|
||||
private boolean isOverlappingToolbar(Drawable drawable) {
|
||||
int width = drawable.getIntrinsicWidth();
|
||||
int height = drawable.getIntrinsicHeight();
|
||||
float widthPercentage = photoView.getWidth() / (float) width;
|
||||
float heightPercentage = photoView.getHeight() / (float) height;
|
||||
float scaleFactor = Math.min(widthPercentage, heightPercentage);
|
||||
int realWidth = (int) (width * scaleFactor);
|
||||
int realHeight = (int) (height * scaleFactor);
|
||||
// return if photo doesn't use the full width,
|
||||
// because it will be moved to the right otherwise
|
||||
if (realWidth < photoView.getWidth()) return false;
|
||||
int drawableTop = (photoView.getHeight() - realHeight) / 2;
|
||||
return drawableTop < appBarLayout.getBottom() &&
|
||||
drawableTop != appBarLayout.getTop();
|
||||
}
|
||||
|
||||
private void showSaveImageDialog() {
|
||||
OnClickListener okListener = (dialog, which) -> {
|
||||
if (SDK_INT >= 19) {
|
||||
Intent intent = getCreationIntent();
|
||||
startActivityForResult(intent, REQUEST_SAVE_ATTACHMENT);
|
||||
} else {
|
||||
viewModel.saveImage(attachment);
|
||||
viewModel.saveImage(getVisibleAttachment());
|
||||
}
|
||||
};
|
||||
Builder builder = new Builder(this, R.style.BriarDialogTheme);
|
||||
@@ -303,7 +279,7 @@ public class ImageActivity extends BriarActivity
|
||||
String fileName = sdf.format(new Date());
|
||||
Intent intent = new Intent(ACTION_CREATE_DOCUMENT);
|
||||
intent.addCategory(CATEGORY_OPENABLE);
|
||||
intent.setType(attachment.getMimeType());
|
||||
intent.setType(getVisibleAttachment().getMimeType());
|
||||
intent.putExtra(EXTRA_TITLE, fileName);
|
||||
return intent;
|
||||
}
|
||||
@@ -320,4 +296,31 @@ public class ImageActivity extends BriarActivity
|
||||
viewModel.onSaveStateSeen();
|
||||
}
|
||||
|
||||
AttachmentItem getVisibleAttachment() {
|
||||
return attachments.get(viewPager.getCurrentItem());
|
||||
}
|
||||
|
||||
private class ImagePagerAdapter extends FragmentStatePagerAdapter {
|
||||
|
||||
private boolean isFirst = true;
|
||||
|
||||
private ImagePagerAdapter(FragmentManager fm) {
|
||||
super(fm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
Fragment f = ImageFragment
|
||||
.newInstance(attachments.get(position), isFirst);
|
||||
isFirst = false;
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return attachments.size();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package org.briarproject.briar.android.conversation;
|
||||
|
||||
import android.arch.lifecycle.ViewModelProvider;
|
||||
import android.arch.lifecycle.ViewModelProviders;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
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 com.github.chrisbanes.photoview.PhotoView;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.widget.ImageView.ScaleType.FIT_START;
|
||||
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENT_POSITION;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@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, 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
BaseActivity a = (BaseActivity) requireNonNull(getActivity());
|
||||
a.getActivityComponent().inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = requireNonNull(getArguments());
|
||||
attachment = requireNonNull(args.getParcelable(ATTACHMENT_POSITION));
|
||||
isFirst = args.getBoolean(IS_FIRST);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.fragment_image, container,
|
||||
false);
|
||||
|
||||
viewModel = ViewModelProviders.of(requireNonNull(getActivity()),
|
||||
viewModelFactory).get(ImageViewModel.class);
|
||||
|
||||
photoView = v.findViewById(R.id.photoView);
|
||||
photoView.setScaleLevels(1, 2, 4);
|
||||
photoView.setOnClickListener(view -> viewModel.clickImage());
|
||||
|
||||
// Request Listener
|
||||
RequestListener<Drawable> listener = new RequestListener<Drawable>() {
|
||||
@Override
|
||||
public boolean onLoadFailed(@Nullable GlideException e,
|
||||
Object model, Target<Drawable> target,
|
||||
boolean isFirstResource) {
|
||||
if (getActivity() != null && isFirst)
|
||||
getActivity().supportStartPostponedEnterTransition();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onResourceReady(Drawable resource, Object model,
|
||||
Target<Drawable> target, DataSource dataSource,
|
||||
boolean isFirstResource) {
|
||||
if (SDK_INT >= 21 && !(resource instanceof Animatable)) {
|
||||
// set transition name only when not animatable,
|
||||
// because the animation won't start otherwise
|
||||
photoView.setTransitionName(
|
||||
attachment.getTransitionName());
|
||||
}
|
||||
// Move image to the top if overlapping toolbar
|
||||
if (viewModel.isOverlappingToolbar(photoView, resource)) {
|
||||
photoView.setScaleType(FIT_START);
|
||||
}
|
||||
if (getActivity() != null && isFirst) {
|
||||
getActivity().supportStartPostponedEnterTransition();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Load Image
|
||||
GlideApp.with(this)
|
||||
.load(attachment)
|
||||
// TODO allow if size < maxTextureSize ?
|
||||
// .override(SIZE_ORIGINAL)
|
||||
.diskCacheStrategy(NONE)
|
||||
.error(R.drawable.ic_image_broken)
|
||||
.addListener(listener)
|
||||
.into(photoView);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,11 @@ import android.app.Application;
|
||||
import android.arch.lifecycle.AndroidViewModel;
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.lifecycle.MutableLiveData;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.view.View;
|
||||
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
@@ -48,7 +50,13 @@ 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 int toolbarTop, toolbarBottom;
|
||||
|
||||
@Inject
|
||||
ImageViewModel(Application application,
|
||||
@@ -61,9 +69,49 @@ public class ImageViewModel extends AndroidViewModel {
|
||||
this.ioExecutor = ioExecutor;
|
||||
}
|
||||
|
||||
void clickImage() {
|
||||
imageClicked.setValue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 the image was clicked,
|
||||
* false if it wasn't.
|
||||
*
|
||||
* Call {@link #onOnImageClickSeen()} after consuming an update.
|
||||
*/
|
||||
LiveData<Boolean> getOnImageClicked() {
|
||||
return imageClicked;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void onOnImageClickSeen() {
|
||||
imageClicked.setValue(false);
|
||||
}
|
||||
|
||||
void setToolbarPosition(int top, int bottom) {
|
||||
toolbarTop = top;
|
||||
toolbarBottom = bottom;
|
||||
}
|
||||
|
||||
boolean isOverlappingToolbar(View screenView, Drawable drawable) {
|
||||
int width = drawable.getIntrinsicWidth();
|
||||
int height = drawable.getIntrinsicHeight();
|
||||
float widthPercentage = screenView.getWidth() / (float) width;
|
||||
float heightPercentage = screenView.getHeight() / (float) height;
|
||||
float scaleFactor = Math.min(widthPercentage, heightPercentage);
|
||||
int realWidth = (int) (width * scaleFactor);
|
||||
int realHeight = (int) (height * scaleFactor);
|
||||
// return if image doesn't use the full width,
|
||||
// because it will be moved to the right otherwise
|
||||
if (realWidth < screenView.getWidth()) return false;
|
||||
int drawableTop = (screenView.getHeight() - realHeight) / 2;
|
||||
return drawableTop < toolbarBottom && drawableTop != toolbarTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -82,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
@@ -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"
|
||||
|
||||
@@ -8,12 +8,11 @@
|
||||
android:background="@color/briar_black"
|
||||
tools:context=".android.conversation.ImageActivity">
|
||||
|
||||
<com.github.chrisbanes.photoview.PhotoView
|
||||
android:id="@+id/photoView"
|
||||
<android.support.v4.view.ViewPager
|
||||
android:id="@+id/viewPager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:srcCompat="@tools:sample/backgrounds/scenic"/>
|
||||
tools:background="@color/briar_green_light"/>
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
|
||||
@@ -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"
|
||||
|
||||
9
briar-android/src/main/res/layout/fragment_image.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.github.chrisbanes.photoview.PhotoView
|
||||
android:id="@+id/photoView"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:srcCompat="@tools:sample/backgrounds/scenic"/>
|
||||
@@ -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>
|
||||
|
||||