Compare commits
23 Commits
1233-remot
...
1242-displ
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6e077ff40 | ||
|
|
4e5e354af2 | ||
|
|
9ea3623c85 | ||
|
|
84089f5e16 | ||
|
|
a994966095 | ||
|
|
2bea581654 | ||
|
|
87377666aa | ||
|
|
9d07b2e141 | ||
|
|
5c312b49e2 | ||
|
|
f56efe45cd | ||
|
|
2332a58681 | ||
|
|
8c6dfaa196 | ||
|
|
3cfb04b60d | ||
|
|
e85fbfb952 | ||
|
|
80ee35d926 | ||
|
|
4796902b9c | ||
|
|
149e67c0f7 | ||
|
|
1d5214117f | ||
|
|
b8f248ca9c | ||
|
|
dfb71a03a5 | ||
|
|
961fdc8e72 | ||
|
|
c3d44663cd | ||
|
|
0081472489 |
@@ -38,6 +38,13 @@ public interface ClientVersioningManager {
|
||||
Visibility getClientVisibility(Transaction txn, ContactId contactId,
|
||||
ClientId clientId, int majorVersion) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the minor version of the given client that is supported by the
|
||||
* given contact, or -1 if the contact does not support the client.
|
||||
*/
|
||||
int getClientMinorVersion(Transaction txn, ContactId contactId,
|
||||
ClientId clientId, int majorVersion) throws DbException;
|
||||
|
||||
interface ClientVersioningHook {
|
||||
|
||||
void onClientVisibilityChanging(Transaction txn, Contact c,
|
||||
|
||||
@@ -153,15 +153,4 @@ public class StringUtils {
|
||||
return new String(c);
|
||||
}
|
||||
|
||||
|
||||
public static String getRandomBase32String(int length) {
|
||||
char[] c = new char[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
int character = random.nextInt(32);
|
||||
if (character < 26) c[i] = (char) ('a' + character);
|
||||
else c[i] = (char) ('2' + (character - 26));
|
||||
}
|
||||
return new String(c);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -89,14 +89,9 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
|
||||
public Visibility getClientVisibility(Transaction txn, ContactId contactId,
|
||||
ClientId clientId, int majorVersion) throws DbException {
|
||||
try {
|
||||
Contact contact = db.getContact(txn, contactId);
|
||||
Group g = getContactGroup(contact);
|
||||
// Contact may be in the process of being added or removed, so
|
||||
// contact group may not exist
|
||||
if (!db.containsGroup(txn, g.getId())) return INVISIBLE;
|
||||
LatestUpdates latest = findLatestUpdates(txn, g.getId());
|
||||
LatestUpdates latest = findLatestUpdates(txn, contactId);
|
||||
if (latest == null || latest.remote == null) return INVISIBLE;
|
||||
if (latest.local == null) throw new DbException();
|
||||
if (latest.remote == null) return INVISIBLE;
|
||||
Update localUpdate = loadUpdate(txn, latest.local.messageId);
|
||||
Update remoteUpdate = loadUpdate(txn, latest.remote.messageId);
|
||||
Map<ClientMajorVersion, Visibility> visibilities =
|
||||
@@ -110,6 +105,24 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getClientMinorVersion(Transaction txn, ContactId contactId,
|
||||
ClientId clientId, int majorVersion) throws DbException {
|
||||
try {
|
||||
LatestUpdates latest = findLatestUpdates(txn, contactId);
|
||||
if (latest == null || latest.remote == null) return -1;
|
||||
Update remoteUpdate = loadUpdate(txn, latest.remote.messageId);
|
||||
ClientMajorVersion cv =
|
||||
new ClientMajorVersion(clientId, majorVersion);
|
||||
for (ClientState remote : remoteUpdate.states) {
|
||||
if (remote.majorVersion.equals(cv)) return remote.minorVersion;
|
||||
}
|
||||
return -1;
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createLocalState(Transaction txn) throws DbException {
|
||||
if (db.containsGroup(txn, localGroup.getId())) return;
|
||||
@@ -336,6 +349,17 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
|
||||
MAJOR_VERSION, c);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private LatestUpdates findLatestUpdates(Transaction txn, ContactId c)
|
||||
throws DbException, FormatException {
|
||||
Contact contact = db.getContact(txn, c);
|
||||
Group g = getContactGroup(contact);
|
||||
// Contact may be in the process of being added or removed, so
|
||||
// contact group may not exist
|
||||
if (!db.containsGroup(txn, g.getId())) return null;
|
||||
return findLatestUpdates(txn, g.getId());
|
||||
}
|
||||
|
||||
private LatestUpdates findLatestUpdates(Transaction txn, GroupId g)
|
||||
throws DbException, FormatException {
|
||||
Map<MessageId, BdfDictionary> metadata =
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
@@ -43,6 +44,7 @@ import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||
@@ -657,4 +659,327 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||
c.registerClient(clientId, 123, 234, hook);
|
||||
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnsInvisibleIfContactGroupDoesNotExist()
|
||||
throws Exception {
|
||||
expectGetContactGroup(false);
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
assertEquals(INVISIBLE, c.getClientVisibility(txn, contact.getId(),
|
||||
clientId, 123));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnsInvisibleIfNoRemoteUpdateExists() throws Exception {
|
||||
MessageId localUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
|
||||
expectGetContactGroup(true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(singletonMap(localUpdateId, localUpdateMeta)));
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
assertEquals(INVISIBLE, c.getClientVisibility(txn, contact.getId(),
|
||||
clientId, 123));
|
||||
}
|
||||
|
||||
@Test(expected = DbException.class)
|
||||
public void testThrowsExceptionIfNoLocalUpdateExists() throws Exception {
|
||||
MessageId remoteUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
|
||||
expectGetContactGroup(true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(singletonMap(remoteUpdateId, remoteUpdateMeta)));
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
c.getClientVisibility(txn, contact.getId(), clientId, 123);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnsInvisibleIfClientNotSupportedLocally()
|
||||
throws Exception {
|
||||
MessageId localUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
MessageId remoteUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||
messageMetadata.put(localUpdateId, localUpdateMeta);
|
||||
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
|
||||
// The client is supported remotely but not locally
|
||||
BdfList localUpdateBody = BdfList.of(new BdfList(), 1L);
|
||||
BdfList remoteUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
|
||||
|
||||
expectGetContactGroup(true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getMessageAsList(txn, localUpdateId);
|
||||
will(returnValue(localUpdateBody));
|
||||
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
|
||||
will(returnValue(remoteUpdateBody));
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
assertEquals(INVISIBLE, c.getClientVisibility(txn, contact.getId(),
|
||||
clientId, 123));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnsInvisibleIfClientNotSupportedRemotely()
|
||||
throws Exception {
|
||||
MessageId localUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
MessageId remoteUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||
messageMetadata.put(localUpdateId, localUpdateMeta);
|
||||
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
|
||||
// The client is supported locally but not remotely
|
||||
BdfList localUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
|
||||
BdfList remoteUpdateBody = BdfList.of(new BdfList(), 1L);
|
||||
|
||||
expectGetContactGroup(true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getMessageAsList(txn, localUpdateId);
|
||||
will(returnValue(localUpdateBody));
|
||||
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
|
||||
will(returnValue(remoteUpdateBody));
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
assertEquals(INVISIBLE, c.getClientVisibility(txn, contact.getId(),
|
||||
clientId, 123));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnsVisibleIfClientNotActiveRemotely() throws Exception {
|
||||
MessageId localUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
MessageId remoteUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||
messageMetadata.put(localUpdateId, localUpdateMeta);
|
||||
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
|
||||
// The client is supported locally and remotely but not active
|
||||
BdfList localUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
|
||||
BdfList remoteUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
|
||||
|
||||
expectGetContactGroup(true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getMessageAsList(txn, localUpdateId);
|
||||
will(returnValue(localUpdateBody));
|
||||
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
|
||||
will(returnValue(remoteUpdateBody));
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
assertEquals(VISIBLE, c.getClientVisibility(txn, contact.getId(),
|
||||
clientId, 123));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnsSharedIfClientActiveRemotely() throws Exception {
|
||||
MessageId localUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
MessageId remoteUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||
messageMetadata.put(localUpdateId, localUpdateMeta);
|
||||
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
|
||||
// The client is supported locally and remotely and active
|
||||
BdfList localUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 234, true)), 1L);
|
||||
BdfList remoteUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 234, true)), 1L);
|
||||
|
||||
expectGetContactGroup(true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getMessageAsList(txn, localUpdateId);
|
||||
will(returnValue(localUpdateBody));
|
||||
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
|
||||
will(returnValue(remoteUpdateBody));
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
assertEquals(SHARED, c.getClientVisibility(txn, contact.getId(),
|
||||
clientId, 123));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnsNegativeIfContactGroupDoesNotExist()
|
||||
throws Exception {
|
||||
expectGetContactGroup(false);
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
assertEquals(-1, c.getClientMinorVersion(txn, contact.getId(),
|
||||
clientId, 123));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnsNegativeIfNoRemoteUpdateExists() throws Exception {
|
||||
MessageId localUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
|
||||
expectGetContactGroup(true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(singletonMap(localUpdateId, localUpdateMeta)));
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
assertEquals(-1, c.getClientMinorVersion(txn, contact.getId(),
|
||||
clientId, 123));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnsNegativeIfClientNotSupportedRemotely()
|
||||
throws Exception {
|
||||
MessageId localUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
MessageId remoteUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||
messageMetadata.put(localUpdateId, localUpdateMeta);
|
||||
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
|
||||
// The client is not supported remotely
|
||||
BdfList remoteUpdateBody = BdfList.of(new BdfList(), 1L);
|
||||
|
||||
expectGetContactGroup(true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
|
||||
will(returnValue(remoteUpdateBody));
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
assertEquals(-1, c.getClientMinorVersion(txn, contact.getId(),
|
||||
clientId, 123));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnsMinorVersionIfClientNotActiveRemotely()
|
||||
throws Exception {
|
||||
MessageId localUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
MessageId remoteUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||
messageMetadata.put(localUpdateId, localUpdateMeta);
|
||||
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
|
||||
// The client is supported remotely but not active
|
||||
BdfList remoteUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
|
||||
|
||||
expectGetContactGroup(true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
|
||||
will(returnValue(remoteUpdateBody));
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
assertEquals(234, c.getClientMinorVersion(txn, contact.getId(),
|
||||
clientId, 123));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnsMinorVersionIfClientActiveRemotely()
|
||||
throws Exception {
|
||||
MessageId localUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
MessageId remoteUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||
messageMetadata.put(localUpdateId, localUpdateMeta);
|
||||
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
|
||||
// The client is supported remotely and active
|
||||
BdfList remoteUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 234, true)), 1L);
|
||||
|
||||
expectGetContactGroup(true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
|
||||
will(returnValue(remoteUpdateBody));
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
assertEquals(234, c.getClientMinorVersion(txn, contact.getId(),
|
||||
clientId, 123));
|
||||
}
|
||||
|
||||
private void expectGetContactGroup(boolean exists) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContact(txn, contact.getId());
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).containsGroup(txn, contactGroup.getId());
|
||||
will(returnValue(exists));
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
style="width:24px;height:24px"
|
||||
viewBox="0 0 24 24"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="ic_link_down.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1020"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="13.906433"
|
||||
inkscape:cx="5.1490538"
|
||||
inkscape:cy="22.945407"
|
||||
inkscape:window-x="1440"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg4" />
|
||||
<path
|
||||
fill="#000000"
|
||||
d="M16,6H13V7.9H16C18.26,7.9 20.1,9.73 20.1,12A4.1,4.1 0 0,1 16,16.1H13V18H16A6,6 0 0,0 22,12C22,8.68 19.31,6 16,6M3.9,12C3.9,9.73 5.74,7.9 8,7.9H11V6H8A6,6 0 0,0 2,12A6,6 0 0,0 8,18H11V16.1H8C5.74,16.1 3.9,14.26 3.9,12M8,13H16V11H8V13Z"
|
||||
id="path2" />
|
||||
<path
|
||||
id="path884"
|
||||
d="m 21.659779,17.473157 h -2.295918 v 3.061224 H 17.51182 l 3.000001,3 2.999999,-3 h -1.852041 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000000;stroke-width:0.38265303" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,58 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
style="width:24px;height:24px"
|
||||
viewBox="0 0 24 24"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="ic_link_up.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1020"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="13.906433"
|
||||
inkscape:cx="5.1490538"
|
||||
inkscape:cy="22.945407"
|
||||
inkscape:window-x="1440"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg4" />
|
||||
<path
|
||||
fill="#000000"
|
||||
d="M16,6H13V7.9H16C18.26,7.9 20.1,9.73 20.1,12A4.1,4.1 0 0,1 16,16.1H13V18H16A6,6 0 0,0 22,12C22,8.68 19.31,6 16,6M3.9,12C3.9,9.73 5.74,7.9 8,7.9H11V6H8A6,6 0 0,0 2,12A6,6 0 0,0 8,18H11V16.1H8C5.74,16.1 3.9,14.26 3.9,12M8,13H16V11H8V13Z"
|
||||
id="path2" />
|
||||
<path
|
||||
id="path884"
|
||||
d="M 21.659779,23.534381 H 19.363861 V 20.473157 H 17.51182 l 3.000001,-3 2.999999,3 h -1.852041 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000000;stroke-width:0.38265303" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -1,57 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
version="1.1"
|
||||
id="svg12"
|
||||
sodipodi:docname="nearby.svg"
|
||||
style="fill:none"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<metadata
|
||||
id="metadata18">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs16" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1020"
|
||||
id="namedview14"
|
||||
showgrid="false"
|
||||
inkscape:zoom="15.170655"
|
||||
inkscape:cx="9.6488139"
|
||||
inkscape:cy="11.430963"
|
||||
inkscape:window-x="1440"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg12" />
|
||||
<path
|
||||
style="opacity:1;fill:#110000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.37658679px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 11.173062,2.4030645 C 9.9685672,2.4471125 8.7753453,2.7130268 7.6640583,3.2033016 8.187015,3.7262611 8.3967456,4.1446273 8.6059301,4.5629964 11.639055,3.412486 15.091396,4.0393524 17.497058,6.4449696 c 0.732151,0.7321412 1.359335,1.5691523 1.673062,2.5104781 0.418424,-0.2091845 0.941213,-0.3133673 1.464151,-0.3133672 h 0.104456 C 20.215788,7.3869735 19.482978,6.3401088 18.541616,5.2941863 16.528265,3.2807917 13.82295,2.3061587 11.173062,2.4030645 Z M 5.1695142,3.2316286 A 2.7193895,2.7193895 0 0 0 2.4501247,5.9510181 2.7193895,2.7193895 0 0 0 5.1695142,8.6704075 2.7193895,2.7193895 0 0 0 7.8889037,5.9510181 2.7193895,2.7193895 0 0 0 5.1695142,3.2316286 Z M 2.5404169,8.5358543 C 0.97153856,12.196552 1.7027262,16.484811 4.6313017,19.413413 c 1.8826514,1.987239 4.497171,2.930071 7.0073853,2.930071 2.510177,0 5.126431,-0.942832 7.009154,-2.930071 0.941272,-0.941362 1.672402,-2.091035 2.195341,-3.346124 h -0.104455 c -0.522939,0 -1.045818,-0.104155 -1.464151,-0.313367 -0.418423,0.941362 -0.940911,1.778328 -1.673062,2.510478 -3.242327,3.242419 -8.5770829,3.242419 -11.819429,0 C 3.27188,15.858828 2.6451458,12.406442 3.795656,9.3732707 3.377287,9.164086 2.9587814,8.9542233 2.5404169,8.5358543 Z m 9.0540081,0.8444984 a 2.9913288,2.9913284 0 0 0 -2.9902654,2.9902663 2.9913288,2.9913284 0 0 0 2.9902654,2.992036 2.9913288,2.9913284 0 0 0 2.992037,-2.992036 2.9913288,2.9913284 0 0 0 -2.992037,-2.9902663 z m 9.138991,0.423134 a 2.7193895,2.7193895 0 0 0 -2.71939,2.7193903 2.7193895,2.7193895 0 0 0 2.71939,2.719389 2.7193895,2.7193895 0 0 0 2.719389,-2.719389 2.7193895,2.7193895 0 0 0 -2.719389,-2.7193903 z"
|
||||
id="path832-3-6"
|
||||
inkscape:connector-curvature="0" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.3 KiB |
@@ -1,176 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="150"
|
||||
height="178"
|
||||
viewBox="0 0 150 178"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg129"
|
||||
sodipodi:docname="nickname.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<metadata
|
||||
id="metadata133">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1020"
|
||||
id="namedview131"
|
||||
showgrid="false"
|
||||
inkscape:zoom="3.7500494"
|
||||
inkscape:cx="84.461975"
|
||||
inkscape:cy="96.344913"
|
||||
inkscape:window-x="1440"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg129" />
|
||||
<path
|
||||
d="M 85.1905,0.127869 3.35693,0.328805 C 2.46521,0.330621 1.6107,0.690255 0.981217,1.32866 0.351735,1.96706 -0.0011965,2.83198 3.04782e-6,3.73331 L 0.372743,156.201 c 0.001185,0.446 0.089367,0.888 0.259505,1.3 0.170138,0.412 0.418902,0.786 0.732092,1.101 0.31319,0.315 0.68466,0.564 1.09322,0.734 0.40856,0.17 0.84619,0.257 1.28792,0.256 l 81.83362,-0.201 c 0.8917,-0.003 1.746,-0.363 2.375,-1.002 0.6291,-0.639 0.9814,-1.504 0.9796,-2.405 L 88.5542,3.51639 C 88.5512,2.61665 88.1955,1.75479 87.565,1.11965 86.9345,0.484503 86.0807,0.127864 85.1905,0.127869 Z"
|
||||
id="path2"
|
||||
inkscape:connector-curvature="0"
|
||||
style="display:inline;opacity:0.25;fill:url(#paint0_linear)" />
|
||||
<path
|
||||
d="M 84.2815,-0.0019907 4.27572,0.195606 C 2.57395,0.199809 1.19777,1.59763 1.20193,3.31772 L 1.56691,154.298 c 0.00416,1.72 1.38709,3.111 3.08885,3.107 l 80.00584,-0.198 c 1.7017,-0.004 3.0779,-1.402 3.0737,-3.122 L 87.3704,3.1049 C 87.3662,1.38481 85.9833,-0.0061937 84.2815,-0.0019907 Z"
|
||||
id="path4"
|
||||
inkscape:connector-curvature="0"
|
||||
style="display:inline;opacity:0.25;fill:#6d6d6d;fill-opacity:0.7" />
|
||||
<path
|
||||
style="display:inline;opacity:0.25;fill:#646464"
|
||||
d="M 81.939453 3.9570312 L 65.933594 3.9960938 C 65.688694 5.6638138 64.861562 7.1881588 63.601562 8.2929688 C 62.341563 9.3977688 60.732453 10.010031 59.064453 10.019531 L 29.302734 10.091797 C 27.634734 10.090597 26.021259 9.4871919 24.755859 8.3886719 C 23.490459 7.2901519 22.655444 5.7700256 22.402344 4.1035156 L 6.6367188 4.1425781 C 5.7689587 4.1449981 4.9382819 4.4954875 4.3261719 5.1171875 C 3.7140519 5.7388975 3.3712469 6.5799313 3.3730469 7.4570312 L 3.4609375 44.068359 L 3.7167969 150.16797 C 3.7209769 151.04497 4.0691969 151.88495 4.6855469 152.50195 C 5.3018969 153.11895 6.1361462 153.46394 7.0039062 153.46094 L 82.306641 153.27539 C 83.171641 153.27039 83.999475 152.92078 84.609375 152.30078 C 85.219375 151.68078 85.560547 150.8408 85.560547 149.9668 L 85.306641 43.5625 L 85.220703 7.2558594 C 85.218303 6.3787594 84.870959 5.5386319 84.255859 4.9199219 C 83.640859 4.3012219 82.807253 3.9552112 81.939453 3.9570312 z M 52.652344 5.7910156 L 34.357422 6.0683594 C 34.107922 6.0721494 33.908409 6.27906 33.912109 6.53125 L 33.916016 6.8300781 C 33.919716 7.0822681 34.1255 7.28504 34.375 7.28125 L 52.671875 7.0019531 C 52.921375 6.9981731 53.118934 6.7912525 53.115234 6.5390625 L 53.111328 6.2402344 C 53.107628 5.9880444 52.901844 5.7872256 52.652344 5.7910156 z "
|
||||
id="path6" />
|
||||
<path
|
||||
d="M 57.2036,6.934 C 57.6016,6.92797 57.9193,6.59699 57.9133,6.19476 57.9074,5.79252 57.5799,5.47134 57.182,5.47738 c -0.398,0.00604 -0.7157,0.33701 -0.7098,0.73924 0.006,0.40224 0.3335,0.72342 0.7314,0.71738 z"
|
||||
id="path10"
|
||||
inkscape:connector-curvature="0"
|
||||
style="opacity:0.25;fill:#dbdbdb" />
|
||||
<path
|
||||
style="display:inline;opacity:0.25;fill:#e0e0e0"
|
||||
d="m 17.640625,19.458984 c -4.1846,0 -7.576172,3.445513 -7.576172,7.695313 0,4.2498 3.391572,7.695312 7.576172,7.695312 4.1845,0 7.576172,-3.445512 7.576172,-7.695312 0,-4.2498 -3.391672,-7.695313 -7.576172,-7.695313 z m 19.882813,4.177735 v 3.234375 h 40.117187 v -3.234375 z m 0,6.466797 v 3.234375 H 77.640625 V 30.103516 Z M 17.640625,44.087891 c -4.1846,0 -7.576172,3.443559 -7.576172,7.693359 0,4.2498 3.391572,7.695313 7.576172,7.695312 4.1845,0 7.576172,-3.445512 7.576172,-7.695312 0,-4.2498 -3.391672,-7.693359 -7.576172,-7.693359 z m 19.882813,4.177734 V 51.5 h 40.117187 v -3.234375 z m 0,6.466797 v 3.232422 H 77.640625 V 54.732422 Z M 17.640625,68.714844 c -4.1846,0 -7.576172,3.445512 -7.576172,7.695312 0,4.2498 3.391572,7.695313 7.576172,7.695313 4.1845,0 7.576172,-3.445513 7.576172,-7.695313 0,-4.2498 -3.391672,-7.695312 -7.576172,-7.695312 z m 19.882813,4.179687 v 3.232422 h 40.117187 v -3.232422 z m 0,6.466797 V 82.59375 H 77.640625 V 79.361328 Z M 17.640625,93.34375 c -4.1846,0 -7.576172,3.445613 -7.576172,7.69531 0,4.25 3.391572,7.69532 7.576172,7.69532 4.1845,0 7.576172,-3.44532 7.576172,-7.69532 0,-4.249698 -3.391672,-7.69531 -7.576172,-7.69531 z m 19.882813,4.179688 v 3.232422 h 40.117187 v -3.232422 z m 0,6.464842 v 3.23438 h 40.117187 v -3.23438 z m -19.882813,13.98438 c -4.1846,0 -7.576172,3.44631 -7.576172,7.69531 0,4.25 3.391572,7.69336 7.576172,7.69336 4.1845,0 7.576172,-3.44336 7.576172,-7.69336 0,-4.249 -3.391672,-7.69531 -7.576172,-7.69531 z m 19.882813,4.17773 v 3.23438 h 40.117187 v -3.23438 z m 0,6.4668 v 3.23437 h 40.117187 v -3.23437 z"
|
||||
id="path12"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="display:inline;fill:#313131"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path44"
|
||||
d="m 136.95,18.4066 -80.0056,0.1976 c -1.7018,0.0042 -3.078,1.402 -3.0738,3.1221 l 0.365,150.9807 c 0.0041,1.72 1.3871,3.111 3.0888,3.107 l 80.0056,-0.198 c 1.702,-0.004 3.078,-1.402 3.074,-3.122 L 140.039,21.5135 c -0.004,-1.7201 -1.387,-3.1111 -3.089,-3.1069 z" />
|
||||
<path
|
||||
style="display:inline;fill:#3e3e3e"
|
||||
d="M 134.60938 22.357422 L 118.59961 22.394531 C 118.35561 24.062231 117.52758 25.586606 116.26758 26.691406 C 115.00758 27.796206 113.39847 28.408469 111.73047 28.417969 L 81.96875 28.492188 C 80.30065 28.490987 78.689228 27.885609 77.423828 26.787109 C 76.158428 25.688609 75.323313 24.168453 75.070312 22.501953 L 59.304688 22.541016 C 58.436887 22.543416 57.604288 22.893925 56.992188 23.515625 C 56.380088 24.137325 56.037262 24.980322 56.039062 25.857422 L 56.128906 62.46875 L 56.748047 62.466797 L 56.128906 62.470703 L 56.382812 168.57031 C 56.385213 169.44731 56.732656 170.28725 57.347656 170.90625 C 57.962756 171.52425 58.796362 171.87114 59.664062 171.86914 L 134.9668 171.68359 C 135.8348 171.68159 136.66534 171.33098 137.27734 170.70898 C 137.88934 170.08798 138.23247 169.24614 138.23047 168.36914 L 137.97266 62.21875 L 137.97461 62.21875 L 137.88867 25.65625 C 137.88667 24.77915 137.54078 23.939012 136.92578 23.320312 C 136.31078 22.701612 135.47738 22.355522 134.60938 22.357422 z "
|
||||
id="path46" />
|
||||
<path
|
||||
style="display:inline;fill:#e0e0e0"
|
||||
d="M 109.84961 23.890625 C 109.45161 23.896725 109.13462 24.228559 109.14062 24.630859 C 109.14663 25.033059 109.47309 25.353756 109.87109 25.347656 C 110.26909 25.341656 110.58608 25.009622 110.58008 24.607422 C 110.57408 24.205222 110.24761 23.884625 109.84961 23.890625 z M 105.32031 24.201172 L 87.025391 24.478516 C 86.775891 24.482316 86.576378 24.691159 86.580078 24.943359 L 86.583984 25.242188 C 86.587684 25.494387 86.793469 25.695206 87.042969 25.691406 L 105.33984 25.414062 C 105.58884 25.410363 105.7872 25.203372 105.7832 24.951172 L 105.7793 24.650391 C 105.7753 24.398191 105.56931 24.197372 105.32031 24.201172 z M 63.365234 37.568359 L 63.365234 53.738281 L 79.365234 53.738281 L 79.365234 37.568359 L 63.365234 37.568359 z M 86.748047 37.568359 L 86.748047 40.800781 L 126.86523 40.800781 L 126.86523 37.568359 L 86.748047 37.568359 z M 86.748047 44.037109 L 86.748047 47.269531 L 126.86523 47.269531 L 126.86523 44.037109 L 86.748047 44.037109 z M 71.787109 112.99609 C 67.602609 112.99609 64.210937 116.44141 64.210938 120.69141 C 64.210938 124.94141 67.602609 128.38672 71.787109 128.38672 C 75.971709 128.38672 79.365234 124.94141 79.365234 120.69141 C 79.365234 116.44141 75.971709 112.99609 71.787109 112.99609 z M 86.748047 116.92773 L 86.748047 120.16016 L 126.86523 120.16016 L 126.86523 116.92773 L 86.748047 116.92773 z M 86.748047 123.39258 L 86.748047 126.62695 L 126.86523 126.62695 L 126.86523 123.39258 L 86.748047 123.39258 z M 71.787109 142.60156 C 67.602609 142.60156 64.210937 146.04687 64.210938 150.29688 C 64.210938 154.54688 67.602609 157.99023 71.787109 157.99023 C 75.971709 157.99023 79.365234 154.54688 79.365234 150.29688 C 79.365234 146.04688 75.971709 142.60156 71.787109 142.60156 z M 86.748047 145.53516 L 86.748047 148.76758 L 126.86523 148.76758 L 126.86523 145.53516 L 86.748047 145.53516 z M 86.748047 152.00195 L 86.748047 155.23438 L 126.86523 155.23438 L 126.86523 152.00195 L 86.748047 152.00195 z "
|
||||
id="path50" />
|
||||
<path
|
||||
style="display:inline;fill:#d5d6d7"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path56"
|
||||
d="M 148.744,68.6655 H 45.619 v 33.5835 h 103.125 z" />
|
||||
<path
|
||||
style="display:inline;fill:#87c214"
|
||||
d="m 97.822266,50.503906 v 5.722656 h 17.966794 v -5.722656 z m -11.074219,30.59961 v 3.232422 h 40.117183 v -3.232422 z m 0,6.46875 v 3.232422 h 40.117183 v -3.232422 z"
|
||||
id="path58"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="display:inline;fill:#f5f5f5"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path68"
|
||||
d="m 66.8652,92.3143 c 4.1846,0 7.5768,-3.4451 7.5768,-7.695 0,-4.2498 -3.3922,-7.6949 -7.5768,-7.6949 -4.1846,0 -7.5768,3.4451 -7.5768,7.6949 0,4.2499 3.3922,7.695 7.5768,7.695 z" />
|
||||
<path
|
||||
style="display:inline;fill:#87c214"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path70"
|
||||
d="m 70.1296,90.0058 h -6.5738 c -0.3153,0.0024 -0.6253,0.0818 -0.9038,0.2312 -0.2784,0.1495 -0.5171,0.3647 -0.6956,0.6274 1.4766,0.8563 3.1474,1.3114 4.8498,1.321 1.7023,0.0096 3.3781,-0.4266 4.864,-1.2662 -0.1666,-0.265 -0.3932,-0.486 -0.6612,-0.6448 -0.2679,-0.1588 -0.5693,-0.2509 -0.8794,-0.2686 z" />
|
||||
<path
|
||||
style="display:inline;fill:#333333"
|
||||
d="m 66.861328,80.746094 c -2.0798,0 -3.765625,1.70444 -3.765625,3.80664 0,0.393506 0.07586,0.766 0.185547,1.123047 -0.327549,0.812667 -0.821888,1.878261 -1.158203,1.708985 0,0 5.069822,4.4252 9.544922,0 -0.355198,-0.613452 -0.773279,-1.18389 -1.21875,-1.732422 0.10502,-0.350063 0.177734,-0.714879 0.177734,-1.09961 0,-2.1022 -1.685825,-3.80664 -3.765625,-3.80664 z"
|
||||
id="path72"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="display:inline;fill:#fda57d"
|
||||
d="m 66.861328,81.570312 c -1.800651,0 -3.302121,1.279176 -3.673828,2.986329 -0.0049,-3.79e-4 -0.0088,-0.0059 -0.01367,-0.0059 -0.1933,0 -0.349609,0.297063 -0.349609,0.664063 0,0.347156 0.141531,0.622551 0.320312,0.652344 0.180942,1.407599 1.108928,2.5691 2.38086,3.058593 v 0.732422 c 0,0.3234 0.127315,0.634681 0.353515,0.863281 0.2263,0.2287 0.533616,0.357422 0.853516,0.357422 h 0.224609 c 0.1588,3e-4 0.316091,-0.03245 0.462891,-0.09375 0.1467,-0.0613 0.280278,-0.150172 0.392578,-0.263672 0.1123,-0.1135 0.201119,-0.248084 0.261719,-0.396484 0.0606,-0.1483 0.0921,-0.30825 0.0918,-0.46875 v -0.720703 c 1.288752,-0.483775 2.232282,-1.654559 2.412109,-3.076172 0.168226,-0.0485 0.298828,-0.311757 0.298828,-0.644531 0,-0.363137 -0.153161,-0.655938 -0.34375,-0.66211 -0.373249,-1.705035 -1.87271,-2.982422 -3.671875,-2.982422 z"
|
||||
id="path78"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="display:inline;fill:#333333"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path84"
|
||||
d="m 63.2529,83.9344 h 7.1792 c 0,0 -0.6122,-2.9296 -3.3275,-2.7401 -2.7154,0.1895 -3.8517,2.7401 -3.8517,2.7401 z" />
|
||||
<defs
|
||||
id="defs127">
|
||||
<linearGradient
|
||||
id="paint0_linear"
|
||||
x1="10.0511"
|
||||
y1="162.566"
|
||||
x2="80.1467"
|
||||
y2="-2.31086"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#808080"
|
||||
stop-opacity="0.25"
|
||||
id="stop110" />
|
||||
<stop
|
||||
offset="0.54"
|
||||
stop-color="#808080"
|
||||
stop-opacity="0.12"
|
||||
id="stop112" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#808080"
|
||||
stop-opacity="0.1"
|
||||
id="stop114" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear"
|
||||
x1="45488.3"
|
||||
y1="16529.6"
|
||||
x2="45488.3"
|
||||
y2="10752.2"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#808080"
|
||||
stop-opacity="0.25"
|
||||
id="stop117" />
|
||||
<stop
|
||||
offset="0.54"
|
||||
stop-color="#808080"
|
||||
stop-opacity="0.12"
|
||||
id="stop119" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#808080"
|
||||
stop-opacity="0.1"
|
||||
id="stop121" />
|
||||
</linearGradient>
|
||||
<clipPath
|
||||
id="clip0">
|
||||
<rect
|
||||
width="150"
|
||||
height="178"
|
||||
fill="white"
|
||||
id="rect124" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 13 KiB |
@@ -2,70 +2,6 @@ apply plugin: 'com.android.application'
|
||||
apply plugin: 'witness'
|
||||
apply from: 'witness.gradle'
|
||||
|
||||
// prototype
|
||||
repositories {
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(path: ':briar-core', configuration: 'default')
|
||||
implementation project(path: ':bramble-core', configuration: 'default')
|
||||
implementation project(':bramble-android')
|
||||
|
||||
def supportVersion = '28.0.0'
|
||||
implementation "com.android.support:support-v4:$supportVersion"
|
||||
implementation("com.android.support:appcompat-v7:$supportVersion") {
|
||||
exclude module: 'support-v4'
|
||||
}
|
||||
implementation("com.android.support:preference-v14:$supportVersion") {
|
||||
exclude module: 'support-v4'
|
||||
}
|
||||
implementation("com.android.support:design:$supportVersion") {
|
||||
exclude module: 'support-v4'
|
||||
exclude module: 'recyclerview-v7'
|
||||
}
|
||||
implementation "com.android.support:cardview-v7:$supportVersion"
|
||||
implementation "com.android.support:support-annotations:$supportVersion"
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
|
||||
implementation('ch.acra:acra:4.9.1') {
|
||||
exclude module: 'support-v4'
|
||||
exclude module: 'support-annotations'
|
||||
}
|
||||
implementation 'info.guardianproject.panic:panic:0.5'
|
||||
implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
|
||||
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
||||
implementation 'com.google.zxing:core:3.3.0'
|
||||
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.8.0'
|
||||
implementation 'com.vanniktech:emoji-google:0.5.1'
|
||||
|
||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'
|
||||
|
||||
compileOnly 'javax.annotation:jsr250-api:1.0'
|
||||
|
||||
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
||||
testImplementation project(path: ':bramble-core', configuration: 'testOutput')
|
||||
testImplementation 'org.robolectric:robolectric:3.8'
|
||||
testImplementation 'org.robolectric:shadows-support-v4:3.3.2'
|
||||
testImplementation 'org.mockito:mockito-core:2.13.0'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation "org.jmock:jmock:2.8.2"
|
||||
testImplementation "org.jmock:jmock-junit4:2.8.2"
|
||||
testImplementation "org.jmock:jmock-legacy:2.8.2"
|
||||
testImplementation "org.hamcrest:hamcrest-library:1.3"
|
||||
testImplementation "org.hamcrest:hamcrest-core:1.3"
|
||||
|
||||
def espressoVersion = '3.0.2'
|
||||
androidTestImplementation "com.android.support.test.espresso:espresso-core:$espressoVersion"
|
||||
androidTestImplementation "com.android.support.test.espresso:espresso-contrib:$espressoVersion"
|
||||
androidTestImplementation "com.android.support.test.espresso:espresso-intents:$espressoVersion"
|
||||
androidTestImplementation "tools.fastlane:screengrab:1.1.0"
|
||||
androidTestImplementation "com.android.support.test.uiautomator:uiautomator-v18:2.1.3"
|
||||
androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.0.2"
|
||||
androidTestCompileOnly 'javax.annotation:jsr250-api:1.0'
|
||||
androidTestImplementation 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
def getStdout = { command, defaultValue ->
|
||||
def stdout = new ByteArrayOutputStream()
|
||||
try {
|
||||
@@ -99,7 +35,7 @@ android {
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix ".test"
|
||||
applicationIdSuffix ".debug"
|
||||
shrinkResources false
|
||||
minifyEnabled true
|
||||
crunchPngs false
|
||||
@@ -190,9 +126,6 @@ dependencies {
|
||||
}
|
||||
implementation 'com.github.chrisbanes:PhotoView:2.1.4' // later versions already use androidx
|
||||
|
||||
// prototype
|
||||
implementation "com.github.kobakei:MaterialFabSpeedDial:1.2.0"
|
||||
|
||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.19'
|
||||
annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">Briar Test</string>
|
||||
<string name="app_package" translatable="false">org.briarproject.briar.android.test</string>
|
||||
<string name="app_name" translatable="false">Briar Debug</string>
|
||||
<string name="app_package" translatable="false">org.briarproject.briar.android.debug</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
package="org.briarproject.briar"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
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"/>
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
||||
@@ -30,8 +29,7 @@
|
||||
android:label="@string/app_name"
|
||||
android:logo="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/BriarTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
android:theme="@style/BriarTheme">
|
||||
|
||||
<receiver
|
||||
android:name="org.briarproject.briar.android.login.SignInReminderReceiver"
|
||||
@@ -424,29 +422,5 @@
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/BriarTheme.NoActionBar"/>
|
||||
|
||||
<!-- Prototype -->
|
||||
<activity
|
||||
android:name=".android.contact.ContactLinkExchangeActivity"
|
||||
android:theme="@style/BriarTheme"
|
||||
android:label="@string/add_contact_title"
|
||||
android:windowSoftInputMode="stateHidden|adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="briar"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".android.contact.PendingRequestsActivity"
|
||||
android:label="@string/pending_contact_requests"
|
||||
android:theme="@style/BriarTheme"/>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -15,17 +15,12 @@ import org.briarproject.briar.android.blog.ReblogFragment;
|
||||
import org.briarproject.briar.android.blog.RssFeedImportActivity;
|
||||
import org.briarproject.briar.android.blog.RssFeedManageActivity;
|
||||
import org.briarproject.briar.android.blog.WriteBlogPostActivity;
|
||||
import org.briarproject.briar.android.contact.ContactLinkExchangeActivity;
|
||||
import org.briarproject.briar.android.contact.ContactLinkExchangeFragment;
|
||||
import org.briarproject.briar.android.contact.ContactListFragment;
|
||||
import org.briarproject.briar.android.contact.ContactModule;
|
||||
import org.briarproject.briar.android.contact.ContactNicknameFragment;
|
||||
import org.briarproject.briar.android.contact.ContactQrCodeInputFragment;
|
||||
import org.briarproject.briar.android.contact.ContactQrCodeOutputFragment;
|
||||
import org.briarproject.briar.android.contact.PendingRequestsActivity;
|
||||
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;
|
||||
@@ -177,10 +172,6 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(UnlockActivity activity);
|
||||
|
||||
void inject(ContactLinkExchangeActivity activity);
|
||||
|
||||
void inject(PendingRequestsActivity activity);
|
||||
|
||||
// Fragments
|
||||
void inject(AuthorNameFragment fragment);
|
||||
|
||||
@@ -228,12 +219,6 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(AliasDialogFragment aliasDialogFragment);
|
||||
|
||||
void inject(ContactLinkExchangeFragment fragment);
|
||||
|
||||
void inject(ContactNicknameFragment fragment);
|
||||
|
||||
void inject(ContactQrCodeOutputFragment fragment);
|
||||
|
||||
void inject(ContactQrCodeInputFragment fragment);
|
||||
void inject(ImageFragment imageFragment);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
package org.briarproject.briar.android.contact;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
|
||||
import org.briarproject.bramble.api.plugin.TorConstants;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.app.AlarmManager.ELAPSED_REALTIME;
|
||||
import static android.content.Intent.ACTION_SEND;
|
||||
import static android.content.Intent.ACTION_VIEW;
|
||||
import static android.content.Intent.EXTRA_TEXT;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
import static android.os.SystemClock.elapsedRealtime;
|
||||
import static java.lang.String.CASE_INSENSITIVE_ORDER;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomBase32String;
|
||||
|
||||
public class ContactLinkExchangeActivity extends BriarActivity implements
|
||||
BaseFragmentListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ContactLinkExchangeActivity.class.getName());
|
||||
|
||||
static final int LINK_LENGTH = 128;
|
||||
static final Pattern LINK_REGEX =
|
||||
Pattern.compile("(briar://)?([a-z2-7]{" + LINK_LENGTH + "})");
|
||||
static final String OUR_LINK = "briar://" + getRandomBase32String(LINK_LENGTH);;
|
||||
|
||||
@Inject
|
||||
LifecycleManager lifecycleManager;
|
||||
@Inject
|
||||
MessagingManager messagingManager;
|
||||
@Inject
|
||||
ConnectionRegistry connectionRegistry;
|
||||
@Inject
|
||||
Clock clock;
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
super.onCreate(state);
|
||||
setContentView(R.layout.activity_fragment_container);
|
||||
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
Intent i = getIntent();
|
||||
if (i != null) {
|
||||
String action = i.getAction();
|
||||
if (ACTION_SEND.equals(action) || ACTION_VIEW.equals(action)) {
|
||||
String text = i.getStringExtra(EXTRA_TEXT);
|
||||
if (text != null) {
|
||||
showInitialFragment(
|
||||
ContactLinkExchangeFragment.newInstance(text));
|
||||
return;
|
||||
}
|
||||
String uri = i.getDataString();
|
||||
if (uri != null) {
|
||||
showInitialFragment(
|
||||
ContactLinkExchangeFragment.newInstance(uri));
|
||||
return;
|
||||
}
|
||||
} else if ("addContact".equals(action)) {
|
||||
removeFakeRequest(i.getStringExtra("name"),
|
||||
i.getLongExtra("timestamp", 0), i.getLongExtra("addAt", 0));
|
||||
setIntent(null);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
if (state == null) {
|
||||
showInitialFragment(new ContactLinkExchangeFragment());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isBriarLink(@Nullable CharSequence s) {
|
||||
return s != null && LINK_REGEX.matcher(s).matches();
|
||||
}
|
||||
|
||||
void scanCode() {
|
||||
showNextFragment(new ContactQrCodeInputFragment());
|
||||
}
|
||||
|
||||
void linkScanned(@Nullable String link) {
|
||||
// FIXME: Contact name is lost
|
||||
showNextFragment(ContactLinkExchangeFragment.newInstance(link));
|
||||
}
|
||||
|
||||
void showCode() {
|
||||
showNextFragment(new ContactQrCodeOutputFragment());
|
||||
}
|
||||
|
||||
void addFakeRequest(String name, String link) {
|
||||
|
||||
AlarmManager alarmManager =
|
||||
(AlarmManager) requireNonNull(getSystemService(ALARM_SERVICE));
|
||||
double random = getPseudoRandom(link, OUR_LINK.replace("briar://", ""));
|
||||
long m = SECONDS.toMillis(50);
|
||||
long fromNow = (long) (-m * Math.log(random));
|
||||
// it should take at least 30 seconds
|
||||
if (fromNow < SECONDS.toMillis(30)) fromNow = SECONDS.toMillis(30);
|
||||
LOG.info("Delay " + fromNow + " ms based on seed " + random);
|
||||
long triggerAt = elapsedRealtime() + fromNow;
|
||||
|
||||
long timestamp = clock.currentTimeMillis();
|
||||
try {
|
||||
messagingManager.addNewPendingContact(name, timestamp, timestamp + fromNow);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
|
||||
Intent i = new Intent(this, ContactLinkExchangeActivity.class);
|
||||
i.setAction("addContact");
|
||||
i.setFlags(FLAG_ACTIVITY_NEW_TASK);
|
||||
i.putExtra("name", name);
|
||||
i.putExtra("timestamp", timestamp);
|
||||
i.putExtra("addAt", timestamp + fromNow);
|
||||
PendingIntent pendingIntent =
|
||||
PendingIntent.getActivity(this, (int) timestamp / 1000, i, 0);
|
||||
alarmManager.set(ELAPSED_REALTIME, triggerAt, pendingIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pseudo-random value greater than or equal to 0 and less than 1,
|
||||
* approximately uniformly distributed, based on the given strings. The
|
||||
* same value is returned if the strings are swapped.
|
||||
*/
|
||||
private double getPseudoRandom(String a, String b) {
|
||||
String first, second;
|
||||
if (CASE_INSENSITIVE_ORDER.compare(a, b) < 0) {
|
||||
first = a;
|
||||
second = b;
|
||||
} else {
|
||||
first = b;
|
||||
second = a;
|
||||
}
|
||||
int hash = (first + second).hashCode() & Integer.MAX_VALUE;
|
||||
return hash / (1.0 + Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
private void removeFakeRequest(String name, long timestamp, long addAt) {
|
||||
if (lifecycleManager.getLifecycleState() != RUNNING) {
|
||||
LOG.info("Lifecycle not started, not adding contact " + name);
|
||||
return;
|
||||
}
|
||||
LOG.info("Adding Contact " + name);
|
||||
try {
|
||||
ContactId c = messagingManager
|
||||
.removePendingContact(name, timestamp, addAt);
|
||||
// fake contact online status
|
||||
connectionRegistry.registerConnection(c, TorConstants.ID, true);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
package org.briarproject.briar.android.contact;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.TextInputEditText;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.support.v4.app.ShareCompat.IntentBuilder;
|
||||
import android.support.v7.widget.AppCompatImageButton;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.content.Context.CLIPBOARD_SERVICE;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.support.v4.graphics.drawable.DrawableCompat.setTint;
|
||||
import static android.support.v4.graphics.drawable.DrawableCompat.wrap;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.briar.android.contact.ContactLinkExchangeActivity.LINK_REGEX;
|
||||
import static org.briarproject.briar.android.contact.ContactLinkExchangeActivity.OUR_LINK;
|
||||
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
|
||||
|
||||
public class ContactLinkExchangeFragment extends BaseFragment {
|
||||
|
||||
static final String TAG = ContactLinkExchangeFragment.class.getName();
|
||||
|
||||
static BaseFragment newInstance(@Nullable String link) {
|
||||
BaseFragment f = new ContactLinkExchangeFragment();
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("link", link);
|
||||
f.setArguments(bundle);
|
||||
return f;
|
||||
}
|
||||
|
||||
private ClipboardManager clipboard;
|
||||
private TextInputLayout linkInputLayout;
|
||||
private TextInputEditText linkInput;
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
if (getActivity() == null || getContext() == null) return null;
|
||||
|
||||
getActivity().setTitle(R.string.add_contact_remotely_title_case);
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_contact_link_exchange,
|
||||
container, false);
|
||||
|
||||
clipboard = (ClipboardManager) requireNonNull(
|
||||
getContext().getSystemService(CLIPBOARD_SERVICE));
|
||||
|
||||
int color =
|
||||
resolveColorAttribute(getContext(), R.attr.colorControlNormal);
|
||||
|
||||
Button continueButton = v.findViewById(R.id.addButton);
|
||||
continueButton.setOnClickListener(view -> onContinueButtonClicked());
|
||||
|
||||
linkInputLayout = v.findViewById(R.id.linkInputLayout);
|
||||
linkInput = v.findViewById(R.id.linkInput);
|
||||
if (SDK_INT < 23) {
|
||||
Drawable drawable = wrap(linkInput.getCompoundDrawables()[0]);
|
||||
setTint(drawable, color);
|
||||
linkInput.setCompoundDrawables(drawable, null, null, null);
|
||||
}
|
||||
if (getArguments() != null)
|
||||
linkInput.setText(getArguments().getString("link"));
|
||||
|
||||
AppCompatImageButton pasteButton = v.findViewById(R.id.pasteButton);
|
||||
pasteButton.setOnClickListener(view -> {
|
||||
ClipData clip = clipboard.getPrimaryClip();
|
||||
if (clip != null)
|
||||
linkInput.setText(clip.getItemAt(0).getText());
|
||||
});
|
||||
|
||||
Button scanCodeButton = v.findViewById(R.id.scanCodeButton);
|
||||
scanCodeButton.setOnClickListener(view -> {
|
||||
ContactLinkExchangeActivity activity = getCastActivity();
|
||||
if (activity != null) activity.scanCode();
|
||||
});
|
||||
|
||||
TextView linkView = v.findViewById(R.id.linkView);
|
||||
linkView.setText(OUR_LINK);
|
||||
|
||||
ClipData clip = ClipData.newPlainText(
|
||||
getString(R.string.link_clip_label), OUR_LINK);
|
||||
|
||||
Button copyButton = v.findViewById(R.id.copyButton);
|
||||
copyButton.setOnClickListener(view -> {
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Toast.makeText(getContext(), R.string.link_copied_toast,
|
||||
LENGTH_SHORT).show();
|
||||
});
|
||||
|
||||
Button shareButton = v.findViewById(R.id.shareButton);
|
||||
shareButton.setOnClickListener(view -> {
|
||||
IntentBuilder.from(getActivity())
|
||||
.setText(OUR_LINK)
|
||||
.setType("text/plain")
|
||||
.startChooser();
|
||||
});
|
||||
|
||||
Button showCodeButton = v.findViewById(R.id.showCodeButton);
|
||||
showCodeButton.setOnClickListener(
|
||||
view -> {
|
||||
ContactLinkExchangeActivity activity = getCastActivity();
|
||||
if (activity != null) activity.showCode();
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
private ContactLinkExchangeActivity getCastActivity() {
|
||||
return (ContactLinkExchangeActivity) getActivity();
|
||||
}
|
||||
|
||||
private boolean isInputError() {
|
||||
boolean briarLink = isBriarLink(linkInput.getText());
|
||||
if (!briarLink) {
|
||||
linkInputLayout.setError(getString(R.string.invalid_link));
|
||||
linkInput.requestFocus();
|
||||
return true;
|
||||
} else linkInputLayout.setError(null);
|
||||
String link = getLink();
|
||||
boolean isOurLink = link != null && OUR_LINK.equals("briar://" + link);
|
||||
if (isOurLink) {
|
||||
linkInputLayout.setError(getString(R.string.own_link_error));
|
||||
linkInput.requestFocus();
|
||||
return true;
|
||||
} else linkInputLayout.setError(null);
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isBriarLink(@Nullable CharSequence s) {
|
||||
ContactLinkExchangeActivity activity = getCastActivity();
|
||||
return activity != null && activity.isBriarLink(s);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getLink() {
|
||||
Matcher matcher = LINK_REGEX.matcher(linkInput.getText());
|
||||
if (matcher.matches()) // needs to be called before groups become available
|
||||
return matcher.group(2);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private void onContinueButtonClicked() {
|
||||
ContactLinkExchangeActivity activity = getCastActivity();
|
||||
if (activity == null || isInputError()) return;
|
||||
|
||||
String linkText = getLink();
|
||||
if (linkText == null) throw new AssertionError();
|
||||
|
||||
BaseFragment f = ContactNicknameFragment.newInstance(linkText);
|
||||
activity.showNextFragment(f);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,22 +2,20 @@ package org.briarproject.briar.android.contact;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.util.Pair;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
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.contact.event.ContactAddedEvent;
|
||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||
import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
@@ -42,22 +40,15 @@ import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||
import org.briarproject.briar.api.conversation.ConversationManager;
|
||||
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
||||
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager.PendingContact;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
|
||||
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE;
|
||||
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
|
||||
import static android.support.v4.view.ViewCompat.getTransitionName;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
@@ -68,8 +59,7 @@ import static org.briarproject.briar.android.conversation.ConversationActivity.C
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class ContactListFragment extends BaseFragment implements EventListener,
|
||||
OnMenuItemClickListener {
|
||||
public class ContactListFragment extends BaseFragment implements EventListener {
|
||||
|
||||
public static final String TAG = ContactListFragment.class.getName();
|
||||
private static final Logger LOG = Logger.getLogger(TAG);
|
||||
@@ -81,13 +71,8 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
||||
@Inject
|
||||
AndroidNotificationManager notificationManager;
|
||||
|
||||
// TODO remove
|
||||
@Inject
|
||||
MessagingManager messagingManager;
|
||||
|
||||
private ContactListAdapter adapter;
|
||||
private BriarRecyclerView list;
|
||||
private Snackbar snackbar;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject
|
||||
@@ -120,12 +105,7 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
||||
|
||||
getActivity().setTitle(R.string.contact_list_button);
|
||||
|
||||
View contentView =
|
||||
inflater.inflate(R.layout.fragment_contact_list, container,
|
||||
false);
|
||||
|
||||
FabSpeedDial speedDialView = contentView.findViewById(R.id.speedDial);
|
||||
speedDialView.addOnMenuItemClickListener(this);
|
||||
View contentView = inflater.inflate(R.layout.list, container, false);
|
||||
|
||||
OnContactClickListener<ContactListItem> onContactClickListener =
|
||||
(view, item) -> {
|
||||
@@ -164,32 +144,26 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
||||
list.setEmptyText(getString(R.string.no_contacts));
|
||||
list.setEmptyAction(getString(R.string.no_contacts_action));
|
||||
|
||||
snackbar = Snackbar.make(contentView,
|
||||
R.string.pending_contact_requests_snackbar, LENGTH_INDEFINITE);
|
||||
snackbar.getView().setBackgroundResource(R.color.briar_primary);
|
||||
snackbar.setAction(R.string.show, v -> startActivity(
|
||||
new Intent(getContext(), PendingRequestsActivity.class)));
|
||||
snackbar.setActionTextColor(ContextCompat
|
||||
.getColor(getContext(), R.color.briar_button_text_positive));
|
||||
|
||||
return contentView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMenuItemClick(FloatingActionButton fab, @Nullable TextView v,
|
||||
int itemId) {
|
||||
switch (itemId) {
|
||||
case R.id.action_add_contact_nearby:
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.contact_list_actions, menu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle presses on the action bar items
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_add_contact:
|
||||
Intent intent =
|
||||
new Intent(getContext(), ContactExchangeActivity.class);
|
||||
startActivity(intent);
|
||||
return;
|
||||
case R.id.action_add_contact_remotely:
|
||||
startActivity(new Intent(getContext(),
|
||||
ContactLinkExchangeActivity.class));
|
||||
return;
|
||||
return true;
|
||||
default:
|
||||
return;
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,26 +175,6 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
||||
notificationManager.clearAllIntroductionNotifications();
|
||||
loadContacts();
|
||||
list.startPeriodicUpdate();
|
||||
|
||||
// TODO remove
|
||||
checkForPendingContacts();
|
||||
}
|
||||
|
||||
// TODO remove
|
||||
private void checkForPendingContacts() {
|
||||
listener.runOnDbThread(() -> {
|
||||
try {
|
||||
Collection<PendingContact> contacts =
|
||||
messagingManager.getPendingContacts();
|
||||
if (contacts.isEmpty()) {
|
||||
runOnUiThreadUnlessDestroyed(() -> snackbar.dismiss());
|
||||
} else {
|
||||
runOnUiThreadUnlessDestroyed(() -> snackbar.show());
|
||||
}
|
||||
} catch (DbException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -296,11 +250,6 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
||||
ConversationMessageHeader h = p.getMessageHeader();
|
||||
updateItem(p.getContactId(), h);
|
||||
}
|
||||
|
||||
// TODO remove
|
||||
else if (e instanceof ContactAddedEvent) {
|
||||
checkForPendingContacts();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateItem(ContactId c, ConversationMessageHeader h) {
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
package org.briarproject.briar.android.contact;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.MainThread;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.design.widget.TextInputEditText;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.support.v4.graphics.drawable.DrawableCompat.setTint;
|
||||
import static android.support.v4.graphics.drawable.DrawableCompat.wrap;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
|
||||
|
||||
public class ContactNicknameFragment extends BaseFragment {
|
||||
|
||||
static final String TAG = ContactNicknameFragment.class.getName();
|
||||
|
||||
static BaseFragment newInstance(@Nullable String link) {
|
||||
BaseFragment f = new ContactNicknameFragment();
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("link", link);
|
||||
f.setArguments(bundle);
|
||||
return f;
|
||||
}
|
||||
|
||||
private TextInputLayout contactNameLayout;
|
||||
private TextInputEditText contactNameInput;
|
||||
|
||||
@Nullable
|
||||
private String link;
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
if (getActivity() == null || getContext() == null) return null;
|
||||
|
||||
getActivity().setTitle(R.string.add_contact_remotely_title_case);
|
||||
|
||||
link = requireNonNull(getArguments()).getString("link");
|
||||
Log.e(TAG, link);
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_contact_choose_nickname,
|
||||
container, false);
|
||||
|
||||
int color =
|
||||
resolveColorAttribute(getContext(), R.attr.colorControlNormal);
|
||||
|
||||
Button addButton = v.findViewById(R.id.addButton);
|
||||
addButton.setOnClickListener(view -> onAddButtonClicked());
|
||||
|
||||
contactNameLayout = v.findViewById(R.id.contactNameLayout);
|
||||
contactNameInput = v.findViewById(R.id.contactNameInput);
|
||||
if (SDK_INT < 23) {
|
||||
Drawable drawable = wrap(contactNameInput.getCompoundDrawables()[0]);
|
||||
setTint(drawable, color);
|
||||
contactNameInput.setCompoundDrawables(drawable, null, null, null);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
private ContactLinkExchangeActivity getCastActivity() {
|
||||
return (ContactLinkExchangeActivity) getActivity();
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@UiThread
|
||||
private boolean isInputError() {
|
||||
boolean validContactName = contactNameInput.getText() != null &&
|
||||
contactNameInput.getText().toString().trim().length() > 0;
|
||||
if (!validContactName) {
|
||||
contactNameLayout.setError(getString(R.string.nickname_missing));
|
||||
contactNameInput.requestFocus();
|
||||
return true;
|
||||
} else contactNameLayout.setError(null);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onAddButtonClicked() {
|
||||
ContactLinkExchangeActivity activity = getCastActivity();
|
||||
if (activity == null || isInputError()) return;
|
||||
|
||||
String name = requireNonNull(contactNameInput.getText()).toString();
|
||||
if (link == null) throw new AssertionError();
|
||||
|
||||
activity.addFakeRequest(name, link);
|
||||
|
||||
Intent intent = new Intent(activity, PendingRequestsActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
package org.briarproject.briar.android.contact;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.zxing.Result;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.keyagreement.CameraException;
|
||||
import org.briarproject.briar.android.keyagreement.CameraView;
|
||||
import org.briarproject.briar.android.keyagreement.QrCodeDecoder;
|
||||
import org.briarproject.briar.android.util.UiUtils;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.Manifest.permission.CAMERA;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION;
|
||||
|
||||
public class ContactQrCodeInputFragment extends BaseFragment
|
||||
implements QrCodeDecoder.ResultCallback {
|
||||
|
||||
static final String TAG = ContactQrCodeInputFragment.class.getName();
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(TAG);
|
||||
|
||||
private CameraView cameraView;
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
if (getActivity() == null) throw new AssertionError();
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
|
||||
cameraView.setPreviewConsumer(new QrCodeDecoder(this));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
if (getActivity() == null) return null;
|
||||
|
||||
getActivity().setTitle(R.string.scan_qr_code_title);
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_contact_qr_code_input,
|
||||
container, false);
|
||||
|
||||
cameraView = v.findViewById(R.id.camera_view);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
if (checkPermissions()) {
|
||||
startCamera();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
try {
|
||||
cameraView.stop();
|
||||
} catch (CameraException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void startCamera() {
|
||||
try {
|
||||
cameraView.start();
|
||||
} catch (CameraException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
Toast.makeText(getContext(), R.string.camera_error_toast,
|
||||
LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkPermissions() {
|
||||
if (getContext() == null) return false;
|
||||
if (ActivityCompat.checkSelfPermission(getContext(), CAMERA) !=
|
||||
PERMISSION_GRANTED) {
|
||||
// Should we show an explanation?
|
||||
if (shouldShowRequestPermissionRationale(CAMERA)) {
|
||||
DialogInterface.OnClickListener continueListener =
|
||||
(dialog, which) -> requestPermission();
|
||||
AlertDialog.Builder
|
||||
builder = new AlertDialog.Builder(getContext(),
|
||||
R.style.BriarDialogTheme);
|
||||
builder.setTitle(R.string.permission_camera_title);
|
||||
builder.setMessage(R.string.permission_camera_request_body);
|
||||
builder.setNeutralButton(R.string.continue_button,
|
||||
continueListener);
|
||||
builder.show();
|
||||
} else {
|
||||
requestPermission();
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode,
|
||||
@NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
if (getContext() == null) return;
|
||||
if (requestCode == REQUEST_PERMISSION_CAMERA_LOCATION) {
|
||||
// If request is cancelled, the result arrays are empty.
|
||||
if (grantResults.length > 0 &&
|
||||
grantResults[0] == PERMISSION_GRANTED) {
|
||||
startCamera();
|
||||
} else {
|
||||
if (!shouldShowRequestPermissionRationale(CAMERA)) {
|
||||
// The user has permanently denied the request
|
||||
AlertDialog.Builder
|
||||
builder = new AlertDialog.Builder(getContext(),
|
||||
R.style.BriarDialogTheme);
|
||||
builder.setTitle(R.string.permission_camera_title);
|
||||
builder.setMessage(R.string.permission_camera_denied_body);
|
||||
builder.setPositiveButton(R.string.ok,
|
||||
UiUtils.getGoToSettingsListener(getContext()));
|
||||
builder.setNegativeButton(R.string.cancel,
|
||||
(dialog, which) -> cancel());
|
||||
builder.show();
|
||||
} else {
|
||||
Toast.makeText(getContext(),
|
||||
R.string.permission_camera_denied_body,
|
||||
LENGTH_LONG).show();
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void requestPermission() {
|
||||
requestPermissions(new String[] {CAMERA}, REQUEST_PERMISSION_CAMERA_LOCATION);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ContactLinkExchangeActivity getCastActivity() {
|
||||
return (ContactLinkExchangeActivity) getActivity();
|
||||
}
|
||||
|
||||
private void cancel() {
|
||||
ContactLinkExchangeActivity activity = getCastActivity();
|
||||
if (activity != null) activity.linkScanned(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult(@NonNull Result result) {
|
||||
LOG.info("Scanned link: " + result.getText());
|
||||
ContactLinkExchangeActivity activity = getCastActivity();
|
||||
if (activity != null) activity.linkScanned(result.getText());
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package org.briarproject.briar.android.contact;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.view.QrCodeView;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.briar.android.contact.ContactLinkExchangeActivity.OUR_LINK;
|
||||
import static org.briarproject.briar.android.keyagreement.QrCodeUtils.createQrCode;
|
||||
|
||||
public class ContactQrCodeOutputFragment extends BaseFragment {
|
||||
|
||||
static final String TAG = ContactQrCodeOutputFragment.class.getName();
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
if (getActivity() == null) return null;
|
||||
|
||||
getActivity().setTitle(R.string.show_qr_code_title);
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_contact_qr_code_output,
|
||||
container, false);
|
||||
|
||||
DisplayMetrics dm = getResources().getDisplayMetrics();
|
||||
Bitmap qrCode = createQrCode(dm, OUR_LINK);
|
||||
QrCodeView qrCodeView = v.findViewById(R.id.qrCodeView);
|
||||
qrCodeView.setQrCode(qrCode);
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
package org.briarproject.briar.android.contact;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager.PendingContact;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
public class PendingRequestsActivity extends BriarActivity
|
||||
implements EventListener {
|
||||
|
||||
@Inject
|
||||
MessagingManager messagingManager;
|
||||
@Inject
|
||||
ContactManager contactManager;
|
||||
@Inject
|
||||
EventBus eventBus;
|
||||
|
||||
private PendingRequestsAdapter adapter;
|
||||
private BriarRecyclerView list;
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
setContentView(R.layout.list);
|
||||
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
adapter = new PendingRequestsAdapter(this, PendingContact.class);
|
||||
list = findViewById(R.id.list);
|
||||
list.setLayoutManager(new LinearLayoutManager(this));
|
||||
list.setAdapter(adapter);
|
||||
list.setPERIODIC_UPDATE_MILLIS(SECONDS.toMillis(9));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
eventBus.addListener(this);
|
||||
list.startPeriodicUpdate();
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
Collection<PendingContact> contacts =
|
||||
messagingManager.getPendingContacts();
|
||||
addPendingContacts(contacts);
|
||||
} catch (DbException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
list.stopPeriodicUpdate();
|
||||
adapter.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof ContactAddedEvent) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
Contact contact = contactManager
|
||||
.getContact(((ContactAddedEvent) e).getContactId());
|
||||
runOnUiThreadUnlessDestroyed(() -> {
|
||||
adapter.remove(contact);
|
||||
if (adapter.isEmpty()) finish();
|
||||
});
|
||||
} catch (DbException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void addPendingContacts(Collection<PendingContact> contacts) {
|
||||
runOnUiThreadUnlessDestroyed(() -> {
|
||||
if (contacts.isEmpty()) {
|
||||
list.showData();
|
||||
} else {
|
||||
adapter.addAll(contacts);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package org.briarproject.briar.android.contact;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.util.BriarAdapter;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager.PendingContact;
|
||||
|
||||
@NotNullByDefault
|
||||
public class PendingRequestsAdapter extends
|
||||
BriarAdapter<PendingContact, PendingRequestsViewHolder> {
|
||||
|
||||
public PendingRequestsAdapter(Context ctx, Class<PendingContact> c) {
|
||||
super(ctx, c);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public PendingRequestsViewHolder onCreateViewHolder(
|
||||
ViewGroup viewGroup, int i) {
|
||||
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
|
||||
R.layout.list_item_pending_contact, viewGroup, false);
|
||||
return new PendingRequestsViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(
|
||||
PendingRequestsViewHolder pendingRequestsViewHolder, int i) {
|
||||
pendingRequestsViewHolder.bind(items.get(i));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(PendingContact item1, PendingContact item2) {
|
||||
return (int) (item1.getTimestamp() - item2.getTimestamp());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(PendingContact item1,
|
||||
PendingContact item2) {
|
||||
return item1.getName().equals(item2.getName()) &&
|
||||
item1.getTimestamp() == item2.getTimestamp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(PendingContact item1,
|
||||
PendingContact item2) {
|
||||
return item1.getName().equals(item2.getName()) &&
|
||||
item1.getTimestamp() == item2.getTimestamp();
|
||||
}
|
||||
|
||||
// TODO remove
|
||||
public void remove(Contact contact) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (items.get(i).getName().equals(contact.getAuthor().getName())) {
|
||||
items.removeItemAt(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package org.briarproject.briar.android.contact;
|
||||
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.widget.RecyclerView.ViewHolder;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.view.TextAvatarView;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager.PendingContact;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.briarproject.bramble.util.StringUtils.toUtf8;
|
||||
import static org.briarproject.briar.android.util.UiUtils.formatDate;
|
||||
|
||||
@NotNullByDefault
|
||||
public class PendingRequestsViewHolder extends ViewHolder {
|
||||
|
||||
private final TextAvatarView avatar;
|
||||
private final TextView name;
|
||||
private final TextView time;
|
||||
private final TextView status;
|
||||
|
||||
public PendingRequestsViewHolder(View v) {
|
||||
super(v);
|
||||
avatar = v.findViewById(R.id.avatar);
|
||||
name = v.findViewById(R.id.name);
|
||||
time = v.findViewById(R.id.time);
|
||||
status = v.findViewById(R.id.status);
|
||||
}
|
||||
|
||||
public void bind(PendingContact item) {
|
||||
avatar.setText(item.getName());
|
||||
avatar.setBackgroundBytes(toUtf8(item.getName() + item.getTimestamp()));
|
||||
name.setText(item.getName());
|
||||
time.setText(formatDate(time.getContext(), item.getTimestamp()));
|
||||
long diff = item.getAddAt() - System.currentTimeMillis();
|
||||
Log.e("TEST", "diff: " + diff);
|
||||
int color = ContextCompat
|
||||
.getColor(status.getContext(), R.color.briar_green);
|
||||
if (diff < SECONDS.toMillis(10)) {
|
||||
status.setText(R.string.adding_contact);
|
||||
} else if (diff < SECONDS.toMillis(20)) {
|
||||
status.setText(R.string.connecting);
|
||||
} else if (diff < SECONDS.toMillis(30)) {
|
||||
status.setText(R.string.waiting_for_contact_to_come_online);
|
||||
color = ContextCompat
|
||||
.getColor(status.getContext(), R.color.briar_yellow);
|
||||
}
|
||||
status.setTextColor(color);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import org.briarproject.briar.api.messaging.Attachment;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
@@ -90,22 +91,44 @@ class AttachmentController {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link AttachmentItem}s from the passed headers and Attachments.
|
||||
*
|
||||
* Note: This closes the {@link Attachment}'s {@link InputStream}.
|
||||
*/
|
||||
List<AttachmentItem> getAttachmentItems(
|
||||
List<Pair<AttachmentHeader, Attachment>> attachments) {
|
||||
boolean needsSize = attachments.size() == 1;
|
||||
List<AttachmentItem> items = new ArrayList<>(attachments.size());
|
||||
for (Pair<AttachmentHeader, Attachment> a : attachments) {
|
||||
AttachmentItem item =
|
||||
getAttachmentItem(a.getFirst(), a.getSecond());
|
||||
getAttachmentItem(a.getFirst(), a.getSecond(), needsSize);
|
||||
items.add(item);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
private AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a) {
|
||||
/**
|
||||
* Creates an {@link AttachmentItem} from the {@link Attachment}'s
|
||||
* {@link InputStream} which will be closed when this method returns.
|
||||
*/
|
||||
AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a,
|
||||
boolean needsSize) {
|
||||
MessageId messageId = h.getMessageId();
|
||||
Size size = new Size();
|
||||
if (!needsSize) {
|
||||
String mimeType = h.getContentType();
|
||||
String extension = getExtensionFromMimeType(mimeType);
|
||||
boolean hasError = false;
|
||||
if (extension == null) {
|
||||
extension = "";
|
||||
hasError = true;
|
||||
}
|
||||
return new AttachmentItem(messageId, 0, 0, mimeType, extension, 0,
|
||||
0, hasError);
|
||||
}
|
||||
|
||||
InputStream is = a.getStream();
|
||||
Size size = new Size();
|
||||
InputStream is = new BufferedInputStream(a.getStream());
|
||||
is.mark(Integer.MAX_VALUE);
|
||||
try {
|
||||
// use exif to get size
|
||||
@@ -134,14 +157,19 @@ class AttachmentController {
|
||||
getThumbnailSize(size.width, size.height, size.mimeType);
|
||||
}
|
||||
// get file extension
|
||||
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
||||
String extension = mimeTypeMap.getExtensionFromMimeType(size.mimeType);
|
||||
String extension = getExtensionFromMimeType(size.mimeType);
|
||||
if (extension == null) {
|
||||
return new AttachmentItem(messageId, 0, 0, "", "", 0, 0, true);
|
||||
}
|
||||
return new AttachmentItem(messageId, size.width, size.height,
|
||||
size.mimeType, extension, thumbnailSize.width, thumbnailSize.height,
|
||||
size.error);
|
||||
size.mimeType, extension, thumbnailSize.width,
|
||||
thumbnailSize.height, size.error);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getExtensionFromMimeType(String mimeType) {
|
||||
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
||||
return mimeTypeMap.getExtensionFromMimeType(mimeType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,7 @@ 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;
|
||||
@@ -111,4 +112,10 @@ public class AttachmentItem implements Parcelable {
|
||||
dest.writeByte((byte) (hasError ? 1 : 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
return o instanceof AttachmentItem &&
|
||||
messageId.equals(((AttachmentItem) o).messageId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -29,7 +30,6 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.Pair;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
@@ -49,7 +49,6 @@ 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.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
||||
@@ -83,7 +82,6 @@ import org.briarproject.briar.api.introduction.IntroductionManager;
|
||||
import org.briarproject.briar.api.messaging.Attachment;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
||||
@@ -110,7 +108,6 @@ import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAn
|
||||
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;
|
||||
@@ -124,7 +121,8 @@ 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;
|
||||
@@ -197,7 +195,7 @@ public class ConversationActivity extends BriarActivity
|
||||
|
||||
private volatile ContactId contactId;
|
||||
@Nullable
|
||||
private volatile GroupId messagingGroupId;
|
||||
private Parcelable layoutManagerState;
|
||||
|
||||
private final Observer<String> contactNameObserver = name -> {
|
||||
requireNonNull(name);
|
||||
@@ -245,6 +243,8 @@ public class ConversationActivity extends BriarActivity
|
||||
requireNonNull(deleted);
|
||||
if (deleted) finish();
|
||||
});
|
||||
viewModel.getAddedPrivateMessage()
|
||||
.observe(this, this::onAddedPrivateMessage);
|
||||
|
||||
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
|
||||
setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
|
||||
@@ -311,6 +311,21 @@ 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
|
||||
@@ -397,7 +412,7 @@ public class ConversationActivity extends BriarActivity
|
||||
textCache.put(id, text);
|
||||
}
|
||||
}
|
||||
if (!h.getAttachmentHeaders().isEmpty()) {
|
||||
if (h.getAttachmentHeaders().size() == 1) {
|
||||
List<AttachmentItem> items =
|
||||
attachmentController.get(id);
|
||||
if (items == null) {
|
||||
@@ -430,8 +445,12 @@ public class ConversationActivity extends BriarActivity
|
||||
List<ConversationItem> items = createItems(headers);
|
||||
adapter.addAll(items);
|
||||
list.showData();
|
||||
// Scroll to the bottom
|
||||
list.scrollToPosition(adapter.getItemCount() - 1);
|
||||
if (layoutManagerState == null) {
|
||||
// Scroll to the bottom
|
||||
list.scrollToPosition(adapter.getItemCount() - 1);
|
||||
} else {
|
||||
layoutManager.onRestoreInstanceState(layoutManagerState);
|
||||
}
|
||||
} else {
|
||||
LOG.info("Concurrent update, reloading");
|
||||
loadMessages();
|
||||
@@ -473,7 +492,8 @@ public class ConversationActivity extends BriarActivity
|
||||
adapter.getMessageItem(m);
|
||||
if (pair != null) {
|
||||
pair.getSecond().setText(text);
|
||||
boolean bottom = adapter.isScrolledToBottom(layoutManager);
|
||||
boolean bottom = layoutManagerState == null &&
|
||||
adapter.isScrolledToBottom(layoutManager);
|
||||
adapter.notifyItemChanged(pair.getFirst());
|
||||
if (bottom) list.scrollToPosition(adapter.getItemCount() - 1);
|
||||
}
|
||||
@@ -486,7 +506,7 @@ public class ConversationActivity extends BriarActivity
|
||||
try {
|
||||
List<Pair<AttachmentHeader, Attachment>> attachments =
|
||||
attachmentController.getMessageAttachments(headers);
|
||||
// TODO move getting the items off to the IoExecutor
|
||||
// TODO move getting the items off to IoExecutor, if size == 1
|
||||
List<AttachmentItem> items =
|
||||
attachmentController.getAttachmentItems(attachments);
|
||||
displayMessageAttachments(messageId, items);
|
||||
@@ -504,7 +524,8 @@ public class ConversationActivity extends BriarActivity
|
||||
adapter.getMessageItem(m);
|
||||
if (pair != null) {
|
||||
pair.getSecond().setAttachments(items);
|
||||
boolean bottom = adapter.isScrolledToBottom(layoutManager);
|
||||
boolean bottom = layoutManagerState == null &&
|
||||
adapter.isScrolledToBottom(layoutManager);
|
||||
adapter.notifyItemChanged(pair.getFirst());
|
||||
if (bottom) list.scrollToPosition(adapter.getItemCount() - 1);
|
||||
}
|
||||
@@ -555,7 +576,8 @@ public class ConversationActivity extends BriarActivity
|
||||
|
||||
private void addConversationItem(ConversationItem item) {
|
||||
runOnUiThreadUnlessDestroyed(() -> {
|
||||
boolean bottom = adapter.isScrolledToBottom(layoutManager);
|
||||
boolean bottom = layoutManagerState == null &&
|
||||
adapter.isScrolledToBottom(layoutManager);
|
||||
adapter.incrementRevision();
|
||||
adapter.add(item);
|
||||
if (bottom) list.scrollToPosition(adapter.getItemCount() - 1);
|
||||
@@ -600,16 +622,11 @@ public class ConversationActivity extends BriarActivity
|
||||
|
||||
@Override
|
||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
||||
if (!imageUris.isEmpty()) {
|
||||
Toast.makeText(this, "Not yet implemented.", LENGTH_LONG).show();
|
||||
textInputView.clearText();
|
||||
return;
|
||||
}
|
||||
if (isNullOrEmpty(text)) throw new AssertionError();
|
||||
if (isNullOrEmpty(text) && imageUris.isEmpty())
|
||||
throw new AssertionError();
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
|
||||
if (messagingGroupId == null) loadGroupId(text, timestamp);
|
||||
else createMessage(text, timestamp);
|
||||
viewModel.sendMessage(text, imageUris, timestamp);
|
||||
textInputView.clearText();
|
||||
}
|
||||
|
||||
@@ -619,48 +636,10 @@ public class ConversationActivity extends BriarActivity
|
||||
return item == null ? 0 : item.getTime() + 1;
|
||||
}
|
||||
|
||||
private void loadGroupId(String text, long timestamp) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
messagingGroupId =
|
||||
messagingManager.getConversationId(contactId);
|
||||
createMessage(text, timestamp);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void createMessage(String text, long timestamp) {
|
||||
cryptoExecutor.execute(() -> {
|
||||
try {
|
||||
//noinspection ConstantConditions init in loadGroupId()
|
||||
storeMessage(privateMessageFactory.createPrivateMessage(
|
||||
messagingGroupId, timestamp, text, emptyList()), text);
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void storeMessage(PrivateMessage m, String text) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
long start = now();
|
||||
messagingManager.addLocalMessage(m);
|
||||
logDuration(LOG, "Storing message", start);
|
||||
Message message = m.getMessage();
|
||||
PrivateMessageHeader h = new PrivateMessageHeader(
|
||||
message.getId(), message.getGroupId(),
|
||||
message.getTimestamp(), true, false, false, false,
|
||||
true, emptyList());
|
||||
textCache.put(message.getId(), text);
|
||||
addConversationItem(h.accept(visitor));
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
});
|
||||
private void onAddedPrivateMessage(@Nullable PrivateMessageHeader h) {
|
||||
if (h == null) return;
|
||||
addConversationItem(h.accept(visitor));
|
||||
viewModel.onAddedPrivateMessageSeen();
|
||||
}
|
||||
|
||||
private void askToRemoveContact() {
|
||||
@@ -858,19 +837,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
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.android.conversation;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView.RecycledViewPool;
|
||||
import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -21,11 +22,17 @@ class ConversationAdapter
|
||||
extends BriarAdapter<ConversationItem, ConversationItemViewHolder> {
|
||||
|
||||
private ConversationListener listener;
|
||||
private final RecycledViewPool imageViewPool;
|
||||
private final ImageItemDecoration imageItemDecoration;
|
||||
|
||||
ConversationAdapter(Context ctx,
|
||||
ConversationListener conversationListener) {
|
||||
super(ctx, ConversationItem.class);
|
||||
listener = conversationListener;
|
||||
// This shares the same pool for view recycling between all image lists
|
||||
imageViewPool = new RecycledViewPool();
|
||||
// Share the item decoration as well
|
||||
imageItemDecoration = new ImageItemDecoration(ctx);
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
@@ -42,15 +49,17 @@ class ConversationAdapter
|
||||
type, viewGroup, false);
|
||||
switch (type) {
|
||||
case R.layout.list_item_conversation_msg_in:
|
||||
return new ConversationMessageViewHolder(v, true);
|
||||
return new ConversationMessageViewHolder(v, listener, true,
|
||||
imageViewPool, imageItemDecoration);
|
||||
case R.layout.list_item_conversation_msg_out:
|
||||
return new ConversationMessageViewHolder(v, false);
|
||||
return new ConversationMessageViewHolder(v, listener, false,
|
||||
imageViewPool, imageItemDecoration);
|
||||
case R.layout.list_item_conversation_notice_in:
|
||||
return new ConversationNoticeViewHolder(v, true);
|
||||
return new ConversationNoticeViewHolder(v, listener, true);
|
||||
case R.layout.list_item_conversation_notice_out:
|
||||
return new ConversationNoticeViewHolder(v, false);
|
||||
return new ConversationNoticeViewHolder(v, listener, false);
|
||||
case R.layout.list_item_conversation_request:
|
||||
return new ConversationRequestViewHolder(v, true);
|
||||
return new ConversationRequestViewHolder(v, listener, true);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown ConversationItem");
|
||||
}
|
||||
@@ -59,7 +68,7 @@ class ConversationAdapter
|
||||
@Override
|
||||
public void onBindViewHolder(ConversationItemViewHolder ui, int position) {
|
||||
ConversationItem item = items.get(position);
|
||||
ui.bind(item, listener);
|
||||
ui.bind(item);
|
||||
listener.onItemVisible(item);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,14 +18,17 @@ import static org.briarproject.briar.android.util.UiUtils.formatDate;
|
||||
@NotNullByDefault
|
||||
abstract class ConversationItemViewHolder extends ViewHolder {
|
||||
|
||||
protected final ConversationListener listener;
|
||||
protected final ConstraintLayout layout;
|
||||
@Nullable
|
||||
private final OutItemViewHolder outViewHolder;
|
||||
private final TextView text;
|
||||
protected final TextView time;
|
||||
|
||||
ConversationItemViewHolder(View v, boolean isIncoming) {
|
||||
ConversationItemViewHolder(View v, ConversationListener listener,
|
||||
boolean isIncoming) {
|
||||
super(v);
|
||||
this.listener = listener;
|
||||
this.outViewHolder = isIncoming ? null : new OutItemViewHolder(v);
|
||||
layout = v.findViewById(R.id.layout);
|
||||
text = v.findViewById(R.id.text);
|
||||
@@ -33,7 +36,7 @@ abstract class ConversationItemViewHolder extends ViewHolder {
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
void bind(ConversationItem item, ConversationListener listener) {
|
||||
void bind(ConversationItem item) {
|
||||
if (item.getText() != null) {
|
||||
text.setText(trim(item.getText()));
|
||||
}
|
||||
|
||||
@@ -1,63 +1,45 @@
|
||||
package org.briarproject.briar.android.conversation;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.constraint.ConstraintSet;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.RecyclerView.RecycledViewPool;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.load.Transformation;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.conversation.glide.BriarImageTransformation;
|
||||
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.support.v4.view.ViewCompat.LAYOUT_DIRECTION_RTL;
|
||||
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
|
||||
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
|
||||
import static android.support.constraint.ConstraintSet.WRAP_CONTENT;
|
||||
import static android.support.v4.content.ContextCompat.getColor;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
class ConversationMessageViewHolder extends ConversationItemViewHolder {
|
||||
|
||||
@DrawableRes
|
||||
private static final int ERROR_RES = R.drawable.ic_image_broken;
|
||||
|
||||
private final ImageView imageView;
|
||||
private final ImageAdapter adapter;
|
||||
private final ViewGroup statusLayout;
|
||||
private final int timeColor, timeColorBubble;
|
||||
private final int radiusBig, radiusSmall;
|
||||
private final boolean isRtl;
|
||||
private final ConstraintSet textConstraints = new ConstraintSet();
|
||||
private final ConstraintSet imageConstraints = new ConstraintSet();
|
||||
private final ConstraintSet imageTextConstraints = new ConstraintSet();
|
||||
|
||||
ConversationMessageViewHolder(View v, boolean isIncoming) {
|
||||
super(v, isIncoming);
|
||||
imageView = v.findViewById(R.id.imageView);
|
||||
ConversationMessageViewHolder(View v, ConversationListener listener,
|
||||
boolean isIncoming, RecycledViewPool imageViewPool,
|
||||
ImageItemDecoration imageItemDecoration) {
|
||||
super(v, listener, isIncoming);
|
||||
statusLayout = v.findViewById(R.id.statusLayout);
|
||||
radiusBig = v.getContext().getResources()
|
||||
.getDimensionPixelSize(R.dimen.message_bubble_radius_big);
|
||||
radiusSmall = v.getContext().getResources()
|
||||
.getDimensionPixelSize(R.dimen.message_bubble_radius_small);
|
||||
|
||||
// image list
|
||||
RecyclerView list = v.findViewById(R.id.imageList);
|
||||
list.setRecycledViewPool(imageViewPool);
|
||||
adapter = new ImageAdapter(v.getContext(), listener);
|
||||
list.setAdapter(adapter);
|
||||
list.addItemDecoration(imageItemDecoration);
|
||||
|
||||
// remember original status text color
|
||||
timeColor = time.getCurrentTextColor();
|
||||
timeColorBubble =
|
||||
ContextCompat.getColor(v.getContext(), R.color.briar_white);
|
||||
|
||||
// find out if we are showing a RTL language, Use the configuration,
|
||||
// because getting the layout direction of views is not reliable
|
||||
Configuration config =
|
||||
imageView.getContext().getResources().getConfiguration();
|
||||
isRtl = SDK_INT >= 17 &&
|
||||
config.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
|
||||
timeColorBubble = getColor(v.getContext(), R.color.briar_white);
|
||||
|
||||
// clone constraint sets from layout files
|
||||
textConstraints
|
||||
@@ -77,85 +59,55 @@ class ConversationMessageViewHolder extends ConversationItemViewHolder {
|
||||
}
|
||||
|
||||
@Override
|
||||
void bind(ConversationItem conversationItem,
|
||||
ConversationListener listener) {
|
||||
super.bind(conversationItem, listener);
|
||||
void bind(ConversationItem conversationItem) {
|
||||
super.bind(conversationItem);
|
||||
ConversationMessageItem item =
|
||||
(ConversationMessageItem) conversationItem;
|
||||
if (item.getAttachments().isEmpty()) {
|
||||
bindTextItem();
|
||||
} else {
|
||||
bindImageItem(item, listener);
|
||||
bindImageItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void bindTextItem() {
|
||||
clearImage();
|
||||
resetStatusLayoutForText();
|
||||
textConstraints.applyTo(layout);
|
||||
adapter.clear();
|
||||
}
|
||||
|
||||
private void bindImageItem(ConversationMessageItem item) {
|
||||
ConstraintSet constraintSet;
|
||||
if (item.getText() == null) {
|
||||
statusLayout.setBackgroundResource(R.drawable.msg_status_bubble);
|
||||
time.setTextColor(timeColorBubble);
|
||||
constraintSet = imageConstraints;
|
||||
} else {
|
||||
resetStatusLayoutForText();
|
||||
constraintSet = imageTextConstraints;
|
||||
}
|
||||
|
||||
if (item.getAttachments().size() == 1) {
|
||||
// apply image size constraints for a single image
|
||||
AttachmentItem attachment = item.getAttachments().get(0);
|
||||
int width = attachment.getThumbnailWidth();
|
||||
int height = attachment.getThumbnailHeight();
|
||||
constraintSet.constrainWidth(R.id.imageList, width);
|
||||
constraintSet.constrainHeight(R.id.imageList, height);
|
||||
} else {
|
||||
// bubble adapts to size of image list
|
||||
constraintSet.constrainWidth(R.id.imageList, WRAP_CONTENT);
|
||||
constraintSet.constrainHeight(R.id.imageList, WRAP_CONTENT);
|
||||
}
|
||||
constraintSet.applyTo(layout);
|
||||
adapter.setConversationItem(item);
|
||||
}
|
||||
|
||||
private void resetStatusLayoutForText() {
|
||||
statusLayout.setBackgroundResource(0);
|
||||
// also reset padding (the background drawable defines some)
|
||||
statusLayout.setPadding(0, 0, 0, 0);
|
||||
time.setTextColor(timeColor);
|
||||
textConstraints.applyTo(layout);
|
||||
}
|
||||
|
||||
private void bindImageItem(ConversationMessageItem item,
|
||||
ConversationListener listener) {
|
||||
// TODO show more than just the first image
|
||||
AttachmentItem attachment = item.getAttachments().get(0);
|
||||
|
||||
ConstraintSet constraintSet;
|
||||
if (item.getText() == null) {
|
||||
statusLayout
|
||||
.setBackgroundResource(R.drawable.msg_status_bubble);
|
||||
time.setTextColor(timeColorBubble);
|
||||
constraintSet = imageConstraints;
|
||||
} else {
|
||||
statusLayout.setBackgroundResource(0);
|
||||
// also reset padding (the background drawable defines some)
|
||||
statusLayout.setPadding(0, 0, 0, 0);
|
||||
time.setTextColor(timeColor);
|
||||
constraintSet = imageTextConstraints;
|
||||
}
|
||||
|
||||
// apply image size constraints, so glides picks them up for scaling
|
||||
int width = attachment.getThumbnailWidth();
|
||||
int height = attachment.getThumbnailHeight();
|
||||
constraintSet.constrainWidth(R.id.imageView, width);
|
||||
constraintSet.constrainHeight(R.id.imageView, height);
|
||||
constraintSet.applyTo(layout);
|
||||
|
||||
if (attachment.hasError()) {
|
||||
clearImage();
|
||||
imageView.setImageResource(ERROR_RES);
|
||||
} else {
|
||||
loadImage(item, attachment, listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearImage() {
|
||||
GlideApp.with(imageView)
|
||||
.clear(imageView);
|
||||
imageView.setOnClickListener(null);
|
||||
}
|
||||
|
||||
private void loadImage(ConversationMessageItem item,
|
||||
AttachmentItem attachment, ConversationListener listener) {
|
||||
boolean leftCornerSmall =
|
||||
(isIncoming() && !isRtl) || (!isIncoming() && isRtl);
|
||||
boolean bottomRound = item.getText() == null;
|
||||
Transformation<Bitmap> transformation = new BriarImageTransformation(
|
||||
radiusSmall, radiusBig, leftCornerSmall, bottomRound);
|
||||
|
||||
GlideApp.with(imageView)
|
||||
.load(attachment)
|
||||
.diskCacheStrategy(NONE)
|
||||
.error(ERROR_RES)
|
||||
.transform(transformation)
|
||||
.transition(withCrossFade())
|
||||
.into(imageView)
|
||||
.waitForLayout();
|
||||
imageView.setOnClickListener(
|
||||
view -> listener.onAttachmentClicked(view, item, attachment));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,16 +19,17 @@ class ConversationNoticeViewHolder extends ConversationItemViewHolder {
|
||||
|
||||
private final TextView msgText;
|
||||
|
||||
ConversationNoticeViewHolder(View v, boolean isIncoming) {
|
||||
super(v, isIncoming);
|
||||
ConversationNoticeViewHolder(View v, ConversationListener listener,
|
||||
boolean isIncoming) {
|
||||
super(v, listener, isIncoming);
|
||||
msgText = v.findViewById(R.id.msgText);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
void bind(ConversationItem item, ConversationListener listener) {
|
||||
void bind(ConversationItem item) {
|
||||
ConversationNoticeItem notice = (ConversationNoticeItem) item;
|
||||
super.bind(notice, listener);
|
||||
super.bind(notice);
|
||||
|
||||
String text = notice.getMsgText();
|
||||
if (isNullOrEmpty(text)) {
|
||||
|
||||
@@ -17,16 +17,17 @@ class ConversationRequestViewHolder extends ConversationNoticeViewHolder {
|
||||
private final Button acceptButton;
|
||||
private final Button declineButton;
|
||||
|
||||
ConversationRequestViewHolder(View v, boolean isIncoming) {
|
||||
super(v, isIncoming);
|
||||
ConversationRequestViewHolder(View v, ConversationListener listener,
|
||||
boolean isIncoming) {
|
||||
super(v, listener, isIncoming);
|
||||
acceptButton = v.findViewById(R.id.acceptButton);
|
||||
declineButton = v.findViewById(R.id.declineButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
void bind(ConversationItem item, ConversationListener listener) {
|
||||
void bind(ConversationItem item) {
|
||||
ConversationRequestItem request = (ConversationRequestItem) item;
|
||||
super.bind(request, listener);
|
||||
super.bind(request);
|
||||
|
||||
if (request.wasAnswered() && request.canBeOpened()) {
|
||||
acceptButton.setVisibility(VISIBLE);
|
||||
|
||||
@@ -5,19 +5,36 @@ import android.arch.lifecycle.AndroidViewModel;
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.lifecycle.MutableLiveData;
|
||||
import android.arch.lifecycle.Transformations;
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.Pair;
|
||||
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.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.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.briar.android.util.UiUtils;
|
||||
import org.briarproject.briar.api.messaging.Attachment;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -26,9 +43,11 @@ import javax.inject.Inject;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
||||
|
||||
@NotNullByDefault
|
||||
public class ConversationViewModel extends AndroidViewModel {
|
||||
@@ -38,7 +57,11 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
|
||||
@DatabaseExecutor
|
||||
private final Executor dbExecutor;
|
||||
@CryptoExecutor
|
||||
private final Executor cryptoExecutor;
|
||||
private final MessagingManager messagingManager;
|
||||
private final ContactManager contactManager;
|
||||
private final PrivateMessageFactory privateMessageFactory;
|
||||
private final AttachmentController attachmentController;
|
||||
|
||||
@Nullable
|
||||
@@ -50,14 +73,24 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
Transformations.map(contact, UiUtils::getContactDisplayName);
|
||||
private final MutableLiveData<Boolean> contactDeleted =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<GroupId> messagingGroupId =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<PrivateMessageHeader> addedHeader =
|
||||
new MutableLiveData<>();
|
||||
|
||||
@Inject
|
||||
ConversationViewModel(Application application,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
ContactManager contactManager, MessagingManager messagingManager) {
|
||||
@CryptoExecutor Executor cryptoExecutor,
|
||||
MessagingManager messagingManager,
|
||||
ContactManager contactManager,
|
||||
PrivateMessageFactory privateMessageFactory) {
|
||||
super(application);
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.cryptoExecutor = cryptoExecutor;
|
||||
this.messagingManager = messagingManager;
|
||||
this.contactManager = contactManager;
|
||||
this.privateMessageFactory = privateMessageFactory;
|
||||
this.attachmentController = new AttachmentController(messagingManager,
|
||||
application.getResources());
|
||||
contactDeleted.setValue(false);
|
||||
@@ -100,6 +133,119 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
void sendMessage(@Nullable String text, List<Uri> uris, long timestamp) {
|
||||
if (messagingGroupId.getValue() == null) loadGroupId();
|
||||
observeForeverOnce(messagingGroupId, groupId -> {
|
||||
if (groupId == null) return;
|
||||
// calls through to creating and storing the message
|
||||
storeAttachments(groupId, text, uris, timestamp);
|
||||
});
|
||||
}
|
||||
|
||||
private void loadGroupId() {
|
||||
if (contactId == null) throw new IllegalStateException();
|
||||
dbExecutor.execute(() -> {
|
||||
try {
|
||||
messagingGroupId.postValue(
|
||||
messagingManager.getConversationId(contactId));
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void storeAttachments(GroupId groupId, @Nullable String text,
|
||||
List<Uri> uris, long timestamp) {
|
||||
dbExecutor.execute(() -> {
|
||||
long start = now();
|
||||
List<AttachmentHeader> attachments = new ArrayList<>();
|
||||
List<AttachmentItem> items = new ArrayList<>();
|
||||
boolean needsSize = uris.size() == 1;
|
||||
for (Uri uri : uris) {
|
||||
Pair<AttachmentHeader, AttachmentItem> pair =
|
||||
createAttachmentHeader(groupId, uri, timestamp,
|
||||
needsSize);
|
||||
if (pair == null) continue;
|
||||
attachments.add(pair.getFirst());
|
||||
items.add(pair.getSecond());
|
||||
}
|
||||
logDuration(LOG, "Storing attachments", start);
|
||||
createMessage(groupId, text, attachments, items, timestamp);
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@DatabaseExecutor
|
||||
private Pair<AttachmentHeader, AttachmentItem> createAttachmentHeader(
|
||||
GroupId groupId, Uri uri, long timestamp, boolean needsSize) {
|
||||
InputStream is = null;
|
||||
try {
|
||||
ContentResolver contentResolver =
|
||||
getApplication().getContentResolver();
|
||||
is = contentResolver.openInputStream(uri);
|
||||
if (is == null) throw new IOException();
|
||||
String contentType = contentResolver.getType(uri);
|
||||
if (contentType == null) throw new IOException("null content type");
|
||||
AttachmentHeader h = messagingManager
|
||||
.addLocalAttachment(groupId, timestamp, contentType, is);
|
||||
is.close();
|
||||
// re-open stream to get AttachmentItem
|
||||
is = contentResolver.openInputStream(uri);
|
||||
if (is == null) throw new IOException();
|
||||
AttachmentItem item = attachmentController
|
||||
.getAttachmentItem(h, new Attachment(is), needsSize);
|
||||
return new Pair<>(h, item);
|
||||
} catch (DbException | IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return null;
|
||||
} finally {
|
||||
if (is != null) tryToClose(is, LOG, WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
private void createMessage(GroupId groupId, @Nullable String text,
|
||||
List<AttachmentHeader> attachments, List<AttachmentItem> aItems,
|
||||
long timestamp) {
|
||||
cryptoExecutor.execute(() -> {
|
||||
try {
|
||||
// TODO remove when text can be null in the backend
|
||||
String msgText = text == null ? "null" : text;
|
||||
PrivateMessage pm = privateMessageFactory
|
||||
.createPrivateMessage(groupId, timestamp, msgText,
|
||||
attachments);
|
||||
attachmentController.put(pm.getMessage().getId(), aItems);
|
||||
storeMessage(pm, msgText, attachments);
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void storeMessage(PrivateMessage m, @Nullable String text,
|
||||
List<AttachmentHeader> attachments) {
|
||||
dbExecutor.execute(() -> {
|
||||
try {
|
||||
long start = now();
|
||||
messagingManager.addLocalMessage(m);
|
||||
logDuration(LOG, "Storing message", start);
|
||||
Message message = m.getMessage();
|
||||
PrivateMessageHeader h = new PrivateMessageHeader(
|
||||
message.getId(), message.getGroupId(),
|
||||
message.getTimestamp(), true, true, false, false,
|
||||
text != null, attachments);
|
||||
// TODO add text to cache when available here
|
||||
addedHeader.postValue(h);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void onAddedPrivateMessageSeen() {
|
||||
addedHeader.setValue(null);
|
||||
}
|
||||
|
||||
AttachmentController getAttachmentController() {
|
||||
return attachmentController;
|
||||
}
|
||||
@@ -120,4 +266,8 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
return contactDeleted;
|
||||
}
|
||||
|
||||
LiveData<PrivateMessageHeader> getAddedPrivateMessage() {
|
||||
return addedHeader;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,18 @@ 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.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 +51,15 @@ 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;
|
||||
|
||||
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 +69,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 +82,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();
|
||||
@@ -102,6 +99,7 @@ public class ImageActivity extends BriarActivity
|
||||
layout = findViewById(R.id.layout);
|
||||
layout.getBackground().setAlpha(255);
|
||||
layout.setCallback(this);
|
||||
layout.getViewTreeObserver().addOnGlobalLayoutListener(this);
|
||||
|
||||
// Status Bar
|
||||
if (SDK_INT >= 21) {
|
||||
@@ -118,59 +116,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
|
||||
@@ -193,11 +161,23 @@ public class ImageActivity extends BriarActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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, Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
if (request == REQUEST_SAVE_ATTACHMENT && result == RESULT_OK) {
|
||||
viewModel.saveImage(attachment, data.getData());
|
||||
viewModel.saveImage(getVisibleAttachment(), data.getData());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,6 +205,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 +247,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 +275,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 +292,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,155 @@
|
||||
package org.briarproject.briar.android.conversation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView.Adapter;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.conversation.glide.Radii;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static android.content.Context.WINDOW_SERVICE;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.briar.android.util.UiUtils.isRtl;
|
||||
|
||||
@NotNullByDefault
|
||||
class ImageAdapter extends Adapter<ImageViewHolder> {
|
||||
|
||||
private final List<AttachmentItem> items = new ArrayList<>();
|
||||
private final ConversationListener listener;
|
||||
private final int imageSize;
|
||||
private final int radiusBig, radiusSmall;
|
||||
private final boolean isRtl;
|
||||
@Nullable
|
||||
private ConversationMessageItem conversationItem;
|
||||
|
||||
ImageAdapter(Context ctx, ConversationListener listener) {
|
||||
this.listener = listener;
|
||||
imageSize = getImageSize(ctx);
|
||||
Resources res = ctx.getResources();
|
||||
radiusBig =
|
||||
res.getDimensionPixelSize(R.dimen.message_bubble_radius_big);
|
||||
radiusSmall =
|
||||
res.getDimensionPixelSize(R.dimen.message_bubble_radius_small);
|
||||
isRtl = isRtl(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) {
|
||||
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
|
||||
R.layout.list_item_image, viewGroup, false);
|
||||
return new ImageViewHolder(v, imageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ImageViewHolder imageViewHolder,
|
||||
int position) {
|
||||
// get item
|
||||
requireNonNull(conversationItem);
|
||||
AttachmentItem item = items.get(position);
|
||||
// set onClick listener
|
||||
imageViewHolder.itemView.setOnClickListener(v ->
|
||||
listener.onAttachmentClicked(v, conversationItem, item)
|
||||
);
|
||||
// bind view holder
|
||||
int size = items.size();
|
||||
boolean isIncoming = conversationItem.isIncoming();
|
||||
boolean hasText = conversationItem.getText() != null;
|
||||
Radii r = getRadii(position, size, isIncoming, hasText);
|
||||
imageViewHolder.bind(item, r, size == 1, singleInRow(position, size));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
void setConversationItem(ConversationMessageItem item) {
|
||||
this.conversationItem = item;
|
||||
this.items.clear();
|
||||
this.items.addAll(item.getAttachments());
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private int getImageSize(Context ctx) {
|
||||
Resources res = ctx.getResources();
|
||||
WindowManager windowManager =
|
||||
(WindowManager) ctx.getSystemService(WINDOW_SERVICE);
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
if (windowManager == null) {
|
||||
return res.getDimensionPixelSize(
|
||||
R.dimen.message_bubble_image_default);
|
||||
}
|
||||
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
|
||||
int imageSize = displayMetrics.widthPixels / 3;
|
||||
int maxSize = res.getDimensionPixelSize(
|
||||
R.dimen.message_bubble_image_max_width);
|
||||
return Math.min(imageSize, maxSize);
|
||||
}
|
||||
|
||||
private Radii getRadii(int pos, int num, boolean isIncoming,
|
||||
boolean hasText) {
|
||||
boolean left = isLeft(pos);
|
||||
boolean single = num == 1;
|
||||
// Top Row
|
||||
int topLeft;
|
||||
int topRight;
|
||||
if (single) {
|
||||
topLeft = isIncoming ? radiusSmall : radiusBig;
|
||||
topRight = !isIncoming ? radiusSmall : radiusBig;
|
||||
} else if (isTopRow(pos)) {
|
||||
topLeft = left ? (isIncoming ? radiusSmall : radiusBig) : 0;
|
||||
topRight = !left ? (!isIncoming ? radiusSmall : radiusBig) : 0;
|
||||
} else {
|
||||
topLeft = 0;
|
||||
topRight = 0;
|
||||
}
|
||||
// Bottom Row
|
||||
boolean singleInRow = singleInRow(pos, num);
|
||||
int bottomLeft;
|
||||
int bottomRight;
|
||||
if (!hasText && isBottomRow(pos, num)) {
|
||||
bottomLeft = singleInRow || left ? radiusBig : 0;
|
||||
bottomRight = singleInRow || !left ? radiusBig : 0;
|
||||
} else {
|
||||
bottomLeft = 0;
|
||||
bottomRight = 0;
|
||||
}
|
||||
if (isRtl) return new Radii(topRight, topLeft, bottomRight, bottomLeft);
|
||||
return new Radii(topLeft, topRight, bottomLeft, bottomRight);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
items.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
static boolean isTopRow(int pos) {
|
||||
return pos < 2;
|
||||
}
|
||||
|
||||
static boolean isLeft(int pos) {
|
||||
return pos % 2 == 0;
|
||||
}
|
||||
|
||||
static boolean isBottomRow(int pos, int num) {
|
||||
return num % 2 == 0 ?
|
||||
pos >= num - 2 : // last two, if even
|
||||
pos > num - 2; // last one, if odd
|
||||
}
|
||||
|
||||
static boolean singleInRow(int pos, int num) {
|
||||
// last item of an odd number
|
||||
return num % 2 != 0 && pos == num -1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
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.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)
|
||||
.diskCacheStrategy(NONE)
|
||||
.error(R.drawable.ic_image_broken)
|
||||
.dontTransform()
|
||||
.addListener(listener)
|
||||
.into(photoView);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.briarproject.briar.android.conversation;
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
@NotNullByDefault
|
||||
class ImageItemDecoration extends ItemDecoration {
|
||||
|
||||
private final int border;
|
||||
private final boolean isRtl;
|
||||
|
||||
ImageItemDecoration(Context ctx) {
|
||||
Resources res = ctx.getResources();
|
||||
|
||||
// for pixel perfection, add a pixel to the border if it has an odd size
|
||||
int b = res.getDimensionPixelSize(R.dimen.message_bubble_border);
|
||||
int realBorderSize = b % 2 == 0 ? b : b + 1;
|
||||
|
||||
// we are applying half the border around the insides of each image
|
||||
// to prevent differently sized images looking slightly broken
|
||||
border = realBorderSize / 2;
|
||||
|
||||
// find out if we are showing a RTL language
|
||||
isRtl = UiUtils.isRtl(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
|
||||
State state) {
|
||||
if (state.getItemCount() == 1) return;
|
||||
int pos = parent.getChildAdapterPosition(view);
|
||||
int num = state.getItemCount();
|
||||
boolean start = isLeft(pos) ^ isRtl;
|
||||
outRect.top = isTopRow(pos) ? 0 : border;
|
||||
outRect.left = start ? 0 : border;
|
||||
outRect.right = start && !singleInRow(pos, num) ? border : 0;
|
||||
outRect.bottom = isBottomRow(pos, num) ? 0 : border;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package org.briarproject.briar.android.conversation;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.v7.widget.RecyclerView.ViewHolder;
|
||||
import android.support.v7.widget.StaggeredGridLayoutManager.LayoutParams;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.load.Transformation;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.conversation.glide.BriarImageTransformation;
|
||||
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;
|
||||
|
||||
@NotNullByDefault
|
||||
class ImageViewHolder extends ViewHolder {
|
||||
|
||||
@DrawableRes
|
||||
private static final int ERROR_RES = R.drawable.ic_image_broken;
|
||||
|
||||
protected final ImageView imageView;
|
||||
private final int imageSize;
|
||||
|
||||
ImageViewHolder(View v, int imageSize) {
|
||||
super(v);
|
||||
imageView = v.findViewById(R.id.imageView);
|
||||
this.imageSize = imageSize;
|
||||
}
|
||||
|
||||
void bind(AttachmentItem attachment, Radii r, boolean single,
|
||||
boolean needsStretch) {
|
||||
if (attachment.hasError()) {
|
||||
GlideApp.with(imageView)
|
||||
.clear(imageView);
|
||||
imageView.setImageResource(ERROR_RES);
|
||||
} else {
|
||||
setImageViewDimensions(attachment, single, needsStretch);
|
||||
loadImage(attachment, r);
|
||||
if (SDK_INT >= 21) {
|
||||
imageView.setTransitionName(attachment.getTransitionName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setImageViewDimensions(AttachmentItem a, boolean single,
|
||||
boolean needsStretch) {
|
||||
LayoutParams params = (LayoutParams) imageView.getLayoutParams();
|
||||
int width = needsStretch ? imageSize * 2 : imageSize;
|
||||
params.width = single ? a.getThumbnailWidth() : width;
|
||||
params.height = single ? a.getThumbnailHeight() : imageSize;
|
||||
params.setFullSpan(!single && needsStretch);
|
||||
imageView.setLayoutParams(params);
|
||||
}
|
||||
|
||||
private void loadImage(AttachmentItem a, Radii r) {
|
||||
Transformation<Bitmap> transformation = new BriarImageTransformation(r);
|
||||
GlideApp.with(imageView)
|
||||
.load(a)
|
||||
.diskCacheStrategy(NONE)
|
||||
.error(ERROR_RES)
|
||||
.transform(transformation)
|
||||
.transition(withCrossFade())
|
||||
.into(imageView)
|
||||
.waitForLayout();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,10 +50,13 @@ public class ImageViewModel extends AndroidViewModel {
|
||||
@IoExecutor
|
||||
private final Executor ioExecutor;
|
||||
|
||||
private MutableLiveData<Boolean> saveState = new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> imageClicked =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> saveState = new MutableLiveData<>();
|
||||
private int toolbarTop, toolbarBottom;
|
||||
|
||||
@Inject
|
||||
public ImageViewModel(Application application,
|
||||
ImageViewModel(Application application,
|
||||
MessagingManager messagingManager,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
@IoExecutor Executor ioExecutor) {
|
||||
@@ -61,6 +66,45 @@ public class ImageViewModel extends AndroidViewModel {
|
||||
this.ioExecutor = ioExecutor;
|
||||
}
|
||||
|
||||
void clickImage() {
|
||||
imageClicked.setValue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 the image was saved,
|
||||
* false if there was an error and null otherwise.
|
||||
|
||||
@@ -7,10 +7,8 @@ import com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
||||
|
||||
public class BriarImageTransformation extends MultiTransformation<Bitmap> {
|
||||
|
||||
public BriarImageTransformation(int smallRadius, int radius,
|
||||
boolean leftCornerSmall, boolean bottomRound) {
|
||||
super(new CenterCrop(), new ImageCornerTransformation(
|
||||
smallRadius, radius, leftCornerSmall, bottomRound));
|
||||
public BriarImageTransformation(Radii r) {
|
||||
super(new CenterCrop(), new CustomCornersTransformation(r));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package org.briarproject.briar.android.conversation.glide;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapShader;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static android.graphics.Bitmap.Config.ARGB_8888;
|
||||
import static android.graphics.Shader.TileMode.CLAMP;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class CustomCornersTransformation extends BitmapTransformation {
|
||||
|
||||
private static final String ID = CustomCornersTransformation.class.getName();
|
||||
|
||||
private final Radii radii;
|
||||
|
||||
public CustomCornersTransformation(Radii radii) {
|
||||
this.radii = radii;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap transform(BitmapPool pool, Bitmap toTransform,
|
||||
int outWidth, int outHeight) {
|
||||
int width = toTransform.getWidth();
|
||||
int height = toTransform.getHeight();
|
||||
|
||||
Bitmap bitmap = pool.get(width, height, ARGB_8888);
|
||||
bitmap.setHasAlpha(true);
|
||||
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
Paint paint = new Paint();
|
||||
paint.setAntiAlias(true);
|
||||
paint.setShader(new BitmapShader(toTransform, CLAMP, CLAMP));
|
||||
drawRect(canvas, paint, width, height);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private void drawRect(Canvas canvas, Paint paint, float width,
|
||||
float height) {
|
||||
drawTopLeft(canvas, paint, radii.topLeft, width, height);
|
||||
drawTopRight(canvas, paint, radii.topRight, width, height);
|
||||
drawBottomLeft(canvas, paint, radii.bottomLeft, width, height);
|
||||
drawBottomRight(canvas, paint, radii.bottomRight, width, height);
|
||||
}
|
||||
|
||||
private void drawTopLeft(Canvas canvas, Paint paint, int radius,
|
||||
float width, float height) {
|
||||
RectF rect = new RectF(
|
||||
0,
|
||||
0,
|
||||
width / 2 + radius + 1,
|
||||
height / 2 + radius + 1
|
||||
);
|
||||
if (radius == 0) canvas.drawRect(rect, paint);
|
||||
else canvas.drawRoundRect(rect, radius, radius, paint);
|
||||
}
|
||||
|
||||
private void drawTopRight(Canvas canvas, Paint paint, int radius,
|
||||
float width, float height) {
|
||||
RectF rect = new RectF(
|
||||
width / 2 - radius,
|
||||
0,
|
||||
width,
|
||||
height / 2 + radius + 1
|
||||
);
|
||||
if (radius == 0) canvas.drawRect(rect, paint);
|
||||
else canvas.drawRoundRect(rect, radius, radius, paint);
|
||||
}
|
||||
|
||||
private void drawBottomLeft(Canvas canvas, Paint paint, int radius,
|
||||
float width, float height) {
|
||||
RectF rect = new RectF(
|
||||
0,
|
||||
height / 2 - radius,
|
||||
width / 2 + radius + 1,
|
||||
height
|
||||
);
|
||||
if (radius == 0) canvas.drawRect(rect, paint);
|
||||
else canvas.drawRoundRect(rect, radius, radius, paint);
|
||||
}
|
||||
|
||||
private void drawBottomRight(Canvas canvas, Paint paint, int radius,
|
||||
float width, float height) {
|
||||
RectF rect = new RectF(
|
||||
width / 2 - radius,
|
||||
height / 2 - radius,
|
||||
width,
|
||||
height
|
||||
);
|
||||
if (radius == 0) canvas.drawRect(rect, paint);
|
||||
else canvas.drawRoundRect(rect, radius, radius, paint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ImageCornerTransformation(" + radii + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof CustomCornersTransformation &&
|
||||
radii.equals(((CustomCornersTransformation) o).radii);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return ID.hashCode() + radii.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
|
||||
messageDigest.update((ID + radii).getBytes(CHARSET));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
package org.briarproject.briar.android.conversation.glide;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapShader;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static android.graphics.Bitmap.Config.ARGB_8888;
|
||||
import static android.graphics.Shader.TileMode.CLAMP;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class ImageCornerTransformation extends BitmapTransformation {
|
||||
|
||||
private static final String ID = ImageCornerTransformation.class.getName();
|
||||
|
||||
private final int smallRadius, radius;
|
||||
private final boolean leftCornerSmall, bottomRound;
|
||||
|
||||
ImageCornerTransformation(int smallRadius, int radius,
|
||||
boolean leftCornerSmall, boolean bottomRound) {
|
||||
this.smallRadius = smallRadius;
|
||||
this.radius = radius;
|
||||
this.leftCornerSmall = leftCornerSmall;
|
||||
this.bottomRound = bottomRound;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap transform(BitmapPool pool, Bitmap toTransform,
|
||||
int outWidth, int outHeight) {
|
||||
int width = toTransform.getWidth();
|
||||
int height = toTransform.getHeight();
|
||||
|
||||
Bitmap bitmap = pool.get(width, height, ARGB_8888);
|
||||
bitmap.setHasAlpha(true);
|
||||
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
Paint paint = new Paint();
|
||||
paint.setAntiAlias(true);
|
||||
paint.setShader(new BitmapShader(toTransform, CLAMP, CLAMP));
|
||||
drawRect(canvas, paint, width, height);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private void drawRect(Canvas canvas, Paint paint, float width,
|
||||
float height) {
|
||||
drawSmallCorner(canvas, paint, width);
|
||||
drawBigCorners(canvas, paint, width, height);
|
||||
}
|
||||
|
||||
private void drawSmallCorner(Canvas canvas, Paint paint, float width) {
|
||||
float left = leftCornerSmall ? 0 : width - radius;
|
||||
float right = leftCornerSmall ? radius : width;
|
||||
canvas.drawRoundRect(new RectF(left, 0, right, radius),
|
||||
smallRadius, smallRadius, paint);
|
||||
}
|
||||
|
||||
private void drawBigCorners(Canvas canvas, Paint paint, float width,
|
||||
float height) {
|
||||
float top = bottomRound ? 0 : radius;
|
||||
RectF rect = new RectF(0, top, width, height);
|
||||
if (bottomRound) {
|
||||
canvas.drawRoundRect(rect, radius, radius, paint);
|
||||
} else {
|
||||
canvas.drawRect(rect, paint);
|
||||
canvas.drawRoundRect(new RectF(0, 0, width, radius * 2),
|
||||
radius, radius, paint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ImageCornerTransformation(smallRadius=" + smallRadius +
|
||||
", radius=" + radius + ", leftCornerSmall=" + leftCornerSmall +
|
||||
", bottomRound=" + bottomRound + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof ImageCornerTransformation &&
|
||||
((ImageCornerTransformation) o).smallRadius == smallRadius &&
|
||||
((ImageCornerTransformation) o).radius == radius &&
|
||||
((ImageCornerTransformation) o).leftCornerSmall ==
|
||||
leftCornerSmall &&
|
||||
((ImageCornerTransformation) o).bottomRound == bottomRound;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return ID.hashCode() + (smallRadius << 16) ^ (radius << 2) ^
|
||||
(leftCornerSmall ? 2 : 0) ^ (bottomRound ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
|
||||
messageDigest.update((ID + '|' + smallRadius + '|' + radius + '|' +
|
||||
leftCornerSmall + '|' + bottomRound).getBytes(CHARSET));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.briarproject.briar.android.conversation.glide;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
public class Radii {
|
||||
|
||||
public final int topLeft, topRight, bottomLeft, bottomRight;
|
||||
|
||||
public Radii(int topLeft, int topRight, int bottomLeft, int bottomRight) {
|
||||
this.topLeft = topLeft;
|
||||
this.topRight = topRight;
|
||||
this.bottomLeft = bottomLeft;
|
||||
this.bottomRight = bottomRight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
return o instanceof Radii &&
|
||||
topLeft == ((Radii) o).topLeft &&
|
||||
topRight == ((Radii) o).topRight &&
|
||||
bottomLeft == ((Radii) o).bottomLeft &&
|
||||
bottomRight == ((Radii) o).bottomRight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return topLeft << 24 ^ topRight << 16 ^ bottomLeft << 8 ^ bottomRight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Radii(topLeft=" + topLeft +
|
||||
",topRight=" + topRight +
|
||||
",bottomLeft=" + bottomLeft +
|
||||
",bottomRight=" + bottomRight;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package org.briarproject.briar.android.keyagreement;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class CameraException extends IOException {
|
||||
class CameraException extends IOException {
|
||||
|
||||
CameraException(String message) {
|
||||
super(message);
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@NotNullByDefault
|
||||
public interface PreviewConsumer {
|
||||
interface PreviewConsumer {
|
||||
|
||||
@UiThread
|
||||
void start(Camera camera, int cameraIndex);
|
||||
|
||||
@@ -29,7 +29,7 @@ import static java.util.logging.Level.WARNING;
|
||||
@SuppressWarnings("deprecation")
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
||||
class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(QrCodeDecoder.class.getName());
|
||||
@@ -40,7 +40,7 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
||||
private Camera camera = null;
|
||||
private int cameraIndex = 0;
|
||||
|
||||
public QrCodeDecoder(ResultCallback callback) {
|
||||
QrCodeDecoder(ResultCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
||||
}
|
||||
|
||||
@NotNullByDefault
|
||||
public interface ResultCallback {
|
||||
interface ResultCallback {
|
||||
|
||||
void handleResult(Result result);
|
||||
}
|
||||
|
||||
@@ -21,13 +21,13 @@ import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@NotNullByDefault
|
||||
public class QrCodeUtils {
|
||||
class QrCodeUtils {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(QrCodeUtils.class.getName());
|
||||
|
||||
@Nullable
|
||||
public static Bitmap createQrCode(DisplayMetrics dm, String input) {
|
||||
static Bitmap createQrCode(DisplayMetrics dm, String input) {
|
||||
int smallestDimen = Math.min(dm.widthPixels, dm.heightPixels);
|
||||
try {
|
||||
// Generate QR code
|
||||
|
||||
@@ -57,6 +57,7 @@ import static android.view.View.VISIBLE;
|
||||
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;
|
||||
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.NO;
|
||||
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.UPDATE;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getDaysUntilExpiry;
|
||||
|
||||
@@ -155,12 +156,12 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
super.onStart();
|
||||
updateTransports();
|
||||
lockManager.checkIfLockable();
|
||||
// controller.showExpiryWarning(new UiResultHandler<ExpiryWarning>(this) {
|
||||
// @Override
|
||||
// public void onResultUi(ExpiryWarning expiry) {
|
||||
// if (expiry != NO) showExpiryWarning(expiry);
|
||||
// }
|
||||
// });
|
||||
controller.showExpiryWarning(new UiResultHandler<ExpiryWarning>(this) {
|
||||
@Override
|
||||
public void onResultUi(ExpiryWarning expiry) {
|
||||
if (expiry != NO) showExpiryWarning(expiry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,9 +14,9 @@ import android.os.PowerManager;
|
||||
import android.support.annotation.AttrRes;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.ColorRes;
|
||||
import android.support.annotation.MainThread;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
@@ -56,6 +56,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
import static android.os.Build.MANUFACTURER;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS;
|
||||
import static android.support.v4.view.ViewCompat.LAYOUT_DIRECTION_RTL;
|
||||
import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_AUTO;
|
||||
import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
|
||||
import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_NO;
|
||||
@@ -342,7 +343,7 @@ public class UiUtils {
|
||||
* If the LiveData's value is available, the {@link Observer} will be
|
||||
* called right away.
|
||||
*/
|
||||
@MainThread
|
||||
@UiThread
|
||||
public static <T> void observeOnce(LiveData<T> liveData,
|
||||
LifecycleOwner owner, Observer<T> observer) {
|
||||
liveData.observe(owner, new Observer<T>() {
|
||||
@@ -354,4 +355,28 @@ public class UiUtils {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #observeOnce(LiveData, LifecycleOwner, Observer)},
|
||||
* but without a {@link LifecycleOwner}.
|
||||
*
|
||||
* Warning: Do NOT call from objects that have a lifecycle.
|
||||
*/
|
||||
@UiThread
|
||||
public static <T> void observeForeverOnce(LiveData<T> liveData,
|
||||
Observer<T> observer) {
|
||||
liveData.observeForever(new Observer<T>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable T t) {
|
||||
observer.onChanged(t);
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean isRtl(Context ctx) {
|
||||
if (SDK_INT < 17) return false;
|
||||
return ctx.getResources().getConfiguration().getLayoutDirection() ==
|
||||
LAYOUT_DIRECTION_RTL;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,8 +28,6 @@ public class BriarRecyclerView extends FrameLayout {
|
||||
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
private long PERIODIC_UPDATE_MILLIS = MIN_DATE_RESOLUTION;
|
||||
|
||||
private RecyclerView recyclerView;
|
||||
private Group emptyState;
|
||||
private AppCompatImageView emptyImage;
|
||||
@@ -217,9 +215,9 @@ public class BriarRecyclerView extends FrameLayout {
|
||||
refresher = () -> {
|
||||
Adapter adapter = recyclerView.getAdapter();
|
||||
adapter.notifyItemRangeChanged(0, adapter.getItemCount());
|
||||
handler.postDelayed(refresher, PERIODIC_UPDATE_MILLIS);
|
||||
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
|
||||
};
|
||||
handler.postDelayed(refresher, PERIODIC_UPDATE_MILLIS);
|
||||
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
|
||||
}
|
||||
|
||||
public void stopPeriodicUpdate() {
|
||||
@@ -229,8 +227,4 @@ public class BriarRecyclerView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
public void setPERIODIC_UPDATE_MILLIS(long millis) {
|
||||
PERIODIC_UPDATE_MILLIS = millis;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,9 +39,6 @@ public class QrCodeView extends FrameLayout {
|
||||
listener.setFullscreen(fullscreen);
|
||||
}
|
||||
);
|
||||
|
||||
// TODO remove
|
||||
fullscreenButton.setVisibility(INVISIBLE);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<corners
|
||||
android:radius="32dp"/>
|
||||
|
||||
<solid
|
||||
android:color="@color/briar_accent"/>
|
||||
|
||||
</shape>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M12,11c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,13c0,-3.31 -2.69,-6 -6,-6s-6,2.69 -6,6c0,2.22 1.21,4.15 3,5.19l1,-1.74c-1.19,-0.7 -2,-1.97 -2,-3.45 0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,1.48 -0.81,2.75 -2,3.45l1,1.74c1.79,-1.04 3,-2.97 3,-5.19zM12,3C6.48,3 2,7.48 2,13c0,3.7 2.01,6.92 4.99,8.65l1,-1.73C5.61,18.53 4,15.96 4,13c0,-4.42 3.58,-8 8,-8s8,3.58 8,8c0,2.96 -1.61,5.53 -4,6.92l1,1.73c2.99,-1.73 5,-4.95 5,-8.65 0,-5.52 -4.48,-10 -10,-10z"/>
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9,5v2h6.59L4,18.59 5.41,20 17,8.41V15h2V5z"/>
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M20,5.41L18.59,4 7,15.59V9H5v10h10v-2H8.41z"/>
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#2A93C6"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#2A93C6"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M19,2h-4.18C14.4,0.84 13.3,0 12,0c-1.3,0 -2.4,0.84 -2.82,2L5,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,4c0,-1.1 -0.9,-2 -2,-2zM12,2c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM19,20L5,20L5,4h2v3h10L17,4h2v16z"/>
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z"/>
|
||||
</vector>
|
||||
@@ -1,12 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M16,6H13V7.9H16C18.26,7.9 20.1,9.73 20.1,12A4.1,4.1 0,0 1,16 16.1H13V18H16A6,6 0,0 0,22 12C22,8.68 19.31,6 16,6M3.9,12C3.9,9.73 5.74,7.9 8,7.9H11V6H8A6,6 0,0 0,2 12A6,6 0,0 0,8 18H11V16.1H8C5.74,16.1 3.9,14.26 3.9,12M8,13H16V11H8V13Z"/>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="m21.6598,17.4732h-2.2959v3.0612H17.5118l3,3 3,-3h-1.852z"/>
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z"/>
|
||||
</vector>
|
||||
@@ -1,12 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M16,6H13V7.9H16C18.26,7.9 20.1,9.73 20.1,12A4.1,4.1 0,0 1,16 16.1H13V18H16A6,6 0,0 0,22 12C22,8.68 19.31,6 16,6M3.9,12C3.9,9.73 5.74,7.9 8,7.9H11V6H8A6,6 0,0 0,2 12A6,6 0,0 0,8 18H11V16.1H8C5.74,16.1 3.9,14.26 3.9,12M8,13H16V11H8V13Z"/>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M21.6598,23.5344H19.3639V20.4732H17.5118l3,-3 3,3h-1.852z"/>
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M11.1731,2.4031C9.9686,2.4471 8.7753,2.713 7.6641,3.2033 8.187,3.7263 8.3967,4.1446 8.6059,4.563 11.6391,3.4125 15.0914,4.0394 17.4971,6.445c0.7322,0.7321 1.3593,1.5692 1.6731,2.5105 0.4184,-0.2092 0.9412,-0.3134 1.4642,-0.3134h0.1045C20.2158,7.387 19.483,6.3401 18.5416,5.2942 16.5283,3.2808 13.823,2.3062 11.1731,2.4031ZM5.1695,3.2316A2.7194,2.7194 0,0 0,2.4501 5.951,2.7194 2.7194,0 0,0 5.1695,8.6704 2.7194,2.7194 0,0 0,7.8889 5.951,2.7194 2.7194,0 0,0 5.1695,3.2316ZM2.5404,8.5359C0.9715,12.1966 1.7027,16.4848 4.6313,19.4134c1.8827,1.9872 4.4972,2.9301 7.0074,2.9301 2.5102,0 5.1264,-0.9428 7.0092,-2.9301 0.9413,-0.9414 1.6724,-2.091 2.1953,-3.3461h-0.1045c-0.5229,0 -1.0458,-0.1042 -1.4642,-0.3134 -0.4184,0.9414 -0.9409,1.7783 -1.6731,2.5105 -3.2423,3.2424 -8.5771,3.2424 -11.8194,0C3.2719,15.8588 2.6451,12.4064 3.7957,9.3733 3.3773,9.1641 2.9588,8.9542 2.5404,8.5359ZM11.5944,9.3804a2.9913,2.9913 0,0 0,-2.9903 2.9903,2.9913 2.9913,0 0,0 2.9903,2.992 2.9913,2.9913 0,0 0,2.992 -2.992,2.9913 2.9913,0 0,0 -2.992,-2.9903zM20.7334,9.8035a2.7194,2.7194 0,0 0,-2.7194 2.7194,2.7194 2.7194,0 0,0 2.7194,2.7194 2.7194,2.7194 0,0 0,2.7194 -2.7194,2.7194 2.7194,0 0,0 -2.7194,-2.7194z"/>
|
||||
</vector>
|
||||
@@ -1,57 +0,0 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="200dp"
|
||||
android:height="237dp"
|
||||
android:viewportHeight="178"
|
||||
android:viewportWidth="150">
|
||||
<path
|
||||
android:fillAlpha="0.25"
|
||||
android:pathData="M85.19,0.128 L3.357,0.329C2.465,0.331 1.611,0.69 0.981,1.329 0.352,1.967 -0.001,2.832 0,3.733L0.373,156.201c0.001,0.446 0.089,0.888 0.26,1.3 0.17,0.412 0.419,0.786 0.732,1.101 0.313,0.315 0.685,0.564 1.093,0.734 0.409,0.17 0.846,0.257 1.288,0.256l81.834,-0.201c0.892,-0.003 1.746,-0.363 2.375,-1.002 0.629,-0.639 0.981,-1.504 0.98,-2.405L88.554,3.516C88.551,2.617 88.196,1.755 87.565,1.12 86.935,0.485 86.081,0.128 85.19,0.128Z">
|
||||
</path>
|
||||
<path
|
||||
android:fillAlpha="0.175"
|
||||
android:fillColor="#6d6d6d"
|
||||
android:pathData="M84.282,-0.002 L4.276,0.196C2.574,0.2 1.198,1.598 1.202,3.318L1.567,154.298c0.004,1.72 1.387,3.111 3.089,3.107l80.006,-0.198c1.702,-0.004 3.078,-1.402 3.074,-3.122L87.37,3.105C87.366,1.385 85.983,-0.006 84.282,-0.002Z"/>
|
||||
<path
|
||||
android:fillAlpha="0.25"
|
||||
android:fillColor="#646464"
|
||||
android:pathData="M81.939,3.957L65.934,3.996C65.689,5.664 64.862,7.188 63.602,8.293C62.342,9.398 60.732,10.01 59.064,10.02L29.303,10.092C27.635,10.091 26.021,9.487 24.756,8.389C23.49,7.29 22.655,5.77 22.402,4.104L6.637,4.143C5.769,4.145 4.938,4.495 4.326,5.117C3.714,5.739 3.371,6.58 3.373,7.457L3.461,44.068L3.717,150.168C3.721,151.045 4.069,151.885 4.686,152.502C5.302,153.119 6.136,153.464 7.004,153.461L82.307,153.275C83.172,153.27 83.999,152.921 84.609,152.301C85.219,151.681 85.561,150.841 85.561,149.967L85.307,43.563L85.221,7.256C85.218,6.379 84.871,5.539 84.256,4.92C83.641,4.301 82.807,3.955 81.939,3.957zM52.652,5.791L34.357,6.068C34.108,6.072 33.908,6.279 33.912,6.531L33.916,6.83C33.92,7.082 34.125,7.285 34.375,7.281L52.672,7.002C52.921,6.998 53.119,6.791 53.115,6.539L53.111,6.24C53.108,5.988 52.902,5.787 52.652,5.791z"/>
|
||||
<path
|
||||
android:fillAlpha="0.25"
|
||||
android:fillColor="#dbdbdb"
|
||||
android:pathData="M57.204,6.934C57.602,6.928 57.919,6.597 57.913,6.195 57.907,5.793 57.58,5.471 57.182,5.477c-0.398,0.006 -0.716,0.337 -0.71,0.739 0.006,0.402 0.333,0.723 0.731,0.717z"/>
|
||||
<path
|
||||
android:fillAlpha="0.25"
|
||||
android:fillColor="#e0e0e0"
|
||||
android:pathData="m17.641,19.459c-4.185,0 -7.576,3.446 -7.576,7.695 0,4.25 3.392,7.695 7.576,7.695 4.185,0 7.576,-3.446 7.576,-7.695 0,-4.25 -3.392,-7.695 -7.576,-7.695zM37.523,23.637v3.234h40.117v-3.234zM37.523,30.104v3.234L77.641,33.338L77.641,30.104ZM17.641,44.088c-4.185,0 -7.576,3.444 -7.576,7.693 0,4.25 3.392,7.695 7.576,7.695 4.185,0 7.576,-3.446 7.576,-7.695 0,-4.25 -3.392,-7.693 -7.576,-7.693zM37.523,48.266L37.523,51.5h40.117v-3.234zM37.523,54.732v3.232L77.641,57.965L77.641,54.732ZM17.641,68.715c-4.185,0 -7.576,3.446 -7.576,7.695 0,4.25 3.392,7.695 7.576,7.695 4.185,0 7.576,-3.446 7.576,-7.695 0,-4.25 -3.392,-7.695 -7.576,-7.695zM37.523,72.895v3.232h40.117v-3.232zM37.523,79.361L37.523,82.594L77.641,82.594L77.641,79.361ZM17.641,93.344c-4.185,0 -7.576,3.446 -7.576,7.695 0,4.25 3.392,7.695 7.576,7.695 4.185,0 7.576,-3.445 7.576,-7.695 0,-4.25 -3.392,-7.695 -7.576,-7.695zM37.523,97.523v3.232h40.117v-3.232zM37.523,103.988v3.234h40.117v-3.234zM17.641,117.973c-4.185,0 -7.576,3.446 -7.576,7.695 0,4.25 3.392,7.693 7.576,7.693 4.185,0 7.576,-3.443 7.576,-7.693 0,-4.249 -3.392,-7.695 -7.576,-7.695zM37.523,122.15v3.234h40.117v-3.234zM37.523,128.617v3.234h40.117v-3.234z"/>
|
||||
<path
|
||||
android:fillColor="#313131"
|
||||
android:pathData="m136.95,18.407 l-80.006,0.198c-1.702,0.004 -3.078,1.402 -3.074,3.122l0.365,150.981c0.004,1.72 1.387,3.111 3.089,3.107l80.006,-0.198c1.702,-0.004 3.078,-1.402 3.074,-3.122L140.039,21.514c-0.004,-1.72 -1.387,-3.111 -3.089,-3.107z"/>
|
||||
<path
|
||||
android:fillColor="#3e3e3e"
|
||||
android:pathData="M134.609,22.357L118.6,22.395C118.356,24.062 117.528,25.587 116.268,26.691C115.008,27.796 113.398,28.408 111.73,28.418L81.969,28.492C80.301,28.491 78.689,27.886 77.424,26.787C76.158,25.689 75.323,24.168 75.07,22.502L59.305,22.541C58.437,22.543 57.604,22.894 56.992,23.516C56.38,24.137 56.037,24.98 56.039,25.857L56.129,62.469L56.748,62.467L56.129,62.471L56.383,168.57C56.385,169.447 56.733,170.287 57.348,170.906C57.963,171.524 58.796,171.871 59.664,171.869L134.967,171.684C135.835,171.682 136.665,171.331 137.277,170.709C137.889,170.088 138.232,169.246 138.23,168.369L137.973,62.219L137.975,62.219L137.889,25.656C137.887,24.779 137.541,23.939 136.926,23.32C136.311,22.702 135.477,22.356 134.609,22.357z"/>
|
||||
<path
|
||||
android:fillColor="#e0e0e0"
|
||||
android:pathData="M109.85,23.891C109.452,23.897 109.135,24.229 109.141,24.631C109.147,25.033 109.473,25.354 109.871,25.348C110.269,25.342 110.586,25.01 110.58,24.607C110.574,24.205 110.248,23.885 109.85,23.891zM105.32,24.201L87.025,24.479C86.776,24.482 86.576,24.691 86.58,24.943L86.584,25.242C86.588,25.494 86.793,25.695 87.043,25.691L105.34,25.414C105.589,25.41 105.787,25.203 105.783,24.951L105.779,24.65C105.775,24.398 105.569,24.197 105.32,24.201zM63.365,37.568L63.365,53.738L79.365,53.738L79.365,37.568L63.365,37.568zM86.748,37.568L86.748,40.801L126.865,40.801L126.865,37.568L86.748,37.568zM86.748,44.037L86.748,47.27L126.865,47.27L126.865,44.037L86.748,44.037zM71.787,112.996C67.603,112.996 64.211,116.441 64.211,120.691C64.211,124.941 67.603,128.387 71.787,128.387C75.972,128.387 79.365,124.941 79.365,120.691C79.365,116.441 75.972,112.996 71.787,112.996zM86.748,116.928L86.748,120.16L126.865,120.16L126.865,116.928L86.748,116.928zM86.748,123.393L86.748,126.627L126.865,126.627L126.865,123.393L86.748,123.393zM71.787,142.602C67.603,142.602 64.211,146.047 64.211,150.297C64.211,154.547 67.603,157.99 71.787,157.99C75.972,157.99 79.365,154.547 79.365,150.297C79.365,146.047 75.972,142.602 71.787,142.602zM86.748,145.535L86.748,148.768L126.865,148.768L126.865,145.535L86.748,145.535zM86.748,152.002L86.748,155.234L126.865,155.234L126.865,152.002L86.748,152.002z"/>
|
||||
<path
|
||||
android:fillColor="#d5d6d7"
|
||||
android:pathData="M148.744,68.665H45.619v33.583h103.125z"/>
|
||||
<path
|
||||
android:fillColor="#87c214"
|
||||
android:pathData="m97.822,50.504v5.723h17.967v-5.723zM86.748,81.104v3.232h40.117v-3.232zM86.748,87.572v3.232h40.117v-3.232z"/>
|
||||
<path
|
||||
android:fillColor="#f5f5f5"
|
||||
android:pathData="m66.865,92.314c4.185,0 7.577,-3.445 7.577,-7.695 0,-4.25 -3.392,-7.695 -7.577,-7.695 -4.185,0 -7.577,3.445 -7.577,7.695 0,4.25 3.392,7.695 7.577,7.695z"/>
|
||||
<path
|
||||
android:fillColor="#87c214"
|
||||
android:pathData="m70.13,90.006h-6.574c-0.315,0.002 -0.625,0.082 -0.904,0.231 -0.278,0.149 -0.517,0.365 -0.696,0.627 1.477,0.856 3.147,1.311 4.85,1.321 1.702,0.01 3.378,-0.427 4.864,-1.266 -0.167,-0.265 -0.393,-0.486 -0.661,-0.645 -0.268,-0.159 -0.569,-0.251 -0.879,-0.269z"/>
|
||||
<path
|
||||
android:fillColor="#333333"
|
||||
android:pathData="m66.861,80.746c-2.08,0 -3.766,1.704 -3.766,3.807 0,0.394 0.076,0.766 0.186,1.123 -0.328,0.813 -0.822,1.878 -1.158,1.709 0,0 5.07,4.425 9.545,0 -0.355,-0.613 -0.773,-1.184 -1.219,-1.732 0.105,-0.35 0.178,-0.715 0.178,-1.1 0,-2.102 -1.686,-3.807 -3.766,-3.807z"/>
|
||||
<path
|
||||
android:fillColor="#fda57d"
|
||||
android:pathData="m66.861,81.57c-1.801,0 -3.302,1.279 -3.674,2.986 -0.005,-0 -0.009,-0.006 -0.014,-0.006 -0.193,0 -0.35,0.297 -0.35,0.664 0,0.347 0.142,0.623 0.32,0.652 0.181,1.408 1.109,2.569 2.381,3.059v0.732c0,0.323 0.127,0.635 0.354,0.863 0.226,0.229 0.534,0.357 0.854,0.357h0.225c0.159,0 0.316,-0.032 0.463,-0.094 0.147,-0.061 0.28,-0.15 0.393,-0.264 0.112,-0.113 0.201,-0.248 0.262,-0.396 0.061,-0.148 0.092,-0.308 0.092,-0.469v-0.721c1.289,-0.484 2.232,-1.655 2.412,-3.076 0.168,-0.049 0.299,-0.312 0.299,-0.645 0,-0.363 -0.153,-0.656 -0.344,-0.662 -0.373,-1.705 -1.873,-2.982 -3.672,-2.982z"/>
|
||||
<path
|
||||
android:fillColor="#333333"
|
||||
android:pathData="m63.253,83.934h7.179c0,0 -0.612,-2.93 -3.328,-2.74 -2.715,0.19 -3.852,2.74 -3.852,2.74z"/>
|
||||
</vector>
|
||||
@@ -1,5 +0,0 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
|
||||
</vector>
|
||||
@@ -1,5 +0,0 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M15,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM6,10L6,7L4,7v3L1,10v2h3v3h2v-3h3v-2L6,10zM15,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#2A93C6"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M3,11H5V13H3V11M11,5H13V9H11V5M9,11H13V15H11V13H9V11M15,11H17V13H19V11H21V13H19V15H21V19H19V21H17V19H13V21H11V17H15V15H17V13H15V11M19,19V15H17V19H19M15,3H21V9H15V3M17,5V7H19V5H17M3,3H9V9H3V3M5,5V7H7V5H5M3,15H9V21H3V15M5,17V19H7V17H5Z"/>
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M4,4H10V10H4V4M20,4V10H14V4H20M14,15H16V13H14V11H16V13H18V11H20V13H18V15H20V18H18V20H16V18H13V20H11V16H14V15M16,15V18H18V15H16M4,20V14H10V20H4M6,6V8H8V6H6M16,6V8H18V6H16M6,16V18H8V16H6M4,11H6V13H4V11M9,11H13V15H11V13H9V11M11,6H13V10H11V6M2,2V6H0V2A2,2 0 0,1 2,0H6V2H2M22,0A2,2 0 0,1 24,2V6H22V2H18V0H22M2,18V22H6V24H2A2,2 0 0,1 0,22V18H2M22,22V18H24V22A2,2 0 0,1 22,24H18V22H22Z"/>
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#2A93C6"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
|
||||
</vector>
|
||||
@@ -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"
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<?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">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/contactNameInput"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_large"
|
||||
android:hint="@string/contact_name_hint"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text|textCapWords"
|
||||
app:layout_constraintBottom_toTopOf="@id/linkInput"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/addButton"
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_large"
|
||||
android:layout_marginTop="8dp"
|
||||
android:enabled="false"
|
||||
android:text="@string/add_contact_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/contactNameInput"
|
||||
tools:enabled="true"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
@@ -1,157 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
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"
|
||||
android:fillViewport="true">
|
||||
|
||||
<android.support.constraint.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_large">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:src="@drawable/ic_nickname"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<android.support.v7.widget.AppCompatImageView
|
||||
android:id="@+id/nicknameIcon"
|
||||
android:layout_width="38dp"
|
||||
android:layout_height="38dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/bubble_accent"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_person"
|
||||
app:layout_constraintBottom_toTopOf="@+id/contactNameLayout"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageView"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:tint="@color/briar_white"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/nicknameIntro"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:gravity="left|start"
|
||||
android:text="@string/nickname_intro"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/nicknameIcon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/nicknameIcon"
|
||||
app:layout_constraintTop_toTopOf="@+id/nicknameIcon"/>
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/contactNameLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
app:errorEnabled="true"
|
||||
app:hintEnabled="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/stepOne"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/nicknameIcon">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/contactNameInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="@string/add_contact_choose_a_nickname"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text|textCapWords"/>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<android.support.constraint.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.5"/>
|
||||
|
||||
|
||||
<android.support.v7.widget.AppCompatImageView
|
||||
android:id="@+id/stepOne"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/bubble_accent"
|
||||
android:gravity="center"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_check_white"
|
||||
android:textColor="@color/briar_white"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/stepOneText"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stepOneText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:text="@string/send_link_title"
|
||||
app:layout_constraintBottom_toTopOf="@+id/addButton"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/stepConnector"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_margin="16dp"
|
||||
android:background="@color/briar_accent"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/stepOne"
|
||||
app:layout_constraintEnd_toStartOf="@+id/stepTwo"
|
||||
app:layout_constraintStart_toEndOf="@+id/stepOne"
|
||||
app:layout_constraintTop_toTopOf="@+id/stepOne"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stepTwo"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:background="@drawable/bubble_accent"
|
||||
android:gravity="center"
|
||||
android:text="2"
|
||||
android:textColor="@color/private_message_date_inverse"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/guideline"
|
||||
app:layout_constraintTop_toTopOf="@+id/stepOne"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stepTwoText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/add_contact_choose_nickname"
|
||||
app:layout_constraintBottom_toTopOf="@+id/addButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/stepTwo"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/addButton"
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/add_contact_button"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
</ScrollView>
|
||||
@@ -1,278 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
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"
|
||||
android:fillViewport="true">
|
||||
|
||||
<android.support.constraint.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_large">
|
||||
|
||||
<android.support.v7.widget.AppCompatImageView
|
||||
android:id="@+id/yourLinkIcon"
|
||||
android:layout_width="38dp"
|
||||
android:layout_height="38dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bubble_accent"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_call_made"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="@color/briar_white"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/yourLink"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:gravity="left|start"
|
||||
android:text="@string/your_link"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/yourLinkIcon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/yourLinkIcon"
|
||||
app:layout_constraintTop_toTopOf="@+id/yourLinkIcon"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/linkView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@color/briar_white"
|
||||
android:padding="8dp"
|
||||
android:textColor="@color/briar_primary"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="18sp"
|
||||
android:typeface="monospace"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/yourLinkIcon"
|
||||
tools:text="briar://scnsdflamslkfjgluoblmksdfbwevlewajfdlkjewwhqliafskfjhskdjhvoieiv"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/shareButton"
|
||||
style="@style/BriarButtonFlat.Positive.Tiny"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableLeft="@drawable/social_share_blue"
|
||||
android:drawablePadding="8dp"
|
||||
android:drawableStart="@drawable/social_share_blue"
|
||||
android:text="@string/share_button"
|
||||
app:layout_constraintBottom_toBottomOf="@id/copyButton"
|
||||
app:layout_constraintEnd_toStartOf="@id/showCodeButton"
|
||||
app:layout_constraintStart_toEndOf="@id/copyButton"
|
||||
app:layout_constraintTop_toTopOf="@id/copyButton"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/copyButton"
|
||||
style="@style/BriarButtonFlat.Positive.Tiny"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:drawableLeft="@drawable/ic_content_copy"
|
||||
android:drawablePadding="8dp"
|
||||
android:drawableStart="@drawable/ic_content_copy"
|
||||
android:text="@string/copy_button"
|
||||
app:layout_constraintEnd_toStartOf="@id/shareButton"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/linkView"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/showCodeButton"
|
||||
style="@style/BriarButtonFlat.Positive.Tiny"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableLeft="@drawable/ic_qr_code"
|
||||
android:drawablePadding="8dp"
|
||||
android:drawableStart="@drawable/ic_qr_code"
|
||||
android:drawableTint="@color/briar_button_text_positive"
|
||||
android:text="@string/show_qr_code_button"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/copyButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/shareButton"
|
||||
app:layout_constraintTop_toTopOf="@id/copyButton"
|
||||
tools:visibility="gone"/>
|
||||
|
||||
<android.support.v7.widget.AppCompatImageView
|
||||
android:id="@+id/linkInputIcon"
|
||||
android:layout_width="38dp"
|
||||
android:layout_height="38dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="@drawable/bubble_accent"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_call_received"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/copyButton"
|
||||
app:tint="@color/briar_white"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/inputLink"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:gravity="left|start"
|
||||
android:text="@string/contact_link_intro"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/linkInputIcon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/linkInputIcon"
|
||||
app:layout_constraintTop_toTopOf="@+id/linkInputIcon"/>
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/linkInputLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
app:errorEnabled="true"
|
||||
app:hintEnabled="false"
|
||||
app:layout_constraintEnd_toStartOf="@+id/pasteButton"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/linkInputIcon">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/linkInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="@string/contact_link_hint"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textUri"/>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<android.support.v7.widget.AppCompatImageButton
|
||||
android:id="@+id/pasteButton"
|
||||
style="@style/BriarButtonFlat.Positive.Tiny"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_content_paste"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/linkInputLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/linkInputLayout"
|
||||
app:layout_constraintTop_toTopOf="@+id/linkInputLayout"
|
||||
app:layout_constraintVertical_bias="0.0"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/scanCodeButton"
|
||||
style="@style/BriarButtonFlat.Positive.Tiny"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableLeft="@drawable/ic_qr_code"
|
||||
android:drawablePadding="8dp"
|
||||
android:drawableStart="@drawable/ic_qr_code"
|
||||
android:text="@string/scan_qr_code_button"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@id/pasteButton"
|
||||
app:layout_constraintTop_toTopOf="@+id/pasteButton"
|
||||
tools:visibility="gone"/>
|
||||
|
||||
<android.support.constraint.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.5"/>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stepOne"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/bubble_accent"
|
||||
android:gravity="center"
|
||||
android:text="1"
|
||||
android:textColor="@color/briar_white"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/stepOneText"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/linkInputLayout"
|
||||
app:layout_constraintVertical_bias="1.0"
|
||||
app:layout_constraintVertical_chainStyle="packed"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stepOneText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/send_link_title"
|
||||
app:layout_constraintBottom_toTopOf="@+id/addButton"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/stepOne"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/stepConnector"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_margin="16dp"
|
||||
android:alpha="0.5"
|
||||
android:background="@color/briar_accent"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/stepOne"
|
||||
app:layout_constraintEnd_toStartOf="@+id/stepTwo"
|
||||
app:layout_constraintStart_toEndOf="@+id/stepOne"
|
||||
app:layout_constraintTop_toTopOf="@+id/stepOne"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stepTwo"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:alpha="0.5"
|
||||
android:background="@drawable/bubble_accent"
|
||||
android:gravity="center"
|
||||
android:text="2"
|
||||
android:textColor="@color/private_message_date_inverse"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/guideline"
|
||||
app:layout_constraintTop_toTopOf="@+id/stepOne"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stepTwoText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/add_contact_choose_nickname"
|
||||
app:layout_constraintBottom_toTopOf="@+id/addButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/stepTwo"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/addButton"
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/continue_button"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/stepOneText"/>
|
||||
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
</ScrollView>
|
||||
@@ -1,95 +0,0 @@
|
||||
<?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">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/contactNameInput"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_large"
|
||||
android:drawableLeft="@drawable/ic_person"
|
||||
android:drawablePadding="8dp"
|
||||
android:drawableTint="?attr/colorControlNormal"
|
||||
android:hint="@string/contact_name_hint"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text|textCapWords"
|
||||
app:layout_constraintBottom_toTopOf="@id/linkInput"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
android:drawableStart="@drawable/ic_person"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/linkInput"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginLeft="@dimen/margin_large"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginStart="@dimen/margin_large"
|
||||
android:layout_marginTop="16dp"
|
||||
android:drawableLeft="@drawable/ic_link"
|
||||
android:drawablePadding="8dp"
|
||||
android:drawableTint="?attr/colorControlNormal"
|
||||
android:hint="@string/contact_link_hint"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textUri"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/contactNameInput"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/pasteButton"
|
||||
style="@style/BriarButtonFlat.Positive"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:drawableLeft="@drawable/ic_content_paste"
|
||||
android:drawablePadding="8dp"
|
||||
android:text="@string/paste_button"
|
||||
app:layout_constraintEnd_toStartOf="@+id/scanCodeButton"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintHorizontal_chainStyle="spread"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/linkInput"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/scanCodeButton"
|
||||
style="@style/BriarButtonFlat.Positive"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:drawableLeft="@drawable/ic_qr_code"
|
||||
android:drawablePadding="8dp"
|
||||
android:text="Scan Code"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/pasteButton"
|
||||
app:layout_constraintTop_toBottomOf="@+id/linkInput"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/addButton"
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_large"
|
||||
android:layout_marginTop="8dp"
|
||||
android:enabled="false"
|
||||
android:text="@string/add_contact_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.75"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/pasteButton"
|
||||
tools:enabled="true"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
@@ -1,87 +0,0 @@
|
||||
<?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">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/linkIntro"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_large"
|
||||
android:text="@string/your_link"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintBottom_toTopOf="@id/linkView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/linkView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/margin_large"
|
||||
android:layout_marginLeft="@dimen/margin_large"
|
||||
android:layout_marginRight="@dimen/margin_large"
|
||||
android:layout_marginStart="@dimen/margin_large"
|
||||
android:background="@color/briar_white"
|
||||
android:padding="8dp"
|
||||
android:textColor="@color/briar_primary"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="18sp"
|
||||
android:typeface="monospace"
|
||||
app:layout_constraintBottom_toTopOf="@+id/copyButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/linkIntro"
|
||||
tools:text="briar://scnsdflamslkfjgluoblmksdfbwevlewajfdlkjewwhqliafskfjhskdjhvoieiv"/>
|
||||
|
||||
<android.support.v7.widget.AppCompatButton
|
||||
android:id="@+id/copyButton"
|
||||
style="@style/BriarButtonFlat.Positive"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_large"
|
||||
android:drawableLeft="@drawable/ic_content_copy"
|
||||
android:drawablePadding="8dp"
|
||||
android:text="@string/copy_button"
|
||||
app:layout_constraintEnd_toStartOf="@+id/shareButton"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintHorizontal_chainStyle="spread"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/linkView"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/shareButton"
|
||||
style="@style/BriarButtonFlat.Positive"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableLeft="@drawable/social_share_blue"
|
||||
android:drawablePadding="8dp"
|
||||
android:text="@string/share_button"
|
||||
app:layout_constraintBottom_toBottomOf="@id/copyButton"
|
||||
app:layout_constraintEnd_toStartOf="@+id/showCodeButton"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/copyButton"
|
||||
app:layout_constraintTop_toTopOf="@id/copyButton"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/showCodeButton"
|
||||
style="@style/BriarButtonFlat.Positive"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableLeft="@drawable/ic_qr_code"
|
||||
android:drawablePadding="8dp"
|
||||
android:drawableTint="@color/briar_button_text_positive"
|
||||
android:text="QR Code"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/copyButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/shareButton"
|
||||
app:layout_constraintTop_toTopOf="@+id/copyButton"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.briarproject.briar.android.view.BriarRecyclerView
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:scrollToEnd="false"/>
|
||||
|
||||
<io.github.kobakei.materialfabspeeddial.FabSpeedDial
|
||||
android:id="@+id/speedDial"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:fab_fabDrawable="@drawable/ic_action_add"
|
||||
app:fab_fabRippleColor="@android:color/transparent"
|
||||
app:fab_menu="@menu/contact_list_actions"
|
||||
app:fab_miniFabTextBackground="@color/briar_accent"
|
||||
app:fab_miniFabTextColor="@android:color/white"/>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
@@ -1,53 +0,0 @@
|
||||
<?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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.briarproject.briar.android.keyagreement.CameraView
|
||||
android:id="@+id/camera_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/textView3"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:visibility="gone"
|
||||
android:text="or"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/enterLinkButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/camera_view"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/enterLinkButton"
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:drawableLeft="@drawable/ic_link"
|
||||
android:drawablePadding="8dp"
|
||||
android:visibility="gone"
|
||||
android:text="Enter Link"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView3"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.briarproject.briar.android.view.QrCodeView
|
||||
android:id="@+id/qrCodeView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/white"/>
|
||||
|
||||
</FrameLayout>
|
||||
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"/>
|
||||
@@ -15,15 +15,19 @@
|
||||
android:background="@drawable/msg_in"
|
||||
android:elevation="@dimen/message_bubble_elevation">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/imageList"
|
||||
android:layout_width="@dimen/message_bubble_image_default"
|
||||
android:layout_height="@dimen/message_bubble_image_default"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="android.support.v7.widget.StaggeredGridLayoutManager"
|
||||
app:layout_constraintBottom_toTopOf="@+id/text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:spanCount="2"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@drawable/alerts_and_states_error"/>
|
||||
tools:listitem="@layout/list_item_image"/>
|
||||
|
||||
<com.vanniktech.emoji.EmojiTextView
|
||||
android:id="@+id/text"
|
||||
@@ -38,10 +42,10 @@
|
||||
android:visibility="gone"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/statusLayout"
|
||||
app:layout_constraintEnd_toEndOf="@+id/imageView"
|
||||
app:layout_constraintEnd_toEndOf="@+id/imageList"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageView"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageList"
|
||||
tools:text="The text of a message which can sometimes be a bit longer as well"/>
|
||||
|
||||
<LinearLayout
|
||||
@@ -53,7 +57,7 @@
|
||||
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
|
||||
android:background="@drawable/msg_status_bubble"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/imageView"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/imageList"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
@@ -15,15 +15,19 @@
|
||||
android:background="@drawable/msg_in"
|
||||
android:elevation="@dimen/message_bubble_elevation">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/imageList"
|
||||
android:layout_width="@dimen/message_bubble_image_default"
|
||||
android:layout_height="@dimen/message_bubble_image_default"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="android.support.v7.widget.StaggeredGridLayoutManager"
|
||||
app:layout_constraintBottom_toTopOf="@+id/text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:spanCount="2"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@drawable/alerts_and_states_error"/>
|
||||
tools:listitem="@layout/list_item_image"/>
|
||||
|
||||
<com.vanniktech.emoji.EmojiTextView
|
||||
android:id="@+id/text"
|
||||
@@ -37,10 +41,10 @@
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/statusLayout"
|
||||
app:layout_constraintEnd_toEndOf="@+id/imageView"
|
||||
app:layout_constraintEnd_toEndOf="@+id/imageList"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageView"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageList"
|
||||
tools:text="The text of a message which can sometimes be a bit longer as well"/>
|
||||
|
||||
<LinearLayout
|
||||
|
||||
@@ -15,15 +15,20 @@
|
||||
android:background="@drawable/msg_in"
|
||||
android:elevation="@dimen/message_bubble_elevation">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/imageList"
|
||||
android:layout_width="@dimen/message_bubble_image_default"
|
||||
android:layout_height="@dimen/message_bubble_image_default"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layoutManager="android.support.v7.widget.StaggeredGridLayoutManager"
|
||||
app:layout_constraintBottom_toTopOf="@+id/text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription"/>
|
||||
app:spanCount="2"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:listitem="@layout/list_item_image"/>
|
||||
|
||||
<com.vanniktech.emoji.EmojiTextView
|
||||
android:id="@+id/text"
|
||||
@@ -40,7 +45,7 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageView"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageList"
|
||||
tools:text="The text of a message which can sometimes be a bit longer as well"/>
|
||||
|
||||
<LinearLayout
|
||||
|
||||
@@ -21,14 +21,18 @@
|
||||
android:background="@drawable/msg_out"
|
||||
android:elevation="@dimen/message_bubble_elevation">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/imageList"
|
||||
android:layout_width="@dimen/message_bubble_image_default"
|
||||
android:layout_height="@dimen/message_bubble_image_default"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layoutManager="android.support.v7.widget.StaggeredGridLayoutManager"
|
||||
app:layout_constraintBottom_toTopOf="@+id/text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:spanCount="2"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@drawable/alerts_and_states_error"/>
|
||||
|
||||
@@ -47,7 +51,7 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageView"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageList"
|
||||
tools:text="This is a long long long message that spans over several lines.\n\nIt ends here."/>
|
||||
|
||||
<LinearLayout
|
||||
|
||||
10
briar-android/src/main/res/layout/list_item_image.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="@dimen/message_bubble_image_default"
|
||||
android:layout_height="@dimen/message_bubble_image_default"
|
||||
android:scaleType="centerCrop"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:srcCompat="@tools:sample/avatars"/>
|
||||
@@ -1,82 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout
|
||||
android:id="@+id/linearLayout4"
|
||||
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="wrap_content">
|
||||
|
||||
<org.briarproject.briar.android.view.TextAvatarView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="@dimen/listitem_picture_frame_size"
|
||||
android:layout_height="@dimen/listitem_picture_frame_size"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginTop="8dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/divider"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<com.vanniktech.emoji.EmojiTextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/margin_large"
|
||||
android:layout_marginStart="@dimen/margin_large"
|
||||
android:layout_marginTop="@dimen/margin_large"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/status"
|
||||
app:layout_constraintEnd_toStartOf="@+id/time"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
app:layout_constraintStart_toEndOf="@+id/avatar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="This is a name of a contact"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/waiting_for_contact_to_come_online"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/divider"
|
||||
app:layout_constraintEnd_toStartOf="@+id/time"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="@+id/name"
|
||||
app:layout_constraintTop_toBottomOf="@+id/name"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_large"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/name"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Dec 24"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
style="@style/Divider.ContactList"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/avatar"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
@@ -4,17 +4,9 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_add_contact_nearby"
|
||||
android:icon="@drawable/ic_nearby"
|
||||
android:orderInCategory="3"
|
||||
android:title="@string/add_contact_nearby_title"
|
||||
app:showAsAction="never"/>
|
||||
android:id="@+id/action_add_contact"
|
||||
android:icon="@drawable/ic_add_white"
|
||||
android:title="@string/add_contact_title"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_add_contact_remotely"
|
||||
android:icon="@drawable/ic_link_menu"
|
||||
android:orderInCategory="2"
|
||||
android:title="@string/add_contact_remotely_title"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
</menu>
|
||||
</menu>
|
||||
@@ -8,7 +8,6 @@
|
||||
<color name="briar_blue_elio_light">#3C80A9</color>
|
||||
<color name="briar_blue_light">#2A93C6</color>
|
||||
<color name="briar_blue_grey">#EBEFF2</color>
|
||||
<color name="briar_yellow">#9e9d24</color>
|
||||
<color name="briar_green">#5C940D</color>
|
||||
<color name="briar_green_light">#95D220</color>
|
||||
<color name="briar_red">#ff0000</color>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<dimen name="message_bubble_radius_top_inner">@dimen/message_bubble_radius_small</dimen>
|
||||
<dimen name="message_bubble_radius_top_outer">@dimen/message_bubble_radius_big</dimen>
|
||||
<dimen name="message_bubble_margin">6dp</dimen>
|
||||
<dimen name="message_bubble_image_default">210dp</dimen>
|
||||
<dimen name="message_bubble_image_default">115dp</dimen>
|
||||
<dimen name="message_bubble_image_min_width">150dp</dimen>
|
||||
<dimen name="message_bubble_image_max_width">240dp</dimen>
|
||||
<dimen name="message_bubble_image_min_height">100dp</dimen>
|
||||
|
||||
@@ -164,41 +164,6 @@
|
||||
<string name="connection_error_explanation">Please check that you\'re both connected to the same Wi-Fi network.</string>
|
||||
<string name="connection_error_feedback">If this problem persists, please <a href="feedback">send feedback</a> to help us improve the app.</string>
|
||||
|
||||
|
||||
<!-- Adding Contacts Remotely PROTOTYPE -->
|
||||
<string name="add_contact_remotely_title_case">Add Contact Remotely</string>
|
||||
<string name="add_contact_nearby_title">Add contact nearby</string>
|
||||
<string name="add_contact_remotely_title">Add contact remotely</string>
|
||||
<string name="contact_name_hint">Give contact a nickname</string>
|
||||
<string name="contact_link_intro">Enter the link from your contact here</string>
|
||||
<string name="contact_link_hint">Contact\'s link</string>
|
||||
<string name="paste_button">Paste</string>
|
||||
<string name="scan_qr_code_button">QR Code</string>
|
||||
<string name="add_contact_button">Add contact</string>
|
||||
<string name="copy_button">Copy</string>
|
||||
<string name="share_button">Share</string>
|
||||
<string name="show_qr_code_button">QR Code</string>
|
||||
<string name="send_link_title">Exchange links</string>
|
||||
<string name="add_contact_choose_nickname">Choose Nickname</string>
|
||||
<string name="add_contact_choose_a_nickname">Choose a nickname</string>
|
||||
<string name="nickname_intro">Give your contact a nickname! Only you can see it.</string>
|
||||
<string name="your_link">Give this link to the contact you want to add</string>
|
||||
<string name="link_clip_label">Briar link</string>
|
||||
<string name="link_copied_toast">Link copied</string>
|
||||
<string name="pending_contact_requests_snackbar">"There are pending contact requests"</string>
|
||||
<string name="pending_contact_requests">Pending contact requests</string>
|
||||
<string name="add_contact_remote_connecting">Connecting…</string>
|
||||
<string name="scan_qr_code_title">Scan QR Code</string>
|
||||
<string name="show_qr_code_title">Your QR Code</string>
|
||||
<string name="camera_error_toast">Camera Error</string>
|
||||
<string name="waiting_for_contact_to_come_online">Waiting for contact to come online…</string>
|
||||
<string name="connecting">Connecting…</string>
|
||||
<string name="adding_contact">Adding contact…</string>
|
||||
<string name="own_link_error">Enter your contact\'s link</string>
|
||||
<string name="nickname_missing">Nickname is missing</string>
|
||||
<string name="invalid_link">Invalid link</string>
|
||||
|
||||
|
||||
<!-- Introductions -->
|
||||
<string name="introduction_onboarding_title">Introduce your contacts</string>
|
||||
<string name="introduction_onboarding_text">You can introduce your contacts to each other, so they don\'t need to meet in person to connect on Briar.</string>
|
||||
|
||||
@@ -91,7 +91,6 @@ dependencyVerification {
|
||||
'com.github.bumptech.glide:gifdecoder:4.8.0:gifdecoder-4.8.0.aar:b00c5454a023a9488ea49603930d9c25e09192e5ceaadf64977aa52946b3c1b4',
|
||||
'com.github.bumptech.glide:glide:4.8.0:glide-4.8.0.aar:5ddf08b12cc43332e812988f16c2c39e7fce49d1c4d94b7948dcde7f00bf49d6',
|
||||
'com.github.chrisbanes:PhotoView:2.1.4:PhotoView-2.1.4.aar:04cb397fcb3df0757c8aed6927ebdd247930b5c78ee9acc59cd07dccdaaf3460',
|
||||
'com.github.kobakei:MaterialFabSpeedDial:1.2.0:MaterialFabSpeedDial-1.2.0.aar:fda1784d6f213e22079d5c1e9ea53e3e6eb4f908297ccfe6dea588c57aafc800',
|
||||
'com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:2.0:accessibility-test-framework-2.0.jar:cdf16ef8f5b8023d003ce3cc1b0d51bda737762e2dab2fedf43d1c4292353f7f',
|
||||
'com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:2.1:accessibility-test-framework-2.1.jar:7b0aa6ed7553597ce0610684a9f7eca8021eee218f2e2f427c04a7fbf5f920bd',
|
||||
'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed',
|
||||
@@ -204,7 +203,6 @@ dependencyVerification {
|
||||
'org.robolectric:shadows-framework:4.0.1:shadows-framework-4.0.1.jar:b468a4d0b225e846f3d52c71fe9c3e2dda0b00ced9317a1ea205bb6d78ba1591',
|
||||
'org.robolectric:shadows-support-v4:3.3.2:shadows-support-v4-3.3.2.jar:6f689264738266e70fe08db7c04b7b5a75155994f4e3f7f311960d90486bf005',
|
||||
'org.robolectric:utils:4.0.1:utils-4.0.1.jar:ee923ed66847271009ebeb246286b7206b160c2b6d1347fe820c00be06c280cb',
|
||||
'tools.fastlane:screengrab:1.1.0:screengrab-1.1.0.aar:03ce3868ee8a0082d14e7a1de0999f91531c0cc794392688beb08ee9bc4495fd',
|
||||
'tools.fastlane:screengrab:1.2.0:screengrab-1.2.0.aar:af4ee23bb06f94404d3ab18e2ea69db8265539fc8da29f9ee45b7e472684ba83',
|
||||
'uk.co.samuelwall:material-tap-target-prompt:2.12.4:material-tap-target-prompt-2.12.4.aar:6c0990ab3aa22de9f7d09dcb0a944e671128c31634ac8429012faa5c508202fb',
|
||||
]
|
||||
|
||||
@@ -8,37 +8,12 @@ import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.conversation.ConversationManager.ConversationClient;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface MessagingManager extends ConversationClient {
|
||||
|
||||
// TODO remove (only for prototype)
|
||||
void addNewPendingContact(String name, long timestamp, long addAt) throws DbException;
|
||||
ContactId removePendingContact(String name, long timestamp, long addAt) throws DbException;
|
||||
Collection<PendingContact> getPendingContacts() throws DbException;
|
||||
class PendingContact {
|
||||
private final String name;
|
||||
private final long timestamp, addAt;
|
||||
public PendingContact(String name, long timestamp, long addAt) {
|
||||
this.name = name;
|
||||
this.timestamp = timestamp;
|
||||
this.addAt = addAt;
|
||||
}
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
public long getAddAt() {
|
||||
return addAt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The unique ID of the messaging client.
|
||||
*/
|
||||
@@ -63,7 +38,7 @@ public interface MessagingManager extends ConversationClient {
|
||||
* Stores a local attachment message.
|
||||
*/
|
||||
AttachmentHeader addLocalAttachment(GroupId groupId, long timestamp,
|
||||
String contentType, ByteBuffer data) throws DbException;
|
||||
String contentType, InputStream is) throws DbException, IOException;
|
||||
|
||||
/**
|
||||
* Returns the ID of the contact with the given private conversation.
|
||||
|
||||
@@ -5,19 +5,13 @@ import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
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.contact.ContactManager.ContactHook;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataParser;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Client;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
@@ -30,7 +24,6 @@ import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
||||
import org.briarproject.briar.api.introduction.event.IntroductionSucceededEvent;
|
||||
import org.briarproject.briar.api.messaging.Attachment;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
@@ -39,7 +32,8 @@ import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||
import org.briarproject.briar.api.messaging.event.PrivateMessageReceivedEvent;
|
||||
import org.briarproject.briar.client.ConversationClientImpl;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -50,7 +44,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
||||
|
||||
@Immutable
|
||||
@@ -61,119 +55,14 @@ class MessagingManagerImpl extends ConversationClientImpl
|
||||
private final ClientVersioningManager clientVersioningManager;
|
||||
private final ContactGroupFactory contactGroupFactory;
|
||||
|
||||
// TODO remove
|
||||
private final ContactManager contactManager;
|
||||
private final IdentityManager identityManager;
|
||||
private final AuthorFactory authorFactory;
|
||||
|
||||
@Inject
|
||||
MessagingManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
||||
ClientVersioningManager clientVersioningManager,
|
||||
MetadataParser metadataParser, MessageTracker messageTracker,
|
||||
ContactGroupFactory contactGroupFactory,
|
||||
ContactManager contactManager, IdentityManager identityManager,
|
||||
AuthorFactory authorFactory) {
|
||||
ContactGroupFactory contactGroupFactory) {
|
||||
super(db, clientHelper, metadataParser, messageTracker);
|
||||
this.clientVersioningManager = clientVersioningManager;
|
||||
this.contactGroupFactory = contactGroupFactory;
|
||||
|
||||
// TODO remove
|
||||
this.contactManager = contactManager;
|
||||
this.identityManager = identityManager;
|
||||
this.authorFactory = authorFactory;
|
||||
}
|
||||
|
||||
private static final String PENDING_CONTACTS = "PENDING_CONTACTS";
|
||||
@Override
|
||||
public void addNewPendingContact(String name, long timestamp, long addAt)
|
||||
throws DbException {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
BdfList list = getPendingContacts(txn);
|
||||
BdfDictionary contact = new BdfDictionary();
|
||||
contact.put("name", name);
|
||||
contact.put("timestamp", timestamp);
|
||||
contact.put("addAt", addAt);
|
||||
list.add(contact);
|
||||
|
||||
Group localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
||||
MAJOR_VERSION);
|
||||
BdfDictionary meta =
|
||||
BdfDictionary.of(new BdfEntry(PENDING_CONTACTS, list));
|
||||
clientHelper.mergeGroupMetadata(txn, localGroup.getId(), meta);
|
||||
|
||||
db.commitTransaction(txn);
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public ContactId removePendingContact(String name, long timestamp, long addAt) throws DbException {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
BdfList list = getPendingContacts(txn);
|
||||
|
||||
BdfDictionary contactDict = new BdfDictionary();
|
||||
contactDict.put("name", name);
|
||||
contactDict.put("timestamp", timestamp);
|
||||
contactDict.put("addAt", addAt);
|
||||
list.remove(contactDict);
|
||||
|
||||
Group localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
||||
MAJOR_VERSION);
|
||||
BdfDictionary meta =
|
||||
BdfDictionary.of(new BdfEntry(PENDING_CONTACTS, list));
|
||||
clientHelper.mergeGroupMetadata(txn, localGroup.getId(), meta);
|
||||
|
||||
AuthorId local = identityManager.getLocalAuthor(txn).getId();
|
||||
byte[] pubKey = new byte[MAX_PUBLIC_KEY_LENGTH];
|
||||
new Random().nextBytes(pubKey);
|
||||
Author remote = authorFactory.createAuthor(name, pubKey);
|
||||
ContactId c =
|
||||
contactManager.addContact(txn, remote, local, false, true);
|
||||
|
||||
Contact contact =
|
||||
contactManager.getContact(txn, remote.getId(), local);
|
||||
IntroductionSucceededEvent event =
|
||||
new IntroductionSucceededEvent(contact);
|
||||
txn.attach(event);
|
||||
|
||||
db.commitTransaction(txn);
|
||||
return c;
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public Collection<PendingContact> getPendingContacts() throws DbException {
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
BdfList list = getPendingContacts(txn);
|
||||
List<PendingContact> contacts = new ArrayList<>(list.size());
|
||||
for (Object o : list) {
|
||||
BdfDictionary d = (BdfDictionary) o;
|
||||
contacts.add(new PendingContact(d.getString("name"),
|
||||
d.getLong("timestamp"), d.getLong("addAt")));
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
return contacts;
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
}
|
||||
private BdfList getPendingContacts(Transaction txn)
|
||||
throws DbException, FormatException {
|
||||
Group localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
||||
MAJOR_VERSION);
|
||||
BdfDictionary d = clientHelper
|
||||
.getGroupMetadataAsDictionary(txn, localGroup.getId());
|
||||
return d.getList(PENDING_CONTACTS, new BdfList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -266,8 +155,9 @@ class MessagingManagerImpl extends ConversationClientImpl
|
||||
|
||||
@Override
|
||||
public AttachmentHeader addLocalAttachment(GroupId groupId, long timestamp,
|
||||
String contentType, ByteBuffer data) {
|
||||
String contentType, InputStream is) throws IOException {
|
||||
// TODO add real implementation
|
||||
if (is.available() == 0) throw new IOException();
|
||||
byte[] b = new byte[MessageId.LENGTH];
|
||||
new Random().nextBytes(b);
|
||||
return new AttachmentHeader(new MessageId(b), "image/png");
|
||||
@@ -329,8 +219,18 @@ class MessagingManagerImpl extends ConversationClientImpl
|
||||
long timestamp = meta.getLong("timestamp");
|
||||
boolean local = meta.getBoolean("local");
|
||||
boolean read = meta.getBoolean("read");
|
||||
// TODO replace fake attachments by real ones
|
||||
int num = (int) (timestamp % 5);
|
||||
boolean hasText = num == 0 || id.hashCode() % 2 == 0;
|
||||
List<AttachmentHeader> attachments = new ArrayList<>(num);
|
||||
for (int i = 0; i < num; i++) {
|
||||
byte[] aIdBytes = id.getBytes().clone();
|
||||
aIdBytes[0] = (byte) i;
|
||||
MessageId aId = new MessageId(aIdBytes);
|
||||
attachments.add(new AttachmentHeader(aId, "image/jpeg"));
|
||||
}
|
||||
headers.add(new PrivateMessageHeader(id, g, timestamp, local,
|
||||
read, s.isSent(), s.isSeen(), true, emptyList()));
|
||||
read, s.isSent(), s.isSeen(), hasText, attachments));
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
@@ -351,7 +251,30 @@ class MessagingManagerImpl extends ConversationClientImpl
|
||||
@Override
|
||||
public Attachment getAttachment(MessageId m) {
|
||||
// TODO add real implementation
|
||||
throw new IllegalStateException("Not yet implemented");
|
||||
String[] files = new String[] {
|
||||
// "error_animated.gif",
|
||||
// "error_high.jpg",
|
||||
// "error_wide.jpg",
|
||||
// "error_huge.gif",
|
||||
// "error_large.gif",
|
||||
// "error_malformed.jpg",
|
||||
// "wide.jpg",
|
||||
// "high.jpg",
|
||||
// "small.png",
|
||||
"kitten1.jpg",
|
||||
"kitten2.jpg",
|
||||
"kitten3.gif",
|
||||
"kitten4.jpg",
|
||||
"kitten5.jpg",
|
||||
"kitten6.png",
|
||||
};
|
||||
int index = Math.abs(m.hashCode() % files.length);
|
||||
String file = files[index];
|
||||
getLogger(MessagingManagerImpl.class.getName())
|
||||
.warning("Loading file: " + file);
|
||||
|
||||
InputStream is = getClass().getClassLoader().getResourceAsStream(file);
|
||||
return new Attachment(is);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BIN
briar-core/src/main/resources/error_animated.gif
Normal file
|
After Width: | Height: | Size: 43 B |
BIN
briar-core/src/main/resources/error_high.jpg
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
briar-core/src/main/resources/error_huge.gif
Normal file
|
After Width: | Height: | Size: 43 B |
BIN
briar-core/src/main/resources/error_large.gif
Normal file
|
After Width: | Height: | Size: 43 B |
0
briar-core/src/main/resources/error_malformed.jpg
Normal file
BIN
briar-core/src/main/resources/error_wide.jpg
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
briar-core/src/main/resources/high.jpg
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
briar-core/src/main/resources/kitten1.jpg
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
briar-core/src/main/resources/kitten2.jpg
Normal file
|
After Width: | Height: | Size: 128 KiB |
BIN
briar-core/src/main/resources/kitten3.gif
Normal file
|
After Width: | Height: | Size: 288 KiB |
BIN
briar-core/src/main/resources/kitten4.jpg
Normal file
|
After Width: | Height: | Size: 35 KiB |