Compare commits

..

23 Commits

Author SHA1 Message Date
Torsten Grote
e6e077ff40 [android] enable image shared element transition for API 21+22
There's an Android framework bug (#224270) on these APIs that causes a NPE
when the shared element is not visible anymore when returning.
Since we know restore the list position, the shared element should be
visible and thus not produce NPEs anymore.
2018-12-13 14:20:52 -02:00
Torsten Grote
4e5e354af2 [android] Fix enter transition to fullscreen ImageActivity 2018-12-13 14:20:52 -02:00
Torsten Grote
9ea3623c85 [android] Save and restore list position of conversation across restarts 2018-12-13 14:20:51 -02:00
Torsten Grote
84089f5e16 DO NOT MERGE: Return multiple fake image attachements in MessagingManager
(cherry picked from commit d9cb675)
2018-12-13 14:20:04 -02:00
Torsten Grote
a994966095 [android] address review comments for image fullscreen swiping 2018-12-13 12:00:51 -02:00
Torsten Grote
2bea581654 [android] Swipe left/right in image screen to see other images from the same message 2018-12-13 11:59:41 -02:00
Torsten Grote
87377666aa Merge branch '1473-display-multiple-images' into 'master'
UX for displaying multiple image attachments

Closes #1473

See merge request briar/briar!1010
2018-12-13 13:07:24 +00:00
akwizgran
9d07b2e141 Resolve merge conflicts.
# Conflicts:
#   briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java
#   briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java
2018-12-13 11:41:04 +00:00
akwizgran
5c312b49e2 Merge branch '1438-send-image-attachments' into 'master'
Store attachments and actually attach them to sent messages

Closes #1438

See merge request briar/briar!1006
2018-12-13 10:27:09 +00:00
Torsten Grote
f56efe45cd Merge branch '1477-get-client-minor-version' into 'master'
Add method for querying client minor version supported by contact

See merge request briar/briar!1014
2018-12-12 19:34:55 +00:00
Torsten Grote
2332a58681 [android] address review comments for displaying multiple images 2018-12-12 17:00:44 -02:00
Torsten Grote
8c6dfaa196 [android] Use @UiThread instead of @MainThread 2018-12-12 16:18:43 -02:00
Torsten Grote
3cfb04b60d Establish some rules for handling InputStreams
* Methods shouldn't place any special requirements on the streams
  passed into them
* This implies that if a stream's going to be marked and reset,
  that should all happen within one method
* This also implies that if a method needs to mark and reset a stream,
  it should wrap the stream in a BufferedInputStream before doing so,
  rather than requiring a markable stream to be passed in
2018-12-12 16:17:50 -02:00
Torsten Grote
e85fbfb952 [android] close InputStream with new IoUtils method 2018-12-12 16:17:50 -02:00
Torsten Grote
80ee35d926 [core] Return fake mini PNG as Attachment instead of throwing exception 2018-12-12 16:17:50 -02:00
Torsten Grote
4796902b9c [android] store attachments and actually attach them to sent messages 2018-12-12 16:17:50 -02:00
akwizgran
149e67c0f7 Reduce code duplication in tests. 2018-12-12 11:57:35 +00:00
akwizgran
1d5214117f Add tests for getClientMinorVersion(). 2018-12-11 17:55:39 +00:00
akwizgran
b8f248ca9c Add tests for getClientVisibility(). 2018-12-11 17:51:42 +00:00
Torsten Grote
dfb71a03a5 [android] Only retrieve image sizes for single images in messages
We need to do this to know the height of messages when binding the view.
The size of single images can be different (e.g. due to orientation).
For multiple images, we use a fixed size, so no retrieval is required.
2018-12-11 15:38:05 -02:00
Torsten Grote
961fdc8e72 [android] Show multiple images in message bubble 2018-12-11 15:28:21 -02:00
Torsten Grote
c3d44663cd [android] Use a nested RecyclerView with a single items to show image attachments
This is preparation for showing multiple image attachments in one
message bubble.
2018-12-11 15:28:21 -02:00
akwizgran
0081472489 Add method for querying contact's client minor version. 2018-12-11 17:25:29 +00:00
105 changed files with 1575 additions and 3216 deletions

View File

@@ -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,

View File

@@ -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);
}
}

View File

@@ -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 =

View File

@@ -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));
}});
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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();
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
});
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
/**

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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()));
}

View File

@@ -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));
}
}

View File

@@ -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)) {

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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.

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -39,9 +39,6 @@ public class QrCodeView extends FrameLayout {
listener.setFullscreen(fullscreen);
}
);
// TODO remove
fullscreenButton.setVisibility(INVISIBLE);
}
@UiThread

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View 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"/>

View File

@@ -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">

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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"/>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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',
]

View File

@@ -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.

View File

@@ -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);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Some files were not shown because too many files have changed in this diff Show More