Compare commits

...

43 Commits

Author SHA1 Message Date
akwizgran
2ba848e445 Set app_package to match applicationIdSuffix. 2018-11-07 09:51:08 +00:00
akwizgran
73f8c5f3f2 Don't crop camera preview before decoding. 2018-10-29 11:05:14 +00:00
Torsten Grote
823dbb1b67 Change package ID and name for prototype 2018-10-23 14:04:28 -03:00
Torsten Grote
1589ea8853 Show QR code again 2018-10-23 14:00:52 -03:00
Torsten Grote
c9ebe14d1e Add error message for invalid links 2018-10-23 13:57:31 -03:00
Torsten Grote
bd3df6e364 Double link size to 128 characters 2018-10-22 13:09:21 -03:00
Torsten Grote
be5a804a46 Show dialog when user tries to add contact with their own link 2018-10-22 12:55:19 -03:00
Torsten Grote
1cd4134cb0 Rearrange layout so sending link comes first 2018-10-22 12:38:51 -03:00
Torsten Grote
6706fea2d3 Make 'Add a Contact' screen scrollable when keyboard is open 2018-10-22 12:07:01 -03:00
akwizgran
62aa0530d8 Hide QR code buttons. 2018-10-22 12:07:01 -03:00
akwizgran
422d0b3c4b Use constant link, derive delay from both links. 2018-10-22 12:07:01 -03:00
akwizgran
9cafd2c340 Initialise addButton early to avoid NPE. 2018-10-22 12:07:01 -03:00
akwizgran
69f01edee3 Combine input and output into one screen. 2018-10-22 12:07:01 -03:00
akwizgran
ca7fea1d95 Lowercase links. 2018-10-22 12:07:01 -03:00
akwizgran
856809e913 Use accent resource so colour is correct for dark theme. 2018-10-22 12:05:19 -03:00
akwizgran
dac10b5e5a Use kobakei's speed dial library. 2018-10-22 12:05:16 -03:00
Torsten Grote
08391a31d0 Hack compound drawable tint for API < 23 2018-10-22 12:02:37 -03:00
Torsten Grote
8bc0bae9db Make link visible in dark theme 2018-10-22 12:02:37 -03:00
Torsten Grote
448c5d3e90 Replace qr code icons in speedial with link icons 2018-10-22 12:02:37 -03:00
Torsten Grote
27be5a0d82 Make QR code another view on the invite link 2018-10-22 12:02:37 -03:00
Torsten Grote
ccd8f16185 Allow multiple pending fake contacts at the same time 2018-10-22 12:02:37 -03:00
Torsten Grote
fc10af73ca Allow actual QR code scanning and use dedicated contact alias fragment 2018-10-22 12:02:37 -03:00
Torsten Grote
6a25f20390 Add (non-functional) button for scanning QR code 2018-10-22 12:02:37 -03:00
Torsten Grote
12f7113b8d Only start one navdrawer activity with contact list 2018-10-22 12:02:37 -03:00
Torsten Grote
56960d51ca Add buttons to switch between showing link and code 2018-10-22 12:02:37 -03:00
Torsten Grote
42e10b1840 Move link input into fragment 2018-10-22 12:02:37 -03:00
Torsten Grote
e0f5c0b1f3 Check if lifecycle is running before adding fake contact 2018-10-22 12:02:37 -03:00
Torsten Grote
22cac6d44a Show QR code by default instead of link 2018-10-22 12:02:37 -03:00
akwizgran
6e109a8804 Accept links without briar:// prefix. 2018-10-22 12:02:37 -03:00
akwizgran
b9bd752f24 Try to make links clickable. 2018-10-22 12:02:37 -03:00
Torsten Grote
5d8f006370 Actually add fake contact ~1 minute after entering link 2018-10-22 12:02:37 -03:00
Torsten Grote
b6b7cfb27a Add pending contact requests screen 2018-10-22 12:02:37 -03:00
akwizgran
89d33a3a84 Use base32 for contact links. 2018-10-22 12:02:37 -03:00
akwizgran
bbdb9e150f Accept text shared by other apps. 2018-10-22 12:02:37 -03:00
akwizgran
f202ddd9a3 Add third speed dial action, share button. 2018-10-22 12:02:37 -03:00
Torsten Grote
ac87f23187 Add Activities for receiving and entering contact link 2018-10-22 12:02:34 -03:00
akwizgran
389b2b5b8e Merge branch '1423-check-lifecycle-state' into 'master'
Check lifecycle state before changing fragment

Closes #1423

See merge request briar/briar!958
2018-10-22 14:27:30 +00:00
Torsten Grote
78abfa3698 Check lifecycle state before changing fragment 2018-10-19 16:40:00 -03:00
akwizgran
e47d41596a Merge branch '1254-inject-object-mapper' into 'master'
briar-headless: Inject a singleton ObjectMapper for JSON parsing

See merge request briar/briar!957
2018-10-18 15:40:20 +00:00
Torsten Grote
8cf54bcedb briar-headless: Inject a singleton ObjectMapper for JSON parsing 2018-10-18 09:55:34 -03:00
akwizgran
89d5145665 Bump version numbers for 1.1.3 release. 2018-10-17 17:51:11 +01:00
Torsten Grote
0706498b03 Merge branch 'upgrade-android-gradle-plugin' into 'master'
Upgrade Android Gradle plugin to 3.2.1.

See merge request briar/briar!956
2018-10-17 16:36:24 +00:00
akwizgran
60a8b03344 Upgrade Android Gradle plugin to 3.2.1. 2018-10-17 12:34:13 +01:00
58 changed files with 2147 additions and 126 deletions

View File

@@ -9,8 +9,8 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 26
versionCode 10102
versionName "1.1.2"
versionCode 10103
versionName "1.1.3"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

View File

@@ -1,34 +1,34 @@
dependencyVerification {
verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
'com.android.tools.analytics-library:protos:26.2.0:protos-26.2.0.jar:306784ac579be37f64f110204ea3fad8568e09e10817bb574bac0fc71b6402c5',
'com.android.tools.analytics-library:shared:26.2.0:shared-26.2.0.jar:10782c593b7edcaf63cf79f7179c4652fee1c01298e0d81d888eea5ea1c320d4',
'com.android.tools.analytics-library:tracker:26.2.0:tracker-26.2.0.jar:74e3cf587bcd6f5f14a6cddb8123c6940baf9947582f5356c25dd852c4726564',
'com.android.tools.build:aapt2:3.2.0-4818971:aapt2-3.2.0-4818971-linux.jar:0d86e045e9eb009ab8add53cf0e5c0f48674e8c8a4edd3b19fc978ea38508bb1',
'com.android.tools.build:apksig:3.2.0:apksig-3.2.0.jar:d393b14a2f85a4052645fa0b59e111b46dea0fd59a934bec6cf1e997a0640e30',
'com.android.tools.build:apkzlib:3.2.0:apkzlib-3.2.0.jar:d8a3404d63a1037207bc2eb472209c606f8143d201f484da9dacd04ffa538c31',
'com.android.tools.build:builder-model:3.2.0:builder-model-3.2.0.jar:7deb28465f2303fdc9df58ba83d080193edefe1624784d3ce9e0a0949ae05a49',
'com.android.tools.build:builder-test-api:3.2.0:builder-test-api-3.2.0.jar:b7c6676da0793240f16874c4008f12ed3ed1bc56a4a73f5e760044024ac9a02a',
'com.android.tools.build:builder:3.2.0:builder-3.2.0.jar:191df45703f615a02db16ce5ce78489f1ec91f9b83e677bec5de29de765e326d',
'com.android.tools.build:gradle-api:3.2.0:gradle-api-3.2.0.jar:5645c5ee4138b1354b24bed43e005964296a5f1ef48b6772ed9800159dcc2830',
'com.android.tools.build:manifest-merger:26.2.0:manifest-merger-26.2.0.jar:b821830bf067852688673cae01fb571c04da094985a3894878070b50f0587edf',
'com.android.tools.ddms:ddmlib:26.2.0:ddmlib-26.2.0.jar:ff9b6951698a6ec005b25e5e3b5f7fe960f566131ba8fcdf713392e10cc2dd50',
'com.android.tools.external.com-intellij:intellij-core:26.2.0:intellij-core-26.2.0.jar:03724f5aff30c8fff3d0b0c05e71f8dc21f246952fe1f3b5916bfb594e337bad',
'com.android.tools.external.com-intellij:kotlin-compiler:26.2.0:kotlin-compiler-26.2.0.jar:2f82e0c2687afe7c4941bdf6702d9a16d71d030a7ddb384a04a0537a361f4bb1',
'com.android.tools.external.org-jetbrains:uast:26.2.0:uast-26.2.0.jar:a439cafcddb2963620fd737005e3ce0560313362ede37e6c6eac5f10ff19eb94',
'com.android.tools.layoutlib:layoutlib-api:26.2.0:layoutlib-api-26.2.0.jar:5987488847729bad6a1e72cd5dcc46b68b61d8f05ef60858b87ed1d3b9bacc58',
'com.android.tools.lint:lint-api:26.2.0:lint-api-26.2.0.jar:aa4dc0399b6ae602286b9b106c661b50f6a8e1143c5949d3e7fd93c4a4d1513d',
'com.android.tools.lint:lint-checks:26.2.0:lint-checks-26.2.0.jar:c8c8dfdbd5c97a371e239b6000f4c6929bce315c2b13e690aaf34e3fcb045520',
'com.android.tools.lint:lint-gradle-api:26.2.0:lint-gradle-api-26.2.0.jar:befc1ad242e5e89e8977b2f799c465fee1b096ac645eba659acab832980599cd',
'com.android.tools.lint:lint-gradle:26.2.0:lint-gradle-26.2.0.jar:332d257f20537020fd268468f678ccfbcc0be494fa50526b63c532836dc7c5dd',
'com.android.tools.lint:lint-kotlin:26.2.0:lint-kotlin-26.2.0.jar:c149e09434aa24a6f7161dbd4b09f08c46f018cb0ee1ee2d1a32bf3b5015e081',
'com.android.tools.lint:lint:26.2.0:lint-26.2.0.jar:4df411ea5ef106780073c6e953ab591a55d52e94c9b1cd2f99970a124eca405e',
'com.android.tools:annotations:26.2.0:annotations-26.2.0.jar:af775f75168acbadb2ff61d6955e4fbeca9b1bd9f7a40b54ab7a489e30af45bb',
'com.android.tools:common:26.2.0:common-26.2.0.jar:3e60e91bc7c7a634bbfae3522c5e0b49e8a268a2d69904bed4bd55833f8bb0c9',
'com.android.tools:dvlib:26.2.0:dvlib-26.2.0.jar:c4d8fac5b8432e3676256ece4e25c3e55e8ef2da4298fb060cc6086d7e2235fc',
'com.android.tools:repository:26.2.0:repository-26.2.0.jar:9c9e320195e7e6d9bbdf9b5b0ed1d7e4da3625ad344902cb47b32c9ef8306d82',
'com.android.tools:sdk-common:26.2.0:sdk-common-26.2.0.jar:1e32745633177b28419041d9bd182bb99b51d1a7ad3f9b1c8b5292ed799656cd',
'com.android.tools:sdklib:26.2.0:sdklib-26.2.0.jar:08e2715f9abd3870df8e311f77ff6f3dae61193c1f6a2de78bf2e49c6ba4725a',
'com.android.tools.analytics-library:protos:26.2.1:protos-26.2.1.jar:2f371f5b1f551e85ab08be4d6a2873471b3d44afd1ebf6aa3298f3b796bf691f',
'com.android.tools.analytics-library:shared:26.2.1:shared-26.2.1.jar:4c1e4e705fa4d45f23aaea230557f6508155012d9c296337787c1d7b26a97f5a',
'com.android.tools.analytics-library:tracker:26.2.1:tracker-26.2.1.jar:4a624ecc976539f755ddb0bb8dfc2dd3d08326cfec59a098dbd70f701ca7fb75',
'com.android.tools.build:aapt2:3.2.1-4818971:aapt2-3.2.1-4818971-linux.jar:f431b6f96c91a2c155144b091a9c97d9805c589fe8efc9c930b6cd346cb60a1e',
'com.android.tools.build:apksig:3.2.1:apksig-3.2.1.jar:2b46f2feffea66037aab29e4261b2433c190194a6ef97b958511eb157f2ccba5',
'com.android.tools.build:apkzlib:3.2.1:apkzlib-3.2.1.jar:c39ad0313905932431fe81c8899c2cf39a4d92ad6c4edcaa4b25432f461452aa',
'com.android.tools.build:builder-model:3.2.1:builder-model-3.2.1.jar:a9f68e6abcec122f9cb5ad352d3f05a3eb03acbcdca95e4d25c16310c2c965ff',
'com.android.tools.build:builder-test-api:3.2.1:builder-test-api-3.2.1.jar:533ac6c2b5884bb54967a33791f2628dfdfac7981af39417a333b43d4379b6be',
'com.android.tools.build:builder:3.2.1:builder-3.2.1.jar:aedcbfd115dbe91d09b4113e66ef50589b558d0aa3b2f133b1d867c9b87fae83',
'com.android.tools.build:gradle-api:3.2.1:gradle-api-3.2.1.jar:57cf0ac5ac1dca8afdb3f62b94265e776e7dcfa641cc3844fb53a05193de208d',
'com.android.tools.build:manifest-merger:26.2.1:manifest-merger-26.2.1.jar:8830573263361035d38cfdcb51e2db94029c93865b21334f5fbf8a27984281a6',
'com.android.tools.ddms:ddmlib:26.2.1:ddmlib-26.2.1.jar:a4bf0a29a19980bf27269465cc782064656750b77c26728f82f9e148b705218b',
'com.android.tools.external.com-intellij:intellij-core:26.2.1:intellij-core-26.2.1.jar:4925ad1892c2687cb1a63427d440ef519c8c59215fefe0dc5d541d5d411fcafe',
'com.android.tools.external.com-intellij:kotlin-compiler:26.2.1:kotlin-compiler-26.2.1.jar:daa064fd708f340ee25fb9823c4c74104ac77f1370b76d907eb9ae6daec0a2ae',
'com.android.tools.external.org-jetbrains:uast:26.2.1:uast-26.2.1.jar:f10f7258d2ab9189562cc0f9ad838c0378fdba439229173390a99de02ebac75b',
'com.android.tools.layoutlib:layoutlib-api:26.2.1:layoutlib-api-26.2.1.jar:ddbf4fca123733fa011595b1cc1f4ac2937ed327b60990711fafc33c775c2ade',
'com.android.tools.lint:lint-api:26.2.1:lint-api-26.2.1.jar:3b57e739de567b98bc9ab56c2c0ee66fc026b4adf5843e8f9804ca0666a6f66e',
'com.android.tools.lint:lint-checks:26.2.1:lint-checks-26.2.1.jar:c86f4cc9aaee722ee4ad70062f7b5af91e9b041914af27adc09f545ab0fb3bc6',
'com.android.tools.lint:lint-gradle-api:26.2.1:lint-gradle-api-26.2.1.jar:2283e7af32e301565f2a797e531f0fc8c648077d457afb3ffdddbee638976c2f',
'com.android.tools.lint:lint-gradle:26.2.1:lint-gradle-26.2.1.jar:8fd90b2f3ec788cbb9801c07ab3e1ea2255aa31a6093157d7ea0ff13d0315ecb',
'com.android.tools.lint:lint-kotlin:26.2.1:lint-kotlin-26.2.1.jar:7a6a5d2b18f69cf1b900d857c2632b4c683713c533295933b8b759f8cab4a877',
'com.android.tools.lint:lint:26.2.1:lint-26.2.1.jar:7848b82ae988b90dee259ae7c7e86e05cbf52db6cd21c8bbd38ce7df08f3f8c5',
'com.android.tools:annotations:26.2.1:annotations-26.2.1.jar:7391c6a1e080174b96e64ceb078dadd31ce4d8a2d2fee0ec65be202126f90f24',
'com.android.tools:common:26.2.1:common-26.2.1.jar:a50aab2d6411ff68f4004a87c7e93d87d8e980a0ec3b352246549897ea2d78e5',
'com.android.tools:dvlib:26.2.1:dvlib-26.2.1.jar:72a83bf2839b1df9b1fbf67ba45d1bfb9f966cd774da4320c762b2be8f1688aa',
'com.android.tools:repository:26.2.1:repository-26.2.1.jar:fa74dae09103faef703df38550ad8fa244c5b6d1bf90d6198be932292b3d9cc1',
'com.android.tools:sdk-common:26.2.1:sdk-common-26.2.1.jar:759d4b292ca69a35cf961fca377b54158fc6c88108978006999442e80a011cf4',
'com.android.tools:sdklib:26.2.1:sdklib-26.2.1.jar:248df7ad5eac4aeb6f96c394c76760de4b7b89ac056e54d0c21a739368b91b45',
'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed',
'com.google.code.gson:gson:2.8.0:gson-2.8.0.jar:c6221763bd79c4f1c3dc7f750b5f29a0bb38b367b81314c4f71896e340c40825',
'com.google.dagger:dagger-compiler:2.0.2:dagger-compiler-2.0.2.jar:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
@@ -72,9 +72,10 @@ dependencyVerification {
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.jetbrains.kotlin:kotlin-reflect:1.2.0:kotlin-reflect-1.2.0.jar:4f48a872bad6e4d9c053f4ad610d11e4012ad7e58dc19a03dd5eb811f36069dd',
'org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.0:kotlin-stdlib-jre7-1.2.0.jar:c7a20fb951d437797afe8980aff6c1e5a03f310c661ba58ba1d4fa90cb0f2926',
'org.jetbrains.kotlin:kotlin-stdlib-jre8:1.2.0:kotlin-stdlib-jre8-1.2.0.jar:633524eee6ef1941f7cb1dab7ee3927b0a221ceee9047aeb5515f4cbb990c82a',
'org.jetbrains.kotlin:kotlin-stdlib:1.2.0:kotlin-stdlib-1.2.0.jar:05cfd9f5ac0b41910703a8925f7211a495909b27a2ffdd1c5106f1689aeafcd4',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.2.71:kotlin-stdlib-common-1.2.71.jar:63999687ff2fce8a592dd180ffbbf8f1d21c26b4044c55cdc74ff3cf3b3cf328',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.71:kotlin-stdlib-jdk7-1.2.71.jar:b136bd61b240e07d4d92ce00d3bd1dbf584400a7bf5f220c2f3cd22446858082',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.71:kotlin-stdlib-jdk8-1.2.71.jar:ac3c8abf47790b64b4f7e2509a53f0c145e061ac1612a597520535d199946ea9',
'org.jetbrains.kotlin:kotlin-stdlib:1.2.71:kotlin-stdlib-1.2.71.jar:4c895c270b87f5fec2a2796e1d89c15407ee821de961527c28588bb46afbc68b',
'org.jetbrains.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',

View File

@@ -153,4 +153,15 @@ 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

@@ -0,0 +1,59 @@
<?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>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,58 @@
<?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>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -2,6 +2,70 @@ 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 {
@@ -22,8 +86,8 @@ android {
defaultConfig {
minSdkVersion 15
targetSdkVersion 26
versionCode 10102
versionName "1.1.2"
versionCode 10103
versionName "1.1.3"
applicationId "org.briarproject.briar.android"
buildConfigField "String", "GitHash",
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
@@ -35,7 +99,7 @@ android {
buildTypes {
debug {
applicationIdSuffix ".debug"
applicationIdSuffix ".debug.qr" // prototype
shrinkResources false
minifyEnabled true
crunchPngs false
@@ -117,6 +181,9 @@ dependencies {
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.12.4'
implementation 'com.vanniktech:emoji-google:0.5.1'
// prototype
implementation "com.github.kobakei:MaterialFabSpeedDial:1.2.0"
annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'
compileOnly 'javax.annotation:jsr250-api:1.0'

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Briar Debug</string>
<string name="app_package" translatable="false">org.briarproject.briar.android.debug</string>
<string name="app_name" translatable="false">Briar Debug (Full)</string>
<string name="app_package" translatable="false">org.briarproject.briar.android.debug.qr</string>
</resources>

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="org.briarproject.briar"
xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
<uses-feature android:name="android.hardware.camera" android:required="false"/>
@@ -27,7 +28,8 @@
android:label="@string/app_name"
android:logo="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/BriarTheme">
android:theme="@style/BriarTheme"
tools:ignore="GoogleAppIndexingWarning">
<receiver
android:name="org.briarproject.briar.android.login.SignInReminderReceiver"
@@ -411,5 +413,29 @@
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,9 +15,14 @@ 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.ContactQrCodeInputFragment;
import org.briarproject.briar.android.contact.ContactQrCodeOutputFragment;
import org.briarproject.briar.android.contact.ConversationActivity;
import org.briarproject.briar.android.contact.PendingRequestsActivity;
import org.briarproject.briar.android.forum.CreateForumActivity;
import org.briarproject.briar.android.forum.ForumActivity;
import org.briarproject.briar.android.forum.ForumListFragment;
@@ -167,6 +172,10 @@ public interface ActivityComponent {
void inject(UnlockActivity activity);
void inject(ContactLinkExchangeActivity activity);
void inject(PendingRequestsActivity activity);
// Fragments
void inject(AuthorNameFragment fragment);
@@ -211,4 +220,11 @@ public interface ActivityComponent {
void inject(ScreenFilterDialogFragment fragment);
void inject(ContactExchangeErrorFragment fragment);
void inject(ContactLinkExchangeFragment fragment);
void inject(ContactQrCodeOutputFragment fragment);
void inject(ContactQrCodeInputFragment fragment);
}

View File

@@ -38,6 +38,7 @@ import java.util.List;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.arch.lifecycle.Lifecycle.State.STARTED;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
@@ -148,6 +149,7 @@ public abstract class BaseActivity extends AppCompatActivity
}
public void showNextFragment(BaseFragment f) {
if (!getLifecycle().getCurrentState().isAtLeast(STARTED)) return;
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.step_next_in,
R.anim.step_previous_out, R.anim.step_previous_in,

View File

@@ -0,0 +1,184 @@
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.db.DbException;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
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.MINUTES;
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
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));
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) {
long timestamp = clock.currentTimeMillis();
try {
messagingManager.addNewPendingContact(name, timestamp);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
AlarmManager alarmManager =
(AlarmManager) requireNonNull(getSystemService(ALARM_SERVICE));
double random = getPseudoRandom(link, OUR_LINK.replace("briar://", ""));
long m = MINUTES.toMillis(1);
long fromNow = (long) (-m * Math.log(random));
LOG.info("Delay " + fromNow + " ms based on seed " + random);
long triggerAt = elapsedRealtime() + fromNow;
Intent i = new Intent(this, ContactLinkExchangeActivity.class);
i.setAction("addContact");
i.setFlags(FLAG_ACTIVITY_NEW_TASK);
i.putExtra("name", name);
i.putExtra("timestamp", timestamp);
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) {
if (lifecycleManager.getLifecycleState() != RUNNING) {
LOG.info("Lifecycle not started, not adding contact " + name);
return;
}
LOG.info("Adding Contact " + name);
try {
messagingManager.removePendingContact(name, timestamp);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
}

View File

@@ -0,0 +1,225 @@
package org.briarproject.briar.android.contact;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Intent;
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.v7.app.AlertDialog;
import android.text.Editable;
import android.text.TextWatcher;
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.content.Intent.ACTION_SEND;
import static android.content.Intent.EXTRA_TEXT;
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
implements TextWatcher {
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, contactNameInput;
private Button addButton;
@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_title);
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);
addButton = v.findViewById(R.id.addButton);
addButton.setOnClickListener(view -> onAddButtonClicked());
contactNameInput = v.findViewById(R.id.contactNameInput);
contactNameInput.addTextChangedListener(this);
if (SDK_INT < 23) {
Drawable drawable =
wrap(contactNameInput.getCompoundDrawables()[0]);
setTint(drawable, color);
contactNameInput.setCompoundDrawables(drawable, null, null, null);
}
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);
}
linkInput.addTextChangedListener(this);
if (getArguments() != null)
linkInput.setText(getArguments().getString("link"));
Button 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 -> {
Intent i = new Intent(ACTION_SEND);
i.putExtra(EXTRA_TEXT, OUR_LINK);
i.setType("text/plain");
startActivity(i);
});
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();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
updateAddButtonState();
}
@Override
public void afterTextChanged(Editable s) {
}
private void updateAddButtonState() {
boolean validContactName = contactNameInput.getText() != null &&
contactNameInput.getText().length() > 0;
boolean briarLink = isBriarLink(linkInput.getText());
if (briarLink) {
linkInputLayout.setErrorEnabled(false);
} else {
linkInputLayout.setError("Invalid link");
}
addButton.setEnabled(validContactName && briarLink);
}
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 onAddButtonClicked() {
ContactLinkExchangeActivity activity = getCastActivity();
if (activity == null) return;
String linkText = getLink();
if (linkText == null) throw new AssertionError();
if (OUR_LINK.equals("briar://" + linkText)) {
new AlertDialog.Builder(activity, R.style.BriarDialogTheme_Neutral)
.setMessage(
"Add the link you get from your contact, not your own link.")
.setNeutralButton(R.string.ok,
(dialog, which) -> {
linkInput.setText(null);
dialog.cancel();
})
.show();
return;
}
activity.addFakeRequest(contactNameInput.getText().toString(),
linkText);
Intent intent = new Intent(activity, PendingRequestsActivity.class);
startActivity(intent);
finish();
}
}

View File

@@ -2,20 +2,22 @@ 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;
@@ -37,17 +39,24 @@ import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.messaging.ConversationManager;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.MessagingManager.PendingContact;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.messaging.event.PrivateMessageReceivedEvent;
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;
@@ -58,7 +67,8 @@ import static org.briarproject.briar.android.contact.ConversationActivity.CONTAC
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ContactListFragment extends BaseFragment implements EventListener {
public class ContactListFragment extends BaseFragment implements EventListener,
OnMenuItemClickListener {
public static final String TAG = ContactListFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
@@ -70,8 +80,13 @@ 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
@@ -104,7 +119,12 @@ public class ContactListFragment extends BaseFragment implements EventListener {
getActivity().setTitle(R.string.contact_list_button);
View contentView = inflater.inflate(R.layout.list, container, false);
View contentView =
inflater.inflate(R.layout.fragment_contact_list, container,
false);
FabSpeedDial speedDialView = contentView.findViewById(R.id.speedDial);
speedDialView.addOnMenuItemClickListener(this);
OnContactClickListener<ContactListItem> onContactClickListener =
(view, item) -> {
@@ -143,26 +163,32 @@ 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 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:
public void onMenuItemClick(FloatingActionButton fab, @Nullable TextView v,
int itemId) {
switch (itemId) {
case R.id.action_add_contact_nearby:
Intent intent =
new Intent(getContext(), ContactExchangeActivity.class);
startActivity(intent);
return true;
return;
case R.id.action_add_contact_remotely:
startActivity(new Intent(getContext(),
ContactLinkExchangeActivity.class));
return;
default:
return super.onOptionsItemSelected(item);
return;
}
}
@@ -174,6 +200,26 @@ 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
@@ -248,6 +294,11 @@ public class ContactListFragment extends BaseFragment implements EventListener {
PrivateMessageHeader h = p.getMessageHeader();
updateItem(p.getContactId(), h);
}
// TODO remove
else if (e instanceof ContactAddedEvent) {
checkForPendingContacts();
}
}
private void updateItem(ContactId c, PrivateMessageHeader h) {

View File

@@ -0,0 +1,185 @@
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;
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) {
// 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_toast,
LENGTH_LONG).show();
cancel();
}
}
}
}
private void requestPermission() {
requestPermissions(new String[] {CAMERA}, REQUEST_PERMISSION_CAMERA);
}
@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

@@ -0,0 +1,54 @@
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

@@ -0,0 +1,122 @@
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;
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);
}
@Override
public void onStart() {
super.onStart();
eventBus.addListener(this);
runOnDbThread(() -> {
try {
Collection<PendingContact> contacts =
messagingManager.getPendingContacts();
addPendingContacts(contacts);
} catch (DbException e) {
e.printStackTrace();
}
});
}
@Override
protected void onStop() {
super.onStop();
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

@@ -0,0 +1,67 @@
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

@@ -0,0 +1,36 @@
package org.briarproject.briar.android.contact;
import android.support.v7.widget.RecyclerView.ViewHolder;
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 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;
public PendingRequestsViewHolder(View v) {
super(v);
avatar = v.findViewById(R.id.avatar);
name = v.findViewById(R.id.name);
time = v.findViewById(R.id.time);
}
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()));
}
}

View File

@@ -2,7 +2,7 @@ package org.briarproject.briar.android.keyagreement;
import java.io.IOException;
class CameraException extends IOException {
public 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
interface PreviewConsumer {
public 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
class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
private static final Logger LOG =
Logger.getLogger(QrCodeDecoder.class.getName());
@@ -40,7 +40,7 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
private Camera camera = null;
private int cameraIndex = 0;
QrCodeDecoder(ResultCallback callback) {
public QrCodeDecoder(ResultCallback callback) {
this.callback = callback;
}
@@ -73,8 +73,7 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
if (data.length == size.width * size.height * 3 / 2) {
CameraInfo info = new CameraInfo();
Camera.getCameraInfo(cameraIndex, info);
new DecoderTask(data, size.width, size.height,
info.orientation).execute();
new DecoderTask(data, size.width, size.height).execute();
} else {
// Camera parameters have changed - ask for a new preview
LOG.info("Preview size does not match camera parameters");
@@ -91,19 +90,18 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
private class DecoderTask extends AsyncTask<Void, Void, Void> {
private final byte[] data;
private final int width, height, orientation;
private final int width;
private final int height;
private DecoderTask(byte[] data, int width, int height,
int orientation) {
private DecoderTask(byte[] data, int width, int height) {
this.data = data;
this.width = width;
this.height = height;
this.orientation = orientation;
}
@Override
protected Void doInBackground(Void... params) {
BinaryBitmap bitmap = binarize(data, width, height, orientation);
BinaryBitmap bitmap = binarize(data, width, height);
Result result;
try {
result = reader.decode(bitmap,
@@ -127,21 +125,14 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
}
}
private static BinaryBitmap binarize(byte[] data, int width, int height,
int orientation) {
// Crop to a square at the top (portrait) or left (landscape) of the
// screen - this will be faster to decode and should include
// everything visible in the viewfinder
int crop = Math.min(width, height);
int left = orientation >= 180 ? width - crop : 0;
int top = orientation >= 180 ? height - crop : 0;
private static BinaryBitmap binarize(byte[] data, int width, int height) {
LuminanceSource src = new PlanarYUVLuminanceSource(data, width,
height, left, top, crop, crop, false);
height, 0, 0, width, height, false);
return new BinaryBitmap(new HybridBinarizer(src));
}
@NotNullByDefault
interface ResultCallback {
public 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
class QrCodeUtils {
public class QrCodeUtils {
private static final Logger LOG =
Logger.getLogger(QrCodeUtils.class.getName());
@Nullable
static Bitmap createQrCode(DisplayMetrics dm, String input) {
public static Bitmap createQrCode(DisplayMetrics dm, String input) {
int smallestDimen = Math.min(dm.widthPixels, dm.heightPixels);
try {
// Generate QR code

View File

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

View File

@@ -0,0 +1,9 @@
<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

@@ -0,0 +1,10 @@
<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

@@ -0,0 +1,10 @@
<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

@@ -0,0 +1,9 @@
<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="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

@@ -0,0 +1,12 @@
<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

@@ -0,0 +1,12 @@
<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

@@ -0,0 +1,5 @@
<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

@@ -0,0 +1,5 @@
<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

@@ -0,0 +1,10 @@
<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

@@ -0,0 +1,9 @@
<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

@@ -0,0 +1,10 @@
<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

@@ -0,0 +1,37 @@
<?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

@@ -0,0 +1,189 @@
<?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.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/margin_large">
<android.support.design.widget.TextInputLayout
android:id="@+id/contactNameLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:errorEnabled="true"
app:hintEnabled="false"
app:layout_constraintBottom_toTopOf="@+id/addButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/pasteButton">
<android.support.design.widget.TextInputEditText
android:id="@+id/contactNameInput"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:drawableLeft="@drawable/ic_person"
android:drawablePadding="8dp"
android:drawableStart="@drawable/ic_person"
android:drawableTint="?attr/colorControlNormal"
android:hint="@string/contact_name_hint"
android:importantForAutofill="no"
android:inputType="text|textCapWords"/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/linkInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:errorEnabled="true"
app:hintEnabled="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/copyButton">
<android.support.design.widget.TextInputEditText
android:id="@+id/linkInput"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:drawableLeft="@drawable/ic_link"
android:drawablePadding="8dp"
android:drawableStart="@drawable/ic_link"
android:drawableTint="?attr/colorControlNormal"
android:hint="@string/contact_link_hint"
android:importantForAutofill="no"
android:inputType="textUri"/>
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/pasteButton"
style="@style/BriarButtonFlat.Positive.Tiny"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:drawableLeft="@drawable/ic_content_paste"
android:drawablePadding="8dp"
android:drawableStart="@drawable/ic_content_paste"
android:text="@string/paste_button"
app:layout_constraintBottom_toTopOf="@+id/contactNameLayout"
app:layout_constraintEnd_toStartOf="@id/scanCodeButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linkInputLayout"/>
<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="visible"
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="visible"/>
<TextView
android:id="@+id/linkIntro"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="left|start"
android:text="@string/send_link_instructions"
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_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_constraintBottom_toTopOf="@+id/copyButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linkIntro"
tools:text="briar://scnsdflamslkfjgluoblmksdfbwevlewajfdlkjewwhqliafskfjhskdjhvoieiv"
/>
<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_constraintBottom_toTopOf="@+id/linkInputLayout"
app:layout_constraintEnd_toStartOf="@id/shareButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linkView"/>
<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/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="visible"
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="visible"/>
<Button
android:id="@+id/addButton"
style="@style/BriarButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="16dp"
android:enabled="false"
android:text="@string/add_contact_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/contactNameLayout"
tools:enabled="true"/>
</android.support.constraint.ConstraintLayout>
</ScrollView>

View File

@@ -0,0 +1,95 @@
<?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

@@ -0,0 +1,87 @@
<?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/send_link_instructions"
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

@@ -0,0 +1,24 @@
<?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

@@ -0,0 +1,53 @@
<?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

@@ -0,0 +1,13 @@
<?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,81 @@
<?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/add_contact_remote_connecting"
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,9 +4,17 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_add_contact"
android:icon="@drawable/ic_add_white"
android:title="@string/add_contact_title"
app:showAsAction="ifRoom"/>
android:id="@+id/action_add_contact_nearby"
android:icon="@drawable/ic_add_nearby"
android:orderInCategory="3"
android:title="@string/add_contact_nearby_title"
app:showAsAction="never"/>
<item
android:id="@+id/action_add_contact_remotely"
android:icon="@drawable/ic_link"
android:orderInCategory="2"
android:title="@string/add_contact_remotely_title"
app:showAsAction="never"/>
</menu>

View File

@@ -151,6 +151,26 @@
<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>
<string name="add_contact_nearby_title">Add Contact Nearby</string>
<string name="add_contact_remotely_title">Add Contact with Link</string>
<string name="contact_name_hint">Give contact a nickname</string>
<string name="contact_link_hint">Your contact\'s link</string>
<string name="paste_button">Paste</string>
<string name="scan_qr_code_button">Scan 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_instructions">Give this link to your contact:</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>
<!-- 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

@@ -53,34 +53,35 @@ dependencyVerification {
'com.android.support:transition:28.0.0:transition-28.0.0.aar:45d09fc51284c17bbab300f5122512ac7d7348a6d23bda2051648bbe76cc9aa5',
'com.android.support:versionedparcelable:28.0.0:versionedparcelable-28.0.0.aar:60eb1cb08f71b65c3f6123135e03ebeb5930b5e126e1e5b2ac91b386908c9d02',
'com.android.support:viewpager:28.0.0:viewpager-28.0.0.aar:013c4c53058758ec104dbae970be58159f75dfe342ba8b937d15ff5282e35ffc',
'com.android.tools.analytics-library:protos:26.2.0:protos-26.2.0.jar:306784ac579be37f64f110204ea3fad8568e09e10817bb574bac0fc71b6402c5',
'com.android.tools.analytics-library:shared:26.2.0:shared-26.2.0.jar:10782c593b7edcaf63cf79f7179c4652fee1c01298e0d81d888eea5ea1c320d4',
'com.android.tools.analytics-library:tracker:26.2.0:tracker-26.2.0.jar:74e3cf587bcd6f5f14a6cddb8123c6940baf9947582f5356c25dd852c4726564',
'com.android.tools.build:aapt2:3.2.0-4818971:aapt2-3.2.0-4818971-linux.jar:0d86e045e9eb009ab8add53cf0e5c0f48674e8c8a4edd3b19fc978ea38508bb1',
'com.android.tools.build:apksig:3.2.0:apksig-3.2.0.jar:d393b14a2f85a4052645fa0b59e111b46dea0fd59a934bec6cf1e997a0640e30',
'com.android.tools.build:apkzlib:3.2.0:apkzlib-3.2.0.jar:d8a3404d63a1037207bc2eb472209c606f8143d201f484da9dacd04ffa538c31',
'com.android.tools.build:builder-model:3.2.0:builder-model-3.2.0.jar:7deb28465f2303fdc9df58ba83d080193edefe1624784d3ce9e0a0949ae05a49',
'com.android.tools.build:builder-test-api:3.2.0:builder-test-api-3.2.0.jar:b7c6676da0793240f16874c4008f12ed3ed1bc56a4a73f5e760044024ac9a02a',
'com.android.tools.build:builder:3.2.0:builder-3.2.0.jar:191df45703f615a02db16ce5ce78489f1ec91f9b83e677bec5de29de765e326d',
'com.android.tools.build:gradle-api:3.2.0:gradle-api-3.2.0.jar:5645c5ee4138b1354b24bed43e005964296a5f1ef48b6772ed9800159dcc2830',
'com.android.tools.build:manifest-merger:26.2.0:manifest-merger-26.2.0.jar:b821830bf067852688673cae01fb571c04da094985a3894878070b50f0587edf',
'com.android.tools.ddms:ddmlib:26.2.0:ddmlib-26.2.0.jar:ff9b6951698a6ec005b25e5e3b5f7fe960f566131ba8fcdf713392e10cc2dd50',
'com.android.tools.external.com-intellij:intellij-core:26.2.0:intellij-core-26.2.0.jar:03724f5aff30c8fff3d0b0c05e71f8dc21f246952fe1f3b5916bfb594e337bad',
'com.android.tools.external.com-intellij:kotlin-compiler:26.2.0:kotlin-compiler-26.2.0.jar:2f82e0c2687afe7c4941bdf6702d9a16d71d030a7ddb384a04a0537a361f4bb1',
'com.android.tools.external.org-jetbrains:uast:26.2.0:uast-26.2.0.jar:a439cafcddb2963620fd737005e3ce0560313362ede37e6c6eac5f10ff19eb94',
'com.android.tools.layoutlib:layoutlib-api:26.2.0:layoutlib-api-26.2.0.jar:5987488847729bad6a1e72cd5dcc46b68b61d8f05ef60858b87ed1d3b9bacc58',
'com.android.tools.lint:lint-api:26.2.0:lint-api-26.2.0.jar:aa4dc0399b6ae602286b9b106c661b50f6a8e1143c5949d3e7fd93c4a4d1513d',
'com.android.tools.lint:lint-checks:26.2.0:lint-checks-26.2.0.jar:c8c8dfdbd5c97a371e239b6000f4c6929bce315c2b13e690aaf34e3fcb045520',
'com.android.tools.lint:lint-gradle-api:26.2.0:lint-gradle-api-26.2.0.jar:befc1ad242e5e89e8977b2f799c465fee1b096ac645eba659acab832980599cd',
'com.android.tools.lint:lint-gradle:26.2.0:lint-gradle-26.2.0.jar:332d257f20537020fd268468f678ccfbcc0be494fa50526b63c532836dc7c5dd',
'com.android.tools.lint:lint-kotlin:26.2.0:lint-kotlin-26.2.0.jar:c149e09434aa24a6f7161dbd4b09f08c46f018cb0ee1ee2d1a32bf3b5015e081',
'com.android.tools.lint:lint:26.2.0:lint-26.2.0.jar:4df411ea5ef106780073c6e953ab591a55d52e94c9b1cd2f99970a124eca405e',
'com.android.tools:annotations:26.2.0:annotations-26.2.0.jar:af775f75168acbadb2ff61d6955e4fbeca9b1bd9f7a40b54ab7a489e30af45bb',
'com.android.tools:common:26.2.0:common-26.2.0.jar:3e60e91bc7c7a634bbfae3522c5e0b49e8a268a2d69904bed4bd55833f8bb0c9',
'com.android.tools:dvlib:26.2.0:dvlib-26.2.0.jar:c4d8fac5b8432e3676256ece4e25c3e55e8ef2da4298fb060cc6086d7e2235fc',
'com.android.tools:repository:26.2.0:repository-26.2.0.jar:9c9e320195e7e6d9bbdf9b5b0ed1d7e4da3625ad344902cb47b32c9ef8306d82',
'com.android.tools:sdk-common:26.2.0:sdk-common-26.2.0.jar:1e32745633177b28419041d9bd182bb99b51d1a7ad3f9b1c8b5292ed799656cd',
'com.android.tools:sdklib:26.2.0:sdklib-26.2.0.jar:08e2715f9abd3870df8e311f77ff6f3dae61193c1f6a2de78bf2e49c6ba4725a',
'com.android.tools.analytics-library:protos:26.2.1:protos-26.2.1.jar:2f371f5b1f551e85ab08be4d6a2873471b3d44afd1ebf6aa3298f3b796bf691f',
'com.android.tools.analytics-library:shared:26.2.1:shared-26.2.1.jar:4c1e4e705fa4d45f23aaea230557f6508155012d9c296337787c1d7b26a97f5a',
'com.android.tools.analytics-library:tracker:26.2.1:tracker-26.2.1.jar:4a624ecc976539f755ddb0bb8dfc2dd3d08326cfec59a098dbd70f701ca7fb75',
'com.android.tools.build:aapt2:3.2.1-4818971:aapt2-3.2.1-4818971-linux.jar:f431b6f96c91a2c155144b091a9c97d9805c589fe8efc9c930b6cd346cb60a1e',
'com.android.tools.build:apksig:3.2.1:apksig-3.2.1.jar:2b46f2feffea66037aab29e4261b2433c190194a6ef97b958511eb157f2ccba5',
'com.android.tools.build:apkzlib:3.2.1:apkzlib-3.2.1.jar:c39ad0313905932431fe81c8899c2cf39a4d92ad6c4edcaa4b25432f461452aa',
'com.android.tools.build:builder-model:3.2.1:builder-model-3.2.1.jar:a9f68e6abcec122f9cb5ad352d3f05a3eb03acbcdca95e4d25c16310c2c965ff',
'com.android.tools.build:builder-test-api:3.2.1:builder-test-api-3.2.1.jar:533ac6c2b5884bb54967a33791f2628dfdfac7981af39417a333b43d4379b6be',
'com.android.tools.build:builder:3.2.1:builder-3.2.1.jar:aedcbfd115dbe91d09b4113e66ef50589b558d0aa3b2f133b1d867c9b87fae83',
'com.android.tools.build:gradle-api:3.2.1:gradle-api-3.2.1.jar:57cf0ac5ac1dca8afdb3f62b94265e776e7dcfa641cc3844fb53a05193de208d',
'com.android.tools.build:manifest-merger:26.2.1:manifest-merger-26.2.1.jar:8830573263361035d38cfdcb51e2db94029c93865b21334f5fbf8a27984281a6',
'com.android.tools.ddms:ddmlib:26.2.1:ddmlib-26.2.1.jar:a4bf0a29a19980bf27269465cc782064656750b77c26728f82f9e148b705218b',
'com.android.tools.external.com-intellij:intellij-core:26.2.1:intellij-core-26.2.1.jar:4925ad1892c2687cb1a63427d440ef519c8c59215fefe0dc5d541d5d411fcafe',
'com.android.tools.external.com-intellij:kotlin-compiler:26.2.1:kotlin-compiler-26.2.1.jar:daa064fd708f340ee25fb9823c4c74104ac77f1370b76d907eb9ae6daec0a2ae',
'com.android.tools.external.org-jetbrains:uast:26.2.1:uast-26.2.1.jar:f10f7258d2ab9189562cc0f9ad838c0378fdba439229173390a99de02ebac75b',
'com.android.tools.layoutlib:layoutlib-api:26.2.1:layoutlib-api-26.2.1.jar:ddbf4fca123733fa011595b1cc1f4ac2937ed327b60990711fafc33c775c2ade',
'com.android.tools.lint:lint-api:26.2.1:lint-api-26.2.1.jar:3b57e739de567b98bc9ab56c2c0ee66fc026b4adf5843e8f9804ca0666a6f66e',
'com.android.tools.lint:lint-checks:26.2.1:lint-checks-26.2.1.jar:c86f4cc9aaee722ee4ad70062f7b5af91e9b041914af27adc09f545ab0fb3bc6',
'com.android.tools.lint:lint-gradle-api:26.2.1:lint-gradle-api-26.2.1.jar:2283e7af32e301565f2a797e531f0fc8c648077d457afb3ffdddbee638976c2f',
'com.android.tools.lint:lint-gradle:26.2.1:lint-gradle-26.2.1.jar:8fd90b2f3ec788cbb9801c07ab3e1ea2255aa31a6093157d7ea0ff13d0315ecb',
'com.android.tools.lint:lint-kotlin:26.2.1:lint-kotlin-26.2.1.jar:7a6a5d2b18f69cf1b900d857c2632b4c683713c533295933b8b759f8cab4a877',
'com.android.tools.lint:lint:26.2.1:lint-26.2.1.jar:7848b82ae988b90dee259ae7c7e86e05cbf52db6cd21c8bbd38ce7df08f3f8c5',
'com.android.tools:annotations:26.2.1:annotations-26.2.1.jar:7391c6a1e080174b96e64ceb078dadd31ce4d8a2d2fee0ec65be202126f90f24',
'com.android.tools:common:26.2.1:common-26.2.1.jar:a50aab2d6411ff68f4004a87c7e93d87d8e980a0ec3b352246549897ea2d78e5',
'com.android.tools:dvlib:26.2.1:dvlib-26.2.1.jar:72a83bf2839b1df9b1fbf67ba45d1bfb9f966cd774da4320c762b2be8f1688aa',
'com.android.tools:repository:26.2.1:repository-26.2.1.jar:fa74dae09103faef703df38550ad8fa244c5b6d1bf90d6198be932292b3d9cc1',
'com.android.tools:sdk-common:26.2.1:sdk-common-26.2.1.jar:759d4b292ca69a35cf961fca377b54158fc6c88108978006999442e80a011cf4',
'com.android.tools:sdklib:26.2.1:sdklib-26.2.1.jar:248df7ad5eac4aeb6f96c394c76760de4b7b89ac056e54d0c21a739368b91b45',
'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',
@@ -161,9 +162,10 @@ dependencyVerification {
'org.hamcrest:hamcrest-integration:1.3:hamcrest-integration-1.3.jar:70f418efbb506c5155da5f9a5a33262ea08a9e4d7fea186aa9015c41a7224ac2',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.jetbrains.kotlin:kotlin-reflect:1.2.0:kotlin-reflect-1.2.0.jar:4f48a872bad6e4d9c053f4ad610d11e4012ad7e58dc19a03dd5eb811f36069dd',
'org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.0:kotlin-stdlib-jre7-1.2.0.jar:c7a20fb951d437797afe8980aff6c1e5a03f310c661ba58ba1d4fa90cb0f2926',
'org.jetbrains.kotlin:kotlin-stdlib-jre8:1.2.0:kotlin-stdlib-jre8-1.2.0.jar:633524eee6ef1941f7cb1dab7ee3927b0a221ceee9047aeb5515f4cbb990c82a',
'org.jetbrains.kotlin:kotlin-stdlib:1.2.0:kotlin-stdlib-1.2.0.jar:05cfd9f5ac0b41910703a8925f7211a495909b27a2ffdd1c5106f1689aeafcd4',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.2.71:kotlin-stdlib-common-1.2.71.jar:63999687ff2fce8a592dd180ffbbf8f1d21c26b4044c55cdc74ff3cf3b3cf328',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.71:kotlin-stdlib-jdk7-1.2.71.jar:b136bd61b240e07d4d92ce00d3bd1dbf584400a7bf5f220c2f3cd22446858082',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.71:kotlin-stdlib-jdk8-1.2.71.jar:ac3c8abf47790b64b4f7e2509a53f0c145e061ac1612a597520535d199946ea9',
'org.jetbrains.kotlin:kotlin-stdlib:1.2.71:kotlin-stdlib-1.2.71.jar:4c895c270b87f5fec2a2796e1d89c15407ee821de961527c28588bb46afbc68b',
'org.jetbrains.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
@@ -190,6 +192,7 @@ dependencyVerification {
'org.robolectric:shadows-framework:3.8:shadows-framework-3.8.jar:83548db7249edf1af87e1a1f4d8f4eec3e85d6220161da601e6f6398476911b2',
'org.robolectric:shadows-support-v4:3.3.2:shadows-support-v4-3.3.2.jar:6f689264738266e70fe08db7c04b7b5a75155994f4e3f7f311960d90486bf005',
'org.robolectric:utils:3.8:utils-3.8.jar:e945d04d40e37554e02d4be1bc3abf9bede45375c843aa36d10ccb6b63edbf34',
'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',
'xmlpull:xmlpull:1.1.3.1:xmlpull-1.1.3.1.jar:34e08ee62116071cbb69c0ed70d15a7a5b208d62798c59f2120bb8929324cb63',

View File

@@ -8,9 +8,31 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.messaging.ConversationManager.ConversationClient;
import java.util.Collection;
@NotNullByDefault
public interface MessagingManager extends ConversationClient {
// TODO remove (only for prototype)
void addNewPendingContact(String name, long timestamp) throws DbException;
void removePendingContact(String name, long timestamp) throws DbException;
Collection<PendingContact> getPendingContacts() throws DbException;
class PendingContact {
private final String name;
private final long timestamp;
public PendingContact(String name, long timestamp) {
this.name = name;
this.timestamp = timestamp;
}
public String getName() {
return name;
}
public long getTimestamp() {
return timestamp;
}
}
/**
* The unique ID of the messaging client.
*/

View File

@@ -5,13 +5,19 @@ 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;
@@ -23,6 +29,7 @@ import org.briarproject.bramble.api.sync.MessageStatus;
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.introduction.event.IntroductionSucceededEvent;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
@@ -31,11 +38,13 @@ import org.briarproject.briar.client.ConversationClientImpl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
@Immutable
@@ -46,14 +55,114 @@ 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) {
ContactGroupFactory contactGroupFactory,
ContactManager contactManager, IdentityManager identityManager,
AuthorFactory authorFactory) {
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)
throws DbException {
Transaction txn = db.startTransaction(false);
try {
BdfList list = getPendingContacts(txn);
BdfDictionary contact = new BdfDictionary();
contact.put("name", name);
contact.put("timestamp", timestamp);
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 void removePendingContact(String name, long timestamp) throws DbException {
Transaction txn = db.startTransaction(false);
try {
BdfList list = getPendingContacts(txn);
BdfDictionary contactDict = new BdfDictionary();
contactDict.put("name", name);
contactDict.put("timestamp", timestamp);
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();
Author remote = authorFactory
.createAuthor(name, new byte[MAX_PUBLIC_KEY_LENGTH]);
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);
} 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")));
}
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

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.headless
import com.fasterxml.jackson.databind.ObjectMapper
import dagger.Module
import dagger.Provides
import org.briarproject.bramble.api.crypto.CryptoComponent
@@ -113,4 +114,8 @@ internal class HeadlessModule(private val appDir: File) {
}
}
@Provides
@Singleton
internal fun provideObjectMapper() = ObjectMapper()
}

View File

@@ -115,10 +115,9 @@ constructor(
/**
* Returns a String from the JSON field or throws [BadRequestResponse] if null or empty.
*/
fun Context.getFromJson(field: String) : String {
fun Context.getFromJson(objectMapper: ObjectMapper, field: String) : String {
try {
// TODO use a static object mapper to avoid re-initializations
val jsonNode = ObjectMapper().readTree(body())
val jsonNode = objectMapper.readTree(body())
if (!jsonNode.hasNonNull(field)) throw BadRequestResponse("'$field' missing in JSON")
val result = jsonNode.get(field).asText()
if (result == null || result.isEmpty()) throw BadRequestResponse("'$field' empty in JSON")

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.headless.blogs
import com.fasterxml.jackson.databind.ObjectMapper
import io.javalin.BadRequestResponse
import io.javalin.Context
import org.briarproject.bramble.api.identity.IdentityManager
@@ -21,6 +22,7 @@ constructor(
private val blogManager: BlogManager,
private val blogPostFactory: BlogPostFactory,
private val identityManager: IdentityManager,
private val objectMapper: ObjectMapper,
private val clock: Clock
) : BlogController {
@@ -35,7 +37,7 @@ constructor(
}
override fun createPost(ctx: Context): Context {
val text = ctx.getFromJson("text")
val text = ctx.getFromJson(objectMapper, "text")
if (utf8IsTooLong(text, MAX_BLOG_POST_TEXT_LENGTH))
throw BadRequestResponse("Blog post text is too long")

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.headless.forums
import com.fasterxml.jackson.databind.ObjectMapper
import io.javalin.BadRequestResponse
import io.javalin.Context
import org.briarproject.bramble.util.StringUtils.utf8IsTooLong
@@ -14,14 +15,15 @@ import javax.inject.Singleton
@Singleton
internal class ForumControllerImpl
@Inject
constructor(private val forumManager: ForumManager) : ForumController {
constructor(private val forumManager: ForumManager, private val objectMapper: ObjectMapper) :
ForumController {
override fun list(ctx: Context): Context {
return ctx.json(forumManager.forums.output())
}
override fun create(ctx: Context): Context {
val name = ctx.getFromJson("name")
val name = ctx.getFromJson(objectMapper, "name")
if (utf8IsTooLong(name, MAX_FORUM_NAME_LENGTH))
throw BadRequestResponse("Forum name is too long")
return ctx.json(forumManager.addForum(name).output())

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.headless.messaging
import com.fasterxml.jackson.databind.ObjectMapper
import io.javalin.BadRequestResponse
import io.javalin.Context
import io.javalin.NotFoundResponse
@@ -45,6 +46,7 @@ constructor(
private val contactManager: ContactManager,
private val webSocketController: WebSocketController,
@DatabaseExecutor private val dbExecutor: Executor,
private val objectMapper: ObjectMapper,
private val clock: Clock
) : MessagingController, EventListener {
@@ -60,7 +62,7 @@ constructor(
override fun write(ctx: Context): Context {
val contact = getContact(ctx)
val message = ctx.getFromJson("text")
val message = ctx.getFromJson(objectMapper, "text")
if (utf8IsTooLong(message, MAX_PRIVATE_MESSAGE_TEXT_LENGTH))
throw BadRequestResponse("Message text is too long")

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.headless
import com.fasterxml.jackson.databind.ObjectMapper
import io.javalin.Context
import io.javalin.core.util.ContextUtil
import io.mockk.mockk
@@ -30,6 +31,8 @@ abstract class ControllerTest {
private val response = mockk<HttpServletResponse>(relaxed = true)
private val outputCtx = ContextUtil.init(request, response)
protected val objectMapper = ObjectMapper()
protected val group: Group = getGroup(getClientId(), 0)
protected val author: Author = getAuthor()
protected val localAuthor: LocalAuthor = getLocalAuthor()

View File

@@ -23,7 +23,7 @@ internal class BlogControllerTest : ControllerTest() {
private val blogPostFactory = mockk<BlogPostFactory>()
private val controller =
BlogControllerImpl(blogManager, blogPostFactory, identityManager, clock)
BlogControllerImpl(blogManager, blogPostFactory, identityManager, objectMapper, clock)
private val blog = Blog(group, author, false)
private val parentId: MessageId? = null

View File

@@ -16,7 +16,7 @@ internal class ForumControllerTest : ControllerTest() {
private val forumManager = mockk<ForumManager>()
private val controller = ForumControllerImpl(forumManager)
private val controller = ForumControllerImpl(forumManager, objectMapper)
private val forum = Forum(group, getRandomString(5), getRandomBytes(5))

View File

@@ -38,6 +38,7 @@ internal class MessagingControllerImplTest : ControllerTest() {
contactManager,
webSocketController,
dbExecutor,
objectMapper,
clock
)

View File

@@ -36,8 +36,15 @@ dependencyVerification {
'org.eclipse.jetty:jetty-util:9.4.12.v20180830:jetty-util-9.4.12.v20180830.jar:60ad53e118a3e7d10418b155b9944d90b2e4e4c732e53ef4f419473288d3f48c',
'org.eclipse.jetty:jetty-webapp:9.4.12.v20180830:jetty-webapp-9.4.12.v20180830.jar:5301e412a32bf7dddcfad458d952179597c61f8fd531c265873562725c3d4646',
'org.eclipse.jetty:jetty-xml:9.4.12.v20180830:jetty-xml-9.4.12.v20180830.jar:5b8298ab3d43ddaf0941d41f51b82c8ae23a247da055fa161b752ab9495155ed',
'org.jetbrains.kotlin:kotlin-android-extensions:1.2.70:kotlin-android-extensions-1.2.70.jar:534601f4b10bd175268a611ea370051f75db3377da11e9400430f01db7c70bd0',
'org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.2.70:kotlin-annotation-processing-gradle-1.2.70.jar:820da7e3637066c14eb3d54dc29cd6d4dc4a041ff603d0b15844403de47b7d12',
'org.jetbrains.kotlin:kotlin-build-common:1.2.70:kotlin-build-common-1.2.70.jar:6756d98108512b8a42013e453912e868777a20f05cac3f40af891e4058e94da6',
'org.jetbrains.kotlin:kotlin-compiler-embeddable:1.2.70:kotlin-compiler-embeddable-1.2.70.jar:8958d6f6ce4e49a6cecaaa9a1711a6b03df793fe066a74d88cf4958f20b0f10d',
'org.jetbrains.kotlin:kotlin-compiler-runner:1.2.70:kotlin-compiler-runner-1.2.70.jar:44654c5f86e2b36222cb8b09e5eeb0b252db1e83d258b27fb31c670c339e65e1',
'org.jetbrains.kotlin:kotlin-daemon-client:1.2.70:kotlin-daemon-client-1.2.70.jar:92b73c45670324ae6b81ccaa123a38eddd53adfe3a2b58938421d4d17a53f9e3',
'org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.2.70:kotlin-gradle-plugin-api-1.2.70.jar:a60b71d564795461e023956d8b3dd03d606d51ad1eb79e7c002dbce62a24378c',
'org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.2.70:kotlin-gradle-plugin-model-1.2.70.jar:a7fdb4b35e7537ad417e6b19595a1df7d676e8e9fa5a8a96dbcf1a40b577493b',
'org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.70:kotlin-gradle-plugin-1.2.70.jar:b9a96b3d39215b7fdcc8013476e70f86b8010277dd3639e58b42e6ffde1f3cab',
'org.jetbrains.kotlin:kotlin-reflect:1.2.41:kotlin-reflect-1.2.41.jar:1bab75771dfa2bb5949cd383ceaedf6f8d354fa0d677804fc5a39e320bab70d3',
'org.jetbrains.kotlin:kotlin-reflect:1.2.70:kotlin-reflect-1.2.70.jar:89ef46a458c5ee58b8460d0d22b0bb484eea0589ccffd59d650ef38bcb60e806',
'org.jetbrains.kotlin:kotlin-script-runtime:1.2.70:kotlin-script-runtime-1.2.70.jar:0124dfcf890e39250c3a4481cdd27f038d8321e93ce983730c4cbc10143eadc2',

View File

@@ -27,7 +27,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.0'
classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'net.ltgt.gradle:gradle-apt-plugin:0.18'
classpath 'ru.vyarus:gradle-animalsniffer-plugin:1.4.6'
classpath files('libs/gradle-witness.jar')