Merge branch '2065-transfer-data-ui' into '1802-sync-via-removable-storage'

Implement UI of transfer data feature

See merge request briar/briar!1486
This commit is contained in:
Torsten Grote
2021-07-02 18:07:57 +00:00
43 changed files with 1563 additions and 218 deletions

View File

@@ -12,4 +12,6 @@ public interface FeatureFlags {
boolean shouldEnableDisappearingMessages();
boolean shouldEnableConnectViaBluetooth();
boolean shouldEnableTransferData();
}

View File

@@ -63,8 +63,6 @@ abstract class RemovableDriveTaskImpl implements RemovableDriveTask {
synchronized (lock) {
observers.add(o);
state = this.state;
}
if (state.isFinished()) {
eventExecutor.execute(() -> o.accept(state));
}
}

View File

@@ -29,6 +29,11 @@ public class TestFeatureFlagModule {
public boolean shouldEnableConnectViaBluetooth() {
return true;
}
@Override
public boolean shouldEnableTransferData() {
return true;
}
};
}
}

View File

@@ -0,0 +1,35 @@
<?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="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="155" height="155"
viewBox="0 0 155 158.05972" fill="none" version="1.1" id="svg24"
sodipodi:docname="transfer_data.svg" inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
<defs id="defs11" />
<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="982"
id="namedview9" showgrid="false" inkscape:zoom="1.4523073" inkscape:cx="8.2820655"
inkscape:cy="-28.56208" inkscape:window-x="1920" inkscape:window-y="72"
inkscape:window-maximized="0" inkscape:current-layer="svg24" />
<metadata id="metadata30">
<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>
<g id="g844" transform="translate(0,3.0301033)">
<path id="path2" style="fill:#82c91e"
d="m 34.888672,24.835938 c -0.929975,-0.02143 -1.778628,0.612487 -1.994141,1.554687 -0.2463,1.0768 0.427106,2.148231 1.503907,2.394531 l 5.339843,1.222656 c -6.856612,4.77068 -12.781629,10.788147 -17.445312,17.767579 -5.4771,8.1968 -9.072016,17.49215 -10.541016,27.21875 -0.217,1.4367 0.827383,2.738037 2.271485,2.898437 1.4442,0.1604 2.74109,-0.880406 2.96289,-2.316406 1.3729,-8.8885 4.672941,-17.381253 9.681641,-24.876953 4.280712,-6.40624 9.717062,-11.928555 16.003906,-16.316407 l -1.271484,5.558594 c -0.2463,1.0768 0.427106,2.150185 1.503906,2.396485 1.0767,0.2463 2.150184,-0.427207 2.396484,-1.503907 l 2.076172,-9.080078 c 0.167435,-0.732007 0.107609,-1.457562 -0.109375,-2.123047 -0.03323,-0.347577 -0.135561,-0.692647 -0.314453,-1.017578 -0.353538,-0.6421 -0.935043,-1.078378 -1.585937,-1.257812 -0.310286,-0.16723 -0.63775,-0.310162 -0.998047,-0.392578 l -9.078125,-2.076172 c -0.134588,-0.03079 -0.269491,-0.04772 -0.402344,-0.05078 z m 76.982418,2.876953 c -0.836,0.0548 -1.63328,0.526718 -2.08203,1.316406 -0.717,1.2635 -0.27106,2.863965 0.96094,3.634765 8.143,5.095901 14.99795,12.028813 20.00195,20.257813 4.93587,8.11686 7.91993,17.252829 8.7461,26.689453 l -4.25,-3.394531 c -0.863,-0.6893 -2.12055,-0.548547 -2.81055,0.314453 -0.689,0.8631 -0.54855,2.121147 0.31445,2.810547 l 7.27735,5.8125 c 1.726,1.3788 4.24209,1.097194 5.62109,-0.628906 l 5.8125,-7.277344 c 0.69,-0.863 0.54855,-2.121147 -0.31445,-2.810547 -0.863,-0.6894 -2.12155,-0.548647 -2.81055,0.314453 l -3.58984,4.494141 c -0.8855,-10.27436 -4.12841,-20.220841 -9.50196,-29.056641 -5.469,-8.9943 -12.97353,-16.564369 -21.89453,-22.105469 -0.46312,-0.287512 -0.97886,-0.40397 -1.48047,-0.371093 z M 45.191406,137.83008 c -0.851573,0.0281 -1.670687,0.44648 -2.164062,1.20898 -0.7895,1.219 -0.44326,2.85471 0.80664,3.59571 9.034,5.356 19.211344,8.51779 29.714844,9.21679 10.5035,0.699 21.011881,-1.08626 30.675782,-5.19726 1.337,-0.569 1.89734,-2.14303 1.27734,-3.45703 -0.621,-1.313 -2.18834,-1.86969 -3.52734,-1.30469 -8.852401,3.73 -18.466473,5.34798 -28.076172,4.70898 -9.609701,-0.64 -18.922925,-3.51767 -27.203126,-8.38867 -0.469612,-0.27637 -0.992962,-0.39966 -1.503906,-0.38281 z" />
<path id="path8" style="fill:#8a9cb3"
d="M 68.474609,0 C 66.724209,0 65.046294,0.6958487 63.808594,1.9335938 62.570894,3.1713338 61.875,4.8491794 61.875,6.5996094 V 46.199219 c 0,1.7504 0.695894,3.430267 1.933594,4.667969 1.2377,1.237698 2.915615,1.933593 4.666015,1.933593 h 19.800782 c 1.7504,0 3.428315,-0.695895 4.666015,-1.933593 1.2377,-1.237702 1.933594,-2.917569 1.933594,-4.667969 V 6.5996094 c 0,-1.75043 -0.695894,-3.4282757 -1.933594,-4.6660156 C 91.703706,0.6958487 90.025791,0 88.275391,0 Z m 0,3.3007812 h 19.800782 c 0.8752,0 1.715084,0.3459738 2.333984,0.9648438 0.6188,0.61887 0.964844,1.4587744 0.964844,2.3339844 V 46.199219 c 0,0.8752 -0.346044,1.715084 -0.964844,2.333984 C 89.990475,49.152003 89.150591,49.5 88.275391,49.5 H 68.474609 c -0.8752,0 -1.715084,-0.347997 -2.333984,-0.966797 -0.6188,-0.6189 -0.964844,-1.458784 -0.964844,-2.333984 V 6.5996094 c 0,-0.87521 0.346044,-1.7151144 0.964844,-2.3339844 0.6189,-0.61887 1.458784,-0.9648437 2.333984,-0.9648438 z M 11.25,97 0,108.40039 v 22.79883 C 0,133.28922 1.6875,135 3.75,135 h 22.5 c 2.0625,0 3.75,-1.71078 3.75,-3.80078 V 100.80078 C 30,98.710782 28.3125,97 26.25,97 Z M 131,100.59961 c -0.796,0 -1.55909,0.31591 -2.12109,0.87891 -0.563,0.562 -0.87891,1.32509 -0.87891,2.12109 v 13.5 c -0.796,0 -1.55909,0.31591 -2.12109,0.87891 -0.563,0.562 -0.87891,1.32509 -0.87891,2.12109 v 16.5 h 3 v -16.5 h 24 v 16.5 h 3 v -16.5 c 0,-0.796 -0.31591,-1.55909 -0.87891,-2.12109 -0.562,-0.563 -1.32509,-0.87891 -2.12109,-0.87891 v -13.5 c 0,-0.796 -0.31591,-1.55909 -0.87891,-2.12109 -0.562,-0.563 -1.32509,-0.87891 -2.12109,-0.87891 z M 12.806641,100.80078 H 26.25 v 30.39844 H 3.75 V 109.97656 Z M 131,103.59961 h 18 v 13.5 H 131 Z M 9.375,106.5 v 7.59961 h 3.75 V 106.5 Z m 5.625,0 v 7.59961 h 3.75 V 106.5 Z m 5.625,0 v 7.59961 h 3.75 V 106.5 Z" />
</g>
<path
d="m 78.375,46.2 c 0.8752,0 1.7146,-0.3477 2.3335,-0.9666 0.6188,-0.6188 0.9665,-1.4582 0.9665,-2.3334 0,-0.8752 -0.3477,-1.7146 -0.9665,-2.3335 C 80.0896,39.9477 79.2502,39.6 78.375,39.6 c -0.8752,0 -1.7146,0.3477 -2.3334,0.9665 -0.6189,0.6189 -0.9666,1.4583 -0.9666,2.3335 0,0.8752 0.3477,1.7146 0.9666,2.3334 0.6188,0.6189 1.4582,0.9666 2.3334,0.9666 z"
fill="#8a9cb3" id="path10" />
<path d="m 134,109.6 h 4.5 v 3 H 134 Z" fill="#8a9cb3" id="path14" />
<path d="m 141.5,109.6 h 4.5 v 3 h -4.5 z" fill="#8a9cb3" id="path16" />
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -0,0 +1,36 @@
<?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="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="120" height="52"
viewBox="0 0 120 52" fill="none" version="1.1" id="svg24"
sodipodi:docname="transfer_data_receive.svg" inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
<defs id="defs11" />
<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="982"
id="namedview9" showgrid="false" inkscape:zoom="4.1077454" inkscape:cx="81.48675"
inkscape:cy="18.42645" inkscape:window-x="1920" inkscape:window-y="72"
inkscape:window-maximized="0" inkscape:current-layer="svg24" inkscape:document-rotation="0"
fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" />
<metadata id="metadata30">
<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>
<path id="path8"
style="fill:#8a9cb3;stroke:none;stroke-width:0.749281;stroke-miterlimit:4;stroke-dasharray:none"
d="m 15.97512,14.267003 c -0.596207,0 -1.167798,0.237677 -1.588736,0.659365 -0.421693,0.42094 -0.659371,0.992537 -0.659371,1.588743 v 10.111264 c -0.596207,0 -1.16631,0.236178 -1.58725,0.657875 -0.421682,0.420938 -0.659361,0.992529 -0.659361,1.588736 v 12.35937 h 2.246611 v -12.35937 h 17.977398 v 12.359369 h 2.24662 V 28.872986 c 0,-0.596207 -0.23619,-1.167798 -0.65788,-1.588737 -0.42094,-0.421697 -0.99253,-0.657875 -1.58874,-0.657875 V 16.515111 c 0,-0.596206 -0.23768,-1.167804 -0.65937,-1.588743 -0.42094,-0.421688 -0.99252,-0.659365 -1.58873,-0.659365 z m 0,2.248108 H 29.456311 V 26.626375 H 15.97512 Z m 2.24662,2.224241 v 2.246609 h 3.369915 v -2.246609 z m 5.618031,0 v 2.246609 h 3.36993 v -2.246609 z"
sodipodi:nodetypes="scsccsccccccsccscssccccccccccccccc" />
<path
d="m 47.827564,24.463076 v 3.073847 h 18.443085 l -8.45308,8.453081 2.182432,2.182432 12.172435,-12.172438 -12.172435,-12.172434 -2.182432,2.182431 8.45308,8.45308 z"
id="path895" style="fill:#82c91e;fill-opacity:1;stroke:none;stroke-width:1.53692" />
<path id="path8-3"
style="fill:#8a9cb3;stroke:none;stroke-width:0.985186;stroke-miterlimit:4;stroke-dasharray:none"
d="M 93.999021,5.9999999e-8 C 92.275189,5.9999999e-8 90.624219,0.68560941 89.405306,1.9045678 88.186391,3.1235212 87.500737,4.7763832 87.500737,6.5002454 V 45.497793 c 0,1.723833 0.685654,3.378723 1.904569,4.597639 C 90.624219,51.314343 92.275189,52 93.999021,52 h 19.500739 c 1.72383,0 3.37676,-0.685657 4.59567,-1.904568 C 119.31435,48.876515 120,47.221626 120,45.497793 V 6.5002454 C 120,4.7763832 119.31435,3.1235212 118.09543,1.9045678 116.87652,0.68560941 115.22359,5.9999999e-8 113.49976,5.9999999e-8 Z m 0,3.252084740000001 h 19.500739 c 0.86192,0 1.68931,0.339865 2.29881,0.9493417 0.60941,0.6094767 0.95131,1.4368929 0.95131,2.2988189 V 45.497793 c 0,0.861917 -0.3419,1.689315 -0.95131,2.29882 -0.6095,0.609408 -1.43689,0.953264 -2.29881,0.953264 H 93.999021 c -0.861916,0 -1.687352,-0.343856 -2.296858,-0.953264 -0.609407,-0.609505 -0.951304,-1.436903 -0.951304,-2.29882 V 6.5002454 c 0,-0.861926 0.341897,-1.6893422 0.951304,-2.2988189 0.609506,-0.6094767 1.434942,-0.9493417 2.296858,-0.9493417 z m 9.750369,32.7640582 c -0.86191,0 -1.68745,0.341895 -2.29686,0.951303 -0.6095,0.609506 -0.95326,1.436904 -0.95326,2.298819 0,0.861916 0.34376,1.687452 0.95326,2.29686 0.60941,0.609507 1.43495,0.951302 2.29686,0.951302 0.86192,0 1.68931,-0.341797 2.29882,-0.951302 0.6094,-0.609408 0.9513,-1.434942 0.9513,-2.29686 0,-0.861915 -0.3419,-1.689311 -0.9513,-2.298819 -0.60951,-0.609408 -1.4369,-0.951303 -2.29882,-0.951303 z"
sodipodi:nodetypes="sssssssscsssssscsscsscsscsssscssscs" />
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,37 @@
<?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="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="120" height="52"
viewBox="0 0 120 52" fill="none" version="1.1" id="svg24"
sodipodi:docname="transfer_data_send.svg" inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
<defs id="defs11" />
<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="982"
id="namedview9" showgrid="false" inkscape:zoom="4.1077454" inkscape:cx="35.020218"
inkscape:cy="26.319497" inkscape:window-x="1920" inkscape:window-y="72"
inkscape:window-maximized="1" inkscape:current-layer="svg24" inkscape:document-rotation="0"
fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0"
lock-margins="true" />
<metadata id="metadata30">
<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>
<path id="path8"
style="fill:#8a9cb3;stroke:none;stroke-width:0.749281;stroke-miterlimit:4;stroke-dasharray:none"
d="m 91.223138,12.30985 c -0.596207,0 -1.167798,0.237677 -1.588736,0.659365 -0.421693,0.42094 -0.659371,0.992537 -0.659371,1.588743 v 10.111264 c -0.596207,0 -1.16631,0.236178 -1.58725,0.657875 -0.421682,0.420938 -0.659361,0.992529 -0.659361,1.588736 v 12.35937 h 2.246611 v -12.35937 h 17.977399 v 12.359369 h 2.24662 V 26.915833 c 0,-0.596207 -0.23619,-1.167798 -0.65788,-1.588737 -0.42094,-0.421697 -0.99253,-0.657875 -1.58874,-0.657875 V 14.557958 c 0,-0.596206 -0.23768,-1.167804 -0.65937,-1.588743 -0.42094,-0.421688 -0.99252,-0.659365 -1.58873,-0.659365 z m 0,2.248108 H 104.70433 V 24.669222 H 91.223138 Z m 2.24662,2.224241 v 2.246609 h 3.369915 v -2.246609 z m 5.618031,0 v 2.246609 h 3.369931 v -2.246609 z"
sodipodi:nodetypes="scsccsccccccsccscssccccccccccccccc" />
<path
d="m 47.827564,24.463076 v 3.073847 h 18.443085 l -8.45308,8.453081 2.182432,2.182432 12.172435,-12.172438 -12.172435,-12.172434 -2.182432,2.182431 8.45308,8.45308 z"
id="path895" style="fill:#82c91e;fill-opacity:1;stroke:none;stroke-width:1.53692" />
<path id="path8-3"
style="fill:#8a9cb3;stroke:none;stroke-width:0.985186;stroke-miterlimit:4;stroke-dasharray:none"
d="M 6.4982841,0 C 4.7744519,0 3.1234817,0.68560936 1.9045686,1.9045677 0.68565358,3.1235212 0,4.7763832 0,6.5002454 V 45.497793 c 0,1.723832 0.68565358,3.378724 1.9045686,4.597639 C 3.1234817,51.314343 4.7744519,52 6.4982841,52 H 25.999019 c 1.723832,0 3.376764,-0.685657 4.595677,-1.904568 1.218914,-1.218917 1.904569,-2.873807 1.904568,-4.597639 V 6.5002454 c 0,-1.7238622 -0.685654,-3.3767242 -1.904568,-4.5956777 C 29.375783,0.68560936 27.722851,0 25.999019,0 Z m 0,3.2520847 H 25.999019 c 0.861916,0 1.689313,0.339865 2.29882,0.9493418 0.609407,0.6094767 0.951302,1.4368929 0.951302,2.2988189 V 45.497793 c 0,0.861916 -0.341895,1.689315 -0.951302,2.29882 -0.609507,0.609408 -1.436904,0.953264 -2.29882,0.953264 H 6.4982841 c -0.8619161,0 -1.6873525,-0.343856 -2.2968577,-0.953264 C 3.5920186,47.187108 3.2501219,46.359709 3.2501219,45.497793 V 6.5002454 c 0,-0.861926 0.3418967,-1.6893422 0.9513045,-2.2988189 C 4.8109316,3.5919497 5.636368,3.2520847 6.4982841,3.2520847 Z M 16.248652,36.016144 c -0.861916,0 -1.68745,0.341895 -2.296859,0.951302 -0.609506,0.609506 -0.953264,1.436905 -0.953264,2.29882 0,0.861916 0.343758,1.687452 0.953264,2.29686 0.609409,0.609507 1.434943,0.951302 2.296859,0.951302 0.861916,0 1.689312,-0.341797 2.298819,-0.951302 0.609408,-0.609408 0.951303,-1.434942 0.951303,-2.29686 0,-0.861915 -0.341895,-1.689311 -0.951303,-2.29882 -0.609507,-0.609407 -1.436903,-0.951302 -2.298819,-0.951302 z"
sodipodi:nodetypes="sssssssscsssssscsscsscsscsssscssscs" />
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -437,6 +437,15 @@
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
</activity>
<activity
android:name="org.briarproject.briar.android.removabledrive.RemovableDriveActivity"
android:label="@string/removable_drive_menu_title"
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
</activity>
<activity
android:name=".android.contact.add.remote.PendingContactListActivity"
android:label="@string/pending_contact_requests"

View File

@@ -38,6 +38,9 @@ import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.removabledrive.ChooserFragment;
import org.briarproject.briar.android.removabledrive.ReceiveFragment;
import org.briarproject.briar.android.removabledrive.SendFragment;
import org.briarproject.briar.android.settings.ConnectionsFragment;
import org.briarproject.briar.android.settings.NotificationsFragment;
import org.briarproject.briar.android.settings.SecurityFragment;
@@ -212,4 +215,10 @@ public interface AndroidComponent
void inject(SecurityFragment securityFragment);
void inject(NotificationsFragment notificationsFragment);
void inject(ChooserFragment chooserFragment);
void inject(SendFragment sendFragment);
void inject(ReceiveFragment receiveFragment);
}

View File

@@ -25,7 +25,6 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.reporting.DevConfig;
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
import org.briarproject.bramble.plugin.file.AndroidRemovableDrivePluginFactory;
import org.briarproject.bramble.plugin.file.RemovableDriveModule;
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory;
import org.briarproject.bramble.util.AndroidUtils;
@@ -43,6 +42,7 @@ import org.briarproject.briar.android.login.LoginModule;
import org.briarproject.briar.android.navdrawer.NavDrawerModule;
import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
import org.briarproject.briar.android.privategroup.list.GroupListModule;
import org.briarproject.briar.android.removabledrive.TransferDataModule;
import org.briarproject.briar.android.reporting.DevReportModule;
import org.briarproject.briar.android.settings.SettingsModule;
import org.briarproject.briar.android.sharing.SharingModule;
@@ -93,7 +93,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
GroupListModule.class,
GroupConversationModule.class,
SharingModule.class,
RemovableDriveModule.class
TransferDataModule.class,
})
public class AppModule {
@@ -307,6 +307,11 @@ public class AppModule {
public boolean shouldEnableConnectViaBluetooth() {
return IS_DEBUG_BUILD;
}
@Override
public boolean shouldEnableTransferData() {
return IS_DEBUG_BUILD;
}
};
}
}

View File

@@ -63,6 +63,7 @@ import org.briarproject.briar.android.privategroup.memberlist.GroupMemberModule;
import org.briarproject.briar.android.privategroup.reveal.GroupRevealModule;
import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity;
import org.briarproject.briar.android.privategroup.reveal.RevealContactsFragment;
import org.briarproject.briar.android.removabledrive.RemovableDriveActivity;
import org.briarproject.briar.android.reporting.CrashFragment;
import org.briarproject.briar.android.reporting.CrashReportActivity;
import org.briarproject.briar.android.reporting.ReportFormFragment;
@@ -176,6 +177,8 @@ public interface ActivityComponent {
void inject(CrashReportActivity crashReportActivity);
void inject(RemovableDriveActivity activity);
// Fragments
void inject(SetupFragment fragment);

View File

@@ -11,8 +11,5 @@ public interface RequestCodes {
int REQUEST_DOZE_WHITELISTING = 9;
int REQUEST_UNLOCK = 11;
int REQUEST_KEYGUARD_UNLOCK = 12;
int REQUEST_ATTACH_IMAGE = 13;
int REQUEST_SAVE_ATTACHMENT = 14;
int REQUEST_AVATAR_IMAGE = 15;
}

View File

@@ -17,7 +17,7 @@ import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed;
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.util.RequestBluetoothDiscoverable;
import org.briarproject.briar.android.util.ActivityLaunchers.RequestBluetoothDiscoverable;
import java.util.logging.Logger;

View File

@@ -14,7 +14,7 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.contact.ContactItem;
import org.briarproject.briar.android.util.RequestBluetoothDiscoverable;
import org.briarproject.briar.android.util.ActivityLaunchers.RequestBluetoothDiscoverable;
import javax.inject.Inject;

View File

@@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.transition.Slide;
@@ -34,7 +35,6 @@ import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.sync.ClientId;
@@ -54,6 +54,9 @@ import org.briarproject.briar.android.forum.ForumActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.introduction.IntroductionActivity;
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
import org.briarproject.briar.android.removabledrive.RemovableDriveActivity;
import org.briarproject.briar.android.util.ActivityLaunchers.GetImageAdvanced;
import org.briarproject.briar.android.util.ActivityLaunchers.GetMultipleImagesAdvanced;
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.android.view.ImagePreview;
@@ -92,6 +95,7 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.appcompat.app.AlertDialog;
@@ -121,6 +125,7 @@ import static android.widget.Toast.LENGTH_SHORT;
import static androidx.core.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
import static androidx.lifecycle.Lifecycle.State.STARTED;
import static androidx.recyclerview.widget.SortedList.INVALID_POSITION;
import static java.util.Collections.singletonList;
import static java.util.Collections.sort;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.INFO;
@@ -132,7 +137,6 @@ import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.bramble.util.StringUtils.join;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ATTACH_IMAGE;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRODUCTION;
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENTS;
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENT_POSITION;
@@ -192,6 +196,12 @@ public class ConversationActivity extends BriarActivity
requireNonNull(name);
loadMessages();
};
private final ActivityResultLauncher<String> launcher = SDK_INT >= 18 ?
registerForActivityResult(new GetMultipleImagesAdvanced(),
this::onImagesChosen) :
registerForActivityResult(new GetImageAdvanced(), uri -> {
if (uri != null) onImagesChosen(singletonList(uri));
});
private AttachmentRetriever attachmentRetriever;
private ConversationViewModel viewModel;
@@ -314,9 +324,6 @@ public class ConversationActivity extends BriarActivity
.make(list, R.string.introduction_sent,
Snackbar.LENGTH_SHORT)
.show();
} else if (request == REQUEST_ATTACH_IMAGE && result == RESULT_OK) {
// TODO: remove cast when removing feature flag
((TextAttachmentController) sendController).onImageReceived(data);
}
}
@@ -375,6 +382,10 @@ public class ConversationActivity extends BriarActivity
if (!featureFlags.shouldEnableConnectViaBluetooth()) {
menu.findItem(R.id.action_connect_via_bluetooth).setVisible(false);
}
// Transfer Data feature only supported on API 19+
if (SDK_INT >= 19 && featureFlags.shouldEnableTransferData()) {
menu.findItem(R.id.action_transfer_data).setVisible(true);
}
// enable alias and bluetooth action once available
observeOnce(viewModel.getContactItem(), this, contact -> {
menu.findItem(R.id.action_set_alias).setEnabled(true);
@@ -415,6 +426,11 @@ public class ConversationActivity extends BriarActivity
new BluetoothConnecterDialogFragment().show(fm,
BluetoothConnecterDialogFragment.TAG);
return true;
} else if (itemId == R.id.action_transfer_data) {
Intent intent = new Intent(this, RemovableDriveActivity.class);
intent.putExtra(CONTACT_ID, contactId.getInt());
startActivity(intent);
return true;
} else if (itemId == R.id.action_delete_all_messages) {
askToDeleteAllMessages();
return true;
@@ -760,8 +776,13 @@ public class ConversationActivity extends BriarActivity
}
@Override
public void onAttachImage(Intent intent) {
startActivityForResult(intent, REQUEST_ATTACH_IMAGE);
public void onAttachImageClicked() {
launcher.launch("image/*");
}
private void onImagesChosen(@Nullable List<Uri> uris) {
// TODO: remove cast when removing feature flag
((TextAttachmentController) sendController).onImageReceived(uris);
}
@Override

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android.conversation;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.transition.Fade;
import android.transition.Transition;
@@ -21,6 +22,7 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.attachment.AttachmentItem;
import org.briarproject.briar.android.util.ActivityLaunchers.CreateDocumentAdvanced;
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.PullDownLayout;
@@ -28,6 +30,7 @@ import java.util.List;
import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog.Builder;
@@ -38,9 +41,6 @@ import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.lifecycle.ViewModelProvider;
import androidx.viewpager.widget.ViewPager;
import static android.content.Intent.ACTION_CREATE_DOCUMENT;
import static android.content.Intent.CATEGORY_OPENABLE;
import static android.content.Intent.EXTRA_TITLE;
import static android.graphics.Color.TRANSPARENT;
import static android.os.Build.VERSION.SDK_INT;
import static android.view.View.GONE;
@@ -51,7 +51,6 @@ import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static com.google.android.material.snackbar.Snackbar.LENGTH_LONG;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SAVE_ATTACHMENT;
import static org.briarproject.briar.android.util.UiUtils.formatDateAbsolute;
import static org.briarproject.briar.android.util.UiUtils.getDialogIcon;
@@ -80,6 +79,10 @@ public class ImageActivity extends BriarActivity
private List<AttachmentItem> attachments;
private MessageId conversationMessageId;
private final ActivityResultLauncher<String> launcher =
registerForActivityResult(new CreateDocumentAdvanced(),
this::onImageUriSelected);
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
@@ -177,16 +180,6 @@ public class ImageActivity extends BriarActivity
layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
@Override
protected void onActivityResult(int request, int result,
@Nullable Intent data) {
super.onActivityResult(request, result, data);
if (request == REQUEST_SAVE_ATTACHMENT && result == RESULT_OK &&
data != null) {
viewModel.saveImage(getVisibleAttachment(), data.getData());
}
}
@Override
public void onPullStart() {
appBarLayout.animate()
@@ -270,8 +263,9 @@ public class ImageActivity extends BriarActivity
private void showSaveImageDialog() {
OnClickListener okListener = (dialog, which) -> {
if (SDK_INT >= 19) {
Intent intent = getCreationIntent();
startActivityForResult(intent, REQUEST_SAVE_ATTACHMENT);
String name = viewModel.getFileName() + "." +
getVisibleAttachment().getExtension();
launcher.launch(name);
} else {
viewModel.saveImage(getVisibleAttachment());
}
@@ -285,13 +279,9 @@ public class ImageActivity extends BriarActivity
builder.show();
}
@RequiresApi(api = 19)
private Intent getCreationIntent() {
Intent intent = new Intent(ACTION_CREATE_DOCUMENT);
intent.addCategory(CATEGORY_OPENABLE);
intent.setType(getVisibleAttachment().getMimeType());
intent.putExtra(EXTRA_TITLE, viewModel.getFileName());
return intent;
private void onImageUriSelected(@Nullable Uri uri) {
if (uri == null) return;
viewModel.saveImage(getVisibleAttachment(), uri);
}
private void onImageSaveStateChanged(@Nullable Boolean error) {

View File

@@ -33,7 +33,6 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -45,6 +44,7 @@ import androidx.annotation.UiThread;
import static android.media.MediaScannerConnection.scanFile;
import static android.os.Environment.DIRECTORY_PICTURES;
import static android.os.Environment.getExternalStoragePublicDirectory;
import static java.util.Locale.US;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
@@ -111,7 +111,7 @@ public class ImageViewModel extends DbViewModel implements EventListener {
}
@UiThread
public void expectAttachments(List<AttachmentItem> attachments) {
void expectAttachments(List<AttachmentItem> attachments) {
for (AttachmentItem item : attachments) {
// no need to track items that are in a final state already
if (item.getState().isFinal()) continue;
@@ -226,8 +226,7 @@ public class ImageViewModel extends DbViewModel implements EventListener {
}
String getFileName() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd",
Locale.getDefault());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HHmmss", US);
return sdf.format(new Date());
}

View File

@@ -0,0 +1,120 @@
package org.briarproject.briar.android.fragment;
import android.content.res.ColorStateList;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ScrollView;
import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.widget.ImageViewCompat;
import androidx.fragment.app.Fragment;
import static android.view.View.FOCUS_DOWN;
/**
* A fragment to be used at the end of a user flow
* where the user should not have the option to go back.
* Here, we only show final information
* before finishing the related activity.
*/
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class FinalFragment extends Fragment {
public static final String TAG = FinalFragment.class.getName();
public static final String ARG_TITLE = "title";
public static final String ARG_ICON = "icon";
public static final String ARG_ICON_TINT = "iconTint";
public static final String ARG_TEXT = "text";
public static FinalFragment newInstance(
@StringRes int title,
@DrawableRes int icon,
@ColorRes int iconTint,
@StringRes int text) {
FinalFragment f = new FinalFragment();
Bundle args = new Bundle();
args.putInt(ARG_TITLE, title);
args.putInt(ARG_ICON, icon);
args.putInt(ARG_ICON_TINT, iconTint);
args.putInt(ARG_TEXT, text);
f.setArguments(args);
return f;
}
private ScrollView scrollView;
protected Button buttonView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View v = inflater
.inflate(R.layout.fragment_final, container, false);
scrollView = (ScrollView) v;
ImageView iconView = v.findViewById(R.id.iconView);
TextView titleView = v.findViewById(R.id.titleView);
TextView textView = v.findViewById(R.id.textView);
buttonView = v.findViewById(R.id.button);
Bundle args = requireArguments();
titleView.setText(args.getInt(ARG_TITLE));
iconView.setImageResource(args.getInt(ARG_ICON));
int color = getResources().getColor(args.getInt(ARG_ICON_TINT));
ColorStateList tint = ColorStateList.valueOf(color);
ImageViewCompat.setImageTintList(iconView, tint);
textView.setText(args.getInt(ARG_TEXT));
buttonView.setOnClickListener(view -> onBackButtonPressed());
AppCompatActivity a = (AppCompatActivity) requireActivity();
a.setTitle(args.getInt(ARG_TITLE));
ActionBar actionBar = a.getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(false);
actionBar.setHomeButtonEnabled(false);
}
a.getOnBackPressedDispatcher().addCallback(
getViewLifecycleOwner(), new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
onBackButtonPressed();
}
});
return v;
}
@Override
public void onStart() {
super.onStart();
// Scroll down in case the screen is small, so the button is visible
scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN));
}
/**
* This is the action that the system back button
* and the button at the bottom will perform.
*/
protected void onBackButtonPressed() {
requireActivity().supportFinishAfterTransition();
}
}

View File

@@ -0,0 +1,78 @@
package org.briarproject.briar.android.removabledrive;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ScrollView;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import static android.view.View.FOCUS_DOWN;
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ChooserFragment extends Fragment {
public final static String TAG = ChooserFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private RemovableDriveViewModel viewModel;
private ScrollView scrollView;
@Override
public void onAttach(Context context) {
super.onAttach(context);
FragmentActivity activity = requireActivity();
getAndroidComponent(activity).inject(this);
viewModel = new ViewModelProvider(activity, viewModelFactory)
.get(RemovableDriveViewModel.class);
}
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_transfer_data_chooser,
container, false);
scrollView = (ScrollView) v;
Button sendButton = v.findViewById(R.id.sendButton);
sendButton.setOnClickListener(i -> viewModel.startSendData());
Button receiveButton = v.findViewById(R.id.receiveButton);
receiveButton.setOnClickListener(i -> viewModel.startReceiveData());
return v;
}
@Override
public void onStart() {
super.onStart();
requireActivity().setTitle(R.string.removable_drive_menu_title);
TransferDataState state = viewModel.getState().getValue();
if (state instanceof TransferDataState.TaskAvailable) {
// we can't come back here now to start another task
// as we only support one per ViewModel instance
requireActivity().supportFinishAfterTransition();
} else {
// Scroll down in case the screen is small, so the button is visible
scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN));
}
}
}

View File

@@ -0,0 +1,54 @@
package org.briarproject.briar.android.removabledrive;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.fragment.FinalFragment;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ErrorFragment extends FinalFragment {
public static ErrorFragment newInstance(@StringRes int title,
@StringRes int text) {
ErrorFragment f = new ErrorFragment();
Bundle args = new Bundle();
args.putInt(ARG_TITLE, title);
args.putInt(ARG_ICON, R.drawable.alerts_and_states_error);
args.putInt(ARG_ICON_TINT, R.color.briar_red_500);
args.putInt(ARG_TEXT, text);
f.setArguments(args);
return f;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View v = super.onCreateView(inflater, container, savedInstanceState);
buttonView.setText(R.string.try_again_button);
return v;
}
@Override
protected void onBackButtonPressed() {
// Re-create this activity when going back in failed state.
// This will also re-create the ViewModel, so we start fresh.
Intent i = requireActivity().getIntent();
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
}
}

View File

@@ -0,0 +1,110 @@
package org.briarproject.briar.android.removabledrive;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.util.ActivityLaunchers.GetContentAdvanced;
import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import static android.view.View.FOCUS_DOWN;
import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_LONG;
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ReceiveFragment extends Fragment {
final static String TAG = ReceiveFragment.class.getName();
private final ActivityResultLauncher<String> launcher =
registerForActivityResult(new GetContentAdvanced(),
this::onDocumentChosen);
@Inject
ViewModelProvider.Factory viewModelFactory;
private RemovableDriveViewModel viewModel;
private ScrollView scrollView;
private Button button;
private ProgressBar progressBar;
@Override
public void onAttach(Context context) {
super.onAttach(context);
FragmentActivity activity = requireActivity();
getAndroidComponent(activity).inject(this);
viewModel = new ViewModelProvider(activity, viewModelFactory)
.get(RemovableDriveViewModel.class);
}
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_transfer_data_receive,
container, false);
scrollView = (ScrollView) v;
progressBar = v.findViewById(R.id.progressBar);
button = v.findViewById(R.id.fileButton);
button.setOnClickListener(view ->
launcher.launch("*/*")
);
viewModel.getOldTaskResumedEvent()
.observeEvent(getViewLifecycleOwner(), this::onOldTaskResumed);
viewModel.getState()
.observe(getViewLifecycleOwner(), this::onStateChanged);
return v;
}
@Override
public void onStart() {
super.onStart();
requireActivity().setTitle(R.string.removable_drive_title_receive);
// Scroll down in case the screen is small, so the button is visible
scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN));
}
private void onOldTaskResumed(boolean resumed) {
if (resumed) {
Toast.makeText(requireContext(),
R.string.removable_drive_ongoing, LENGTH_LONG).show();
}
}
private void onStateChanged(TransferDataState state) {
if (state instanceof TransferDataState.NoDataToSend) {
throw new IllegalStateException();
} else if (state instanceof TransferDataState.Ready) {
button.setEnabled(true);
} else if (state instanceof TransferDataState.TaskAvailable) {
button.setEnabled(false);
progressBar.setVisibility(VISIBLE);
}
}
private void onDocumentChosen(@Nullable Uri uri) {
if (uri == null) return;
viewModel.importData(uri);
}
}

View File

@@ -0,0 +1,144 @@
package org.briarproject.briar.android.removabledrive;
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
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.FinalFragment;
import org.briarproject.briar.android.removabledrive.RemovableDriveViewModel.Action;
import org.briarproject.briar.android.removabledrive.TransferDataState.TaskAvailable;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
import static org.briarproject.briar.android.util.UiUtils.showFragment;
@RequiresApi(19)
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class RemovableDriveActivity extends BriarActivity {
@Inject
ViewModelProvider.Factory viewModelFactory;
private RemovableDriveViewModel viewModel;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
viewModel = new ViewModelProvider(this, viewModelFactory)
.get(RemovableDriveViewModel.class);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = requireNonNull(getIntent());
int contactId = intent.getIntExtra(CONTACT_ID, -1);
if (contactId == -1) throw new IllegalArgumentException("ContactId");
viewModel.setContactId(new ContactId(contactId));
setContentView(R.layout.activity_fragment_container);
viewModel.getActionEvent().observeEvent(this, this::onActionReceived);
viewModel.getState().observe(this, this::onStateChanged);
if (savedInstanceState == null) {
Fragment f = new ChooserFragment();
String tag = ChooserFragment.TAG;
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, f, tag)
.commit();
}
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
private void onActionReceived(Action action) {
Fragment f;
String tag;
if (action == Action.SEND) {
f = new SendFragment();
tag = SendFragment.TAG;
} else if (action == Action.RECEIVE) {
f = new ReceiveFragment();
tag = ReceiveFragment.TAG;
} else throw new AssertionError();
showFragment(getSupportFragmentManager(), f, tag);
}
private void onStateChanged(TransferDataState state) {
if (!(state instanceof TaskAvailable)) return;
RemovableDriveTask.State s = ((TaskAvailable) state).state;
if (s.isFinished()) {
FragmentManager fm = getSupportFragmentManager();
Action action;
// We can't simply rely on viewModel.getActionEvent()
// as that might have been destroyed in the meantime.
if (fm.findFragmentByTag(SendFragment.TAG) != null) {
action = Action.SEND;
} else if (fm.findFragmentByTag(ReceiveFragment.TAG) != null) {
action = Action.RECEIVE;
} else {
action = requireNonNull(
viewModel.getActionEvent().getLastValue());
}
Fragment f;
if (s.isSuccess()) f = getSuccessFragment(action);
else f = getErrorFragment(action);
showFragment(getSupportFragmentManager(), f, FinalFragment.TAG);
}
}
private Fragment getSuccessFragment(Action action) {
@StringRes int title, text;
if (action == Action.SEND) {
title = R.string.removable_drive_success_send_title;
text = R.string.removable_drive_success_send_text;
} else if (action == Action.RECEIVE) {
title = R.string.removable_drive_success_receive_title;
text = R.string.removable_drive_success_receive_text;
} else throw new AssertionError();
return FinalFragment.newInstance(title,
R.drawable.ic_check_circle_outline, R.color.briar_brand_green,
text);
}
private Fragment getErrorFragment(Action action) {
@StringRes int title, text;
if (action == Action.SEND) {
title = R.string.removable_drive_error_send_title;
text = R.string.removable_drive_error_send_text;
} else if (action == Action.RECEIVE) {
title = R.string.removable_drive_error_receive_title;
text = R.string.removable_drive_error_receive_text;
} else throw new AssertionError();
return ErrorFragment.newInstance(title, text);
}
}

View File

@@ -5,97 +5,186 @@ import android.net.Uri;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask.State;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.lifecycle.AndroidViewModel;
import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static java.util.Locale.US;
import static java.util.logging.Logger.getLogger;
import static java.util.Objects.requireNonNull;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_URI;
@UiThread
@NotNullByDefault
class RemovableDriveViewModel extends AndroidViewModel {
class RemovableDriveViewModel extends DbViewModel {
private static final Logger LOG =
getLogger(RemovableDriveViewModel.class.getName());
enum Action {SEND, RECEIVE}
private final RemovableDriveManager manager;
private final ConcurrentHashMap<Consumer<State>, RemovableDriveTask>
observers = new ConcurrentHashMap<>();
private final MutableLiveEvent<Action> action = new MutableLiveEvent<>();
private final MutableLiveEvent<Boolean> oldTaskResumed =
new MutableLiveEvent<>();
private final MutableLiveData<TransferDataState> state =
new MutableLiveData<>();
@Nullable
private ContactId contactId = null;
@Nullable
private RemovableDriveTask task = null;
@Nullable
private Consumer<State> taskObserver = null;
@Inject
RemovableDriveViewModel(Application app,
RemovableDriveViewModel(
Application app,
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
TransactionManager db,
AndroidExecutor androidExecutor,
RemovableDriveManager removableDriveManager) {
super(app);
super(app, dbExecutor, lifecycleManager, db, androidExecutor);
this.manager = removableDriveManager;
}
String getFileName() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS", US);
return sdf.format(new Date());
}
LiveData<State> write(ContactId contactId, Uri uri) {
TransportProperties p = new TransportProperties();
p.put(PROP_URI, uri.toString());
return observe(manager.startWriterTask(contactId, p));
}
LiveData<State> read(Uri uri) {
TransportProperties p = new TransportProperties();
p.put(PROP_URI, uri.toString());
return observe(manager.startReaderTask(p));
}
@Nullable
LiveData<State> ongoingWrite() {
RemovableDriveTask task = manager.getCurrentWriterTask();
if (task == null) {
return null;
}
return observe(task);
}
@Nullable
LiveData<State> ongoingRead() {
RemovableDriveTask task = manager.getCurrentReaderTask();
if (task == null) {
return null;
}
return observe(task);
}
private LiveData<State> observe(RemovableDriveTask task) {
MutableLiveData<State> state = new MutableLiveData<>();
Consumer<State> observer = state::postValue;
task.addObserver(observer);
observers.put(observer, task);
return state;
}
@Override
protected void onCleared() {
for (Map.Entry<Consumer<State>, RemovableDriveTask> entry
: observers.entrySet()) {
entry.getValue().removeObserver(entry.getKey());
if (task != null) {
// when we have a task, we must have an observer for it
Consumer<State> observer = requireNonNull(taskObserver);
task.removeObserver(observer);
}
}
/**
* Set this as soon as it becomes available.
*/
void setContactId(ContactId contactId) {
this.contactId = contactId;
}
@UiThread
void startSendData() {
action.setEvent(Action.SEND);
// check if there is already a send/write task
task = manager.getCurrentWriterTask();
if (task == null) {
// check if there's even something to send
ContactId c = requireNonNull(contactId);
runOnDbThread(() -> {
try {
if (!manager.isTransportSupportedByContact(c)) {
state.postValue(new TransferDataState.NotSupported());
} else if (manager.isWriterTaskNeeded(c)) {
state.postValue(new TransferDataState.Ready());
} else {
state.postValue(new TransferDataState.NoDataToSend());
}
} catch (DbException e) {
handleException(e);
}
});
} else {
// observe old task
taskObserver =
s -> state.setValue(new TransferDataState.TaskAvailable(s));
task.addObserver(taskObserver);
oldTaskResumed.setEvent(true);
}
}
@UiThread
void startReceiveData() {
action.setEvent(Action.RECEIVE);
// check if there is already a receive/read task
task = manager.getCurrentReaderTask();
if (task == null) {
state.setValue(new TransferDataState.Ready());
} else {
// observe old task
taskObserver =
s -> state.setValue(new TransferDataState.TaskAvailable(s));
task.addObserver(taskObserver);
oldTaskResumed.setEvent(true);
}
}
String getFileName() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", US);
return sdf.format(new Date());
}
/**
* Call this only when in {@link TransferDataState.Ready}.
*/
@UiThread
void exportData(Uri uri) {
// starting an action more than once is not supported for simplicity
if (task != null) throw new IllegalStateException();
// from now on, we are not re-usable
// (because gets a state update right away on the UiThread)
taskObserver =
s -> state.setValue(new TransferDataState.TaskAvailable(s));
// start the writer task for this contact and observe it
TransportProperties p = new TransportProperties();
p.put(PROP_URI, uri.toString());
ContactId c = requireNonNull(contactId);
task = manager.startWriterTask(c, p);
task.addObserver(taskObserver);
}
/**
* Call this only when in {@link TransferDataState.Ready}.
*/
@UiThread
void importData(Uri uri) {
// starting an action more than once is not supported for simplicity
if (task != null) throw new IllegalStateException();
// from now on, we are not re-usable
// (because gets a state update right away on the UiThread)
taskObserver =
s -> state.setValue(new TransferDataState.TaskAvailable(s));
TransportProperties p = new TransportProperties();
p.put(PROP_URI, uri.toString());
task = manager.startReaderTask(p);
task.addObserver(taskObserver);
}
LiveEvent<Action> getActionEvent() {
return action;
}
LiveEvent<Boolean> getOldTaskResumedEvent() {
return oldTaskResumed;
}
LiveData<TransferDataState> getState() {
return state;
}
}

View File

@@ -0,0 +1,133 @@
package org.briarproject.briar.android.removabledrive;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.briar.R;
import org.briarproject.briar.android.util.ActivityLaunchers.CreateDocumentAdvanced;
import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import static android.os.Build.VERSION.SDK_INT;
import static android.view.View.FOCUS_DOWN;
import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_LONG;
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class SendFragment extends Fragment {
final static String TAG = SendFragment.class.getName();
private final ActivityResultLauncher<String> launcher =
registerForActivityResult(new CreateDocumentAdvanced(),
this::onDocumentCreated);
@Inject
ViewModelProvider.Factory viewModelFactory;
private RemovableDriveViewModel viewModel;
private ScrollView scrollView;
private TextView introTextView;
private Button button;
private ProgressBar progressBar;
@Override
public void onAttach(Context context) {
super.onAttach(context);
FragmentActivity activity = requireActivity();
getAndroidComponent(activity).inject(this);
viewModel = new ViewModelProvider(activity, viewModelFactory)
.get(RemovableDriveViewModel.class);
}
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_transfer_data_send,
container, false);
scrollView = (ScrollView) v;
introTextView = v.findViewById(R.id.introTextView);
progressBar = v.findViewById(R.id.progressBar);
button = v.findViewById(R.id.fileButton);
button.setOnClickListener(view ->
launcher.launch(viewModel.getFileName())
);
viewModel.getOldTaskResumedEvent()
.observeEvent(getViewLifecycleOwner(), this::onOldTaskResumed);
viewModel.getState()
.observe(getViewLifecycleOwner(), this::onStateChanged);
return v;
}
@Override
public void onStart() {
super.onStart();
requireActivity().setTitle(R.string.removable_drive_title_send);
// Scroll down in case the screen is small, so the button is visible
scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN));
}
private void onOldTaskResumed(boolean resumed) {
if (resumed) {
Toast.makeText(requireContext(),
R.string.removable_drive_ongoing, LENGTH_LONG).show();
}
}
private void onStateChanged(TransferDataState state) {
if (state instanceof TransferDataState.NoDataToSend) {
introTextView.setText(R.string.removable_drive_send_no_data);
button.setEnabled(false);
} else if (state instanceof TransferDataState.NotSupported) {
introTextView.setText(R.string.removable_drive_send_not_supported);
button.setEnabled(false);
} else if (state instanceof TransferDataState.Ready) {
button.setEnabled(true);
} else if (state instanceof TransferDataState.TaskAvailable) {
button.setEnabled(false);
RemovableDriveTask.State s =
((TransferDataState.TaskAvailable) state).state;
if (s.getTotal() > 0L && progressBar.getVisibility() != VISIBLE) {
progressBar.setVisibility(VISIBLE);
progressBar.setMax(100);
}
int progress = s.getTotal() == 0 ? 0 : // no div by null
(int) ((double) s.getDone() / s.getTotal() * 100);
if (SDK_INT >= 24) {
progressBar.setProgress(progress, true);
} else {
progressBar.setProgress(progress);
}
}
}
private void onDocumentCreated(@Nullable Uri uri) {
if (uri == null) return;
viewModel.exportData(uri);
}
}

View File

@@ -8,11 +8,12 @@ import dagger.Module;
import dagger.multibindings.IntoMap;
@Module
public interface RemovableDriveModule {
public interface TransferDataModule {
@Binds
@IntoMap
@ViewModelKey(RemovableDriveViewModel.class)
ViewModel bindRemovableDriveViewModel(RemovableDriveViewModel removableDriveViewModel);
ViewModel bindRemovableDriveViewModel(
RemovableDriveViewModel removableDriveViewModel);
}

View File

@@ -0,0 +1,40 @@
package org.briarproject.briar.android.removabledrive;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
@NotNullByDefault
abstract class TransferDataState {
/**
* There is nothing we can send to the chosen contact.
* This only applies to sending data, but not to receiving it.
*/
static class NoDataToSend extends TransferDataState {
}
/**
* The chosen contact does not support the transport, yet.
* So we can't send them data this way.
*/
static class NotSupported extends TransferDataState {
}
/**
* We are ready to let the user select a file for sending or receiving data.
*/
static class Ready extends TransferDataState {
}
/**
* A task with state information is available and should be shown in the UI.
*/
static class TaskAvailable extends TransferDataState {
final RemovableDriveTask.State state;
TaskAvailable(RemovableDriveTask.State state) {
this.state = state;
}
}
}

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.settings;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
@@ -9,9 +8,11 @@ import android.view.View;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.util.ActivityLaunchers.GetImageAdvanced;
import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
@@ -20,12 +21,9 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceGroup;
import static android.app.Activity.RESULT_OK;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_AVATAR_IMAGE;
import static org.briarproject.briar.android.util.UiUtils.createSelectImageIntent;
import static org.briarproject.briar.android.util.UiUtils.triggerFeedback;
@MethodsNotNullByDefault
@@ -45,6 +43,10 @@ public class SettingsFragment extends PreferenceFragmentCompat {
private SettingsViewModel viewModel;
private AvatarPreference prefAvatar;
private final ActivityResultLauncher<String> launcher =
registerForActivityResult(new GetImageAdvanced(),
this::onImageSelected);
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
@@ -60,8 +62,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
prefAvatar = requireNonNull(findPreference(PREF_KEY_AVATAR));
if (viewModel.shouldEnableProfilePictures()) {
prefAvatar.setOnPreferenceClickListener(preference -> {
Intent intent = createSelectImageIntent(false);
startActivityForResult(intent, REQUEST_AVATAR_IMAGE);
launcher.launch("image/*");
return true;
});
} else {
@@ -102,20 +103,11 @@ public class SettingsFragment extends PreferenceFragmentCompat {
requireActivity().setTitle(R.string.settings_button);
}
@Override
public void onActivityResult(int request, int result,
@Nullable Intent data) {
super.onActivityResult(request, result, data);
if (request == REQUEST_AVATAR_IMAGE && result == RESULT_OK) {
if (data == null) return;
Uri uri = data.getData();
if (uri == null) return;
DialogFragment dialog =
ConfirmAvatarDialogFragment.newInstance(uri);
dialog.show(getParentFragmentManager(),
ConfirmAvatarDialogFragment.TAG);
}
private void onImageSelected(@Nullable Uri uri) {
if (uri == null) return;
DialogFragment dialog = ConfirmAvatarDialogFragment.newInstance(uri);
dialog.show(getParentFragmentManager(),
ConfirmAvatarDialogFragment.TAG);
}
}

View File

@@ -0,0 +1,88 @@
package org.briarproject.briar.android.util;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument;
import androidx.activity.result.contract.ActivityResultContracts.GetContent;
import androidx.activity.result.contract.ActivityResultContracts.GetMultipleContents;
import androidx.annotation.Nullable;
import static android.app.Activity.RESULT_CANCELED;
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
import static android.content.Intent.EXTRA_MIME_TYPES;
import static android.os.Build.VERSION.SDK_INT;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
@NotNullByDefault
public class ActivityLaunchers {
public static class CreateDocumentAdvanced extends CreateDocument {
@Override
public Intent createIntent(Context context, String input) {
Intent i = super.createIntent(context, input);
putShowAdvancedExtra(i);
return i;
}
}
public static class GetContentAdvanced extends GetContent {
@Override
public Intent createIntent(Context context, String input) {
Intent i = super.createIntent(context, input);
putShowAdvancedExtra(i);
return i;
}
}
public static class GetImageAdvanced extends GetContent {
@Override
public Intent createIntent(Context context, String input) {
Intent i = super.createIntent(context, input);
putShowAdvancedExtra(i);
i.setType("image/*");
if (SDK_INT >= 19)
i.putExtra(EXTRA_MIME_TYPES, getSupportedImageContentTypes());
return i;
}
}
@TargetApi(18)
public static class GetMultipleImagesAdvanced extends GetMultipleContents {
@Override
public Intent createIntent(Context context, String input) {
Intent i = super.createIntent(context, input);
putShowAdvancedExtra(i);
i.setType("image/*");
if (SDK_INT >= 19)
i.putExtra(EXTRA_MIME_TYPES, getSupportedImageContentTypes());
return i;
}
}
public static class RequestBluetoothDiscoverable
extends ActivityResultContract<Integer, Boolean> {
@Override
public Intent createIntent(Context context, Integer duration) {
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
i.putExtra(EXTRA_DISCOVERABLE_DURATION, duration);
return i;
}
@Override
public Boolean parseResult(int resultCode, @Nullable Intent intent) {
return resultCode != RESULT_CANCELED;
}
}
private static void putShowAdvancedExtra(Intent i) {
i.putExtra(SDK_INT <= 28 ? "android.content.extra.SHOW_ADVANCED" :
"android.provider.extra.SHOW_ADVANCED", true);
}
}

View File

@@ -1,31 +0,0 @@
package org.briarproject.briar.android.util;
import android.content.Context;
import android.content.Intent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.annotation.Nullable;
import static android.app.Activity.RESULT_CANCELED;
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
@NotNullByDefault
public class RequestBluetoothDiscoverable
extends ActivityResultContract<Integer, Boolean> {
@Override
public Intent createIntent(Context context, Integer duration) {
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
i.putExtra(EXTRA_DISCOVERABLE_DURATION, duration);
return i;
}
@Override
public Boolean parseResult(int resultCode, @Nullable Intent intent) {
return resultCode != RESULT_CANCELED;
}
}

View File

@@ -57,7 +57,9 @@ import androidx.core.content.ContextCompat;
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
import androidx.core.text.HtmlCompat;
import androidx.core.util.Consumer;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
@@ -65,12 +67,7 @@ import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.Context.POWER_SERVICE;
import static android.content.Intent.ACTION_GET_CONTENT;
import static android.content.Intent.ACTION_OPEN_DOCUMENT;
import static android.content.Intent.CATEGORY_DEFAULT;
import static android.content.Intent.CATEGORY_OPENABLE;
import static android.content.Intent.EXTRA_ALLOW_MULTIPLE;
import static android.content.Intent.EXTRA_MIME_TYPES;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.Build.MANUFACTURER;
import static android.os.Build.VERSION.SDK_INT;
@@ -105,7 +102,6 @@ import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
@@ -135,6 +131,17 @@ public class UiUtils {
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
public static void showFragment(FragmentManager fm, Fragment f,
@Nullable String tag) {
fm.beginTransaction()
.setCustomAnimations(R.anim.step_next_in,
R.anim.step_previous_out, R.anim.step_previous_in,
R.anim.step_next_out)
.replace(R.id.fragmentContainer, f, tag)
.addToBackStack(tag)
.commit();
}
public static String getContactDisplayName(Author author,
@Nullable String alias) {
String name = author.getName();
@@ -297,18 +304,6 @@ public class UiUtils {
};
}
public static Intent createSelectImageIntent(boolean allowMultiple) {
Intent intent = new Intent(SDK_INT >= 19 ?
ACTION_OPEN_DOCUMENT : ACTION_GET_CONTENT);
intent.setType("image/*");
intent.addCategory(CATEGORY_OPENABLE);
if (SDK_INT >= 19)
intent.putExtra(EXTRA_MIME_TYPES, getSupportedImageContentTypes());
if (allowMultiple && SDK_INT >= 18)
intent.putExtra(EXTRA_ALLOW_MULTIPLE, true);
return intent;
}
public static void showOnboardingDialog(Context ctx, String text) {
new AlertDialog.Builder(ctx, R.style.OnboardingDialogTheme)
.setMessage(text)
@@ -334,6 +329,11 @@ public class UiUtils {
return i;
}
public static void putShowAdvancedExtra(Intent i) {
i.putExtra(SDK_INT <= 28 ? "android.content.extra.SHOW_ADVANCED" :
"android.provider.extra.SHOW_ADVANCED", true);
}
/**
* @return true if location is enabled,
* or it isn't required due to this being a SDK < 28 device.

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.view;
import android.app.Activity;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
@@ -15,13 +14,13 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.attachment.AttachmentItemResult;
import org.briarproject.briar.android.attachment.AttachmentManager;
import org.briarproject.briar.android.attachment.AttachmentResult;
import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.appcompat.app.AlertDialog.Builder;
@@ -31,7 +30,6 @@ import androidx.lifecycle.Observer;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
import static android.os.Build.VERSION.SDK_INT;
import static android.view.View.GONE;
import static android.widget.Toast.LENGTH_LONG;
import static androidx.core.content.ContextCompat.getColor;
@@ -143,38 +141,23 @@ public class TextAttachmentController extends TextSendController
builder.show();
return;
}
Intent intent = UiUtils.createSelectImageIntent(true);
if (attachmentListener.getLifecycle().getCurrentState() != DESTROYED) {
attachmentListener.onAttachImage(intent);
attachmentListener.onAttachImageClicked();
}
}
/**
* This is called with the result Intent returned by the Activity started
* with {@link UiUtils#createSelectImageIntent(boolean)}.
* <p>
* This method must be called at most once per call to
* {@link AttachmentListener#onAttachImage(Intent)}.
* Normally, this is true if called from
* {@link AttachmentListener#onAttachImageClicked()}.
* Normally, this is true if called from the launcher equivalent of
* {@link Activity#onActivityResult(int, int, Intent)} since this is called
* at most once per call to
* {@link Activity#startActivityForResult(Intent, int)}.
* at most once per call to {@link ActivityResultLauncher#launch(Object)}.
*/
@SuppressWarnings("JavadocReference")
public void onImageReceived(@Nullable Intent resultData) {
if (resultData == null) return;
public void onImageReceived(@Nullable List<Uri> newUris) {
if (newUris == null) return;
if (loadingUris || !imageUris.isEmpty()) throw new AssertionError();
List<Uri> newUris = new ArrayList<>();
if (resultData.getData() != null) {
newUris.add(resultData.getData());
onNewUris(false, newUris);
} else if (SDK_INT >= 18 && resultData.getClipData() != null) {
ClipData clipData = resultData.getClipData();
for (int i = 0; i < clipData.getItemCount(); i++) {
newUris.add(clipData.getItemAt(i).getUri());
}
onNewUris(false, newUris);
}
onNewUris(false, newUris);
}
private void onNewUris(boolean restart, List<Uri> newUris) {
@@ -329,7 +312,7 @@ public class TextAttachmentController extends TextSendController
@UiThread
public interface AttachmentListener extends SendListener {
void onAttachImage(Intent intent);
void onAttachImageClicked();
void onTooManyAttachments();
}

View File

@@ -23,7 +23,6 @@ import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.arch.core.util.Function;
@@ -50,7 +49,7 @@ public abstract class DbViewModel extends AndroidViewModel {
protected final AndroidExecutor androidExecutor;
public DbViewModel(
@NonNull Application application,
Application application,
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
TransactionManager db,

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" />
</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="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M16.59,7.58L10,14.17l-3.59,-3.58L5,12l5,5 8,-8zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z" />
</vector>

View File

@@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="155dp"
android:height="155dp"
android:viewportWidth="155"
android:viewportHeight="155">
<path
android:fillColor="@color/briar_lime_400"
android:pathData="m34.889,27.866c-0.93,-0.021 -1.779,0.612 -1.994,1.555 -0.246,1.077 0.427,2.148 1.504,2.395l5.34,1.223c-6.857,4.771 -12.782,10.788 -17.445,17.768 -5.477,8.197 -9.072,17.492 -10.541,27.219 -0.217,1.437 0.827,2.738 2.271,2.898 1.444,0.16 2.741,-0.88 2.963,-2.316 1.373,-8.889 4.673,-17.381 9.682,-24.877 4.281,-6.406 9.717,-11.929 16.004,-16.316l-1.271,5.559c-0.246,1.077 0.427,2.15 1.504,2.396 1.077,0.246 2.15,-0.427 2.396,-1.504l2.076,-9.08c0.167,-0.732 0.108,-1.458 -0.109,-2.123 -0.033,-0.348 -0.136,-0.693 -0.314,-1.018 -0.354,-0.642 -0.935,-1.078 -1.586,-1.258 -0.31,-0.167 -0.638,-0.31 -0.998,-0.393l-9.078,-2.076c-0.135,-0.031 -0.269,-0.048 -0.402,-0.051zM111.871,30.743c-0.836,0.055 -1.633,0.527 -2.082,1.316 -0.717,1.263 -0.271,2.864 0.961,3.635 8.143,5.096 14.998,12.029 20.002,20.258 4.936,8.117 7.92,17.253 8.746,26.689l-4.25,-3.395c-0.863,-0.689 -2.121,-0.549 -2.811,0.314 -0.689,0.863 -0.549,2.121 0.314,2.811l7.277,5.813c1.726,1.379 4.242,1.097 5.621,-0.629l5.813,-7.277c0.69,-0.863 0.549,-2.121 -0.314,-2.811 -0.863,-0.689 -2.122,-0.549 -2.811,0.314l-3.59,4.494c-0.886,-10.274 -4.128,-20.221 -9.502,-29.057 -5.469,-8.994 -12.974,-16.564 -21.895,-22.105 -0.463,-0.288 -0.979,-0.404 -1.48,-0.371zM45.191,140.86c-0.852,0.028 -1.671,0.446 -2.164,1.209 -0.789,1.219 -0.443,2.855 0.807,3.596 9.034,5.356 19.211,8.518 29.715,9.217 10.503,0.699 21.012,-1.086 30.676,-5.197 1.337,-0.569 1.897,-2.143 1.277,-3.457 -0.621,-1.313 -2.188,-1.87 -3.527,-1.305 -8.852,3.73 -18.466,5.348 -28.076,4.709 -9.61,-0.64 -18.923,-3.518 -27.203,-8.389 -0.47,-0.276 -0.993,-0.4 -1.504,-0.383z" />
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M68.475,3.03C66.724,3.03 65.046,3.726 63.809,4.964 62.571,6.201 61.875,7.879 61.875,9.63L61.875,49.229c0,1.75 0.696,3.43 1.934,4.668 1.238,1.238 2.916,1.934 4.666,1.934h19.801c1.75,0 3.428,-0.696 4.666,-1.934 1.238,-1.238 1.934,-2.918 1.934,-4.668L94.875,9.63c0,-1.75 -0.696,-3.428 -1.934,-4.666C91.704,3.726 90.026,3.03 88.275,3.03ZM68.475,6.331h19.801c0.875,0 1.715,0.346 2.334,0.965 0.619,0.619 0.965,1.459 0.965,2.334L91.574,49.229c0,0.875 -0.346,1.715 -0.965,2.334C89.99,52.182 89.151,52.53 88.275,52.53L68.475,52.53c-0.875,0 -1.715,-0.348 -2.334,-0.967 -0.619,-0.619 -0.965,-1.459 -0.965,-2.334L65.176,9.63c0,-0.875 0.346,-1.715 0.965,-2.334 0.619,-0.619 1.459,-0.965 2.334,-0.965zM11.25,100.03 L0,111.43v22.799C0,136.319 1.688,138.03 3.75,138.03h22.5c2.063,0 3.75,-1.711 3.75,-3.801L30,103.831C30,101.741 28.313,100.03 26.25,100.03ZM131,103.63c-0.796,0 -1.559,0.316 -2.121,0.879 -0.563,0.562 -0.879,1.325 -0.879,2.121v13.5c-0.796,0 -1.559,0.316 -2.121,0.879 -0.563,0.562 -0.879,1.325 -0.879,2.121v16.5h3v-16.5h24v16.5h3v-16.5c0,-0.796 -0.316,-1.559 -0.879,-2.121 -0.562,-0.563 -1.325,-0.879 -2.121,-0.879v-13.5c0,-0.796 -0.316,-1.559 -0.879,-2.121 -0.562,-0.563 -1.325,-0.879 -2.121,-0.879zM12.807,103.831L26.25,103.831v30.398L3.75,134.229L3.75,113.007ZM131,106.63h18v13.5L131,120.13ZM9.375,109.53v7.6h3.75L13.125,109.53ZM15,109.53v7.6h3.75L18.75,109.53ZM20.625,109.53v7.6h3.75L24.375,109.53Z" />
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="m78.375,46.2c0.875,0 1.715,-0.348 2.333,-0.967 0.619,-0.619 0.966,-1.458 0.966,-2.333 0,-0.875 -0.348,-1.715 -0.966,-2.333C80.09,39.948 79.25,39.6 78.375,39.6c-0.875,0 -1.715,0.348 -2.333,0.966 -0.619,0.619 -0.967,1.458 -0.967,2.333 0,0.875 0.348,1.715 0.967,2.333 0.619,0.619 1.458,0.967 2.333,0.967z" />
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="m134,109.6h4.5v3H134Z" />
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="m141.5,109.6h4.5v3h-4.5z" />
</vector>

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="120dp"
android:height="52dp"
android:viewportWidth="120"
android:viewportHeight="52">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="m15.975,14.267c-0.596,0 -1.168,0.238 -1.589,0.659 -0.422,0.421 -0.659,0.993 -0.659,1.589v10.111c-0.596,0 -1.166,0.236 -1.587,0.658 -0.422,0.421 -0.659,0.993 -0.659,1.589v12.359h2.247v-12.359h17.977v12.359h2.247L33.951,28.873c0,-0.596 -0.236,-1.168 -0.658,-1.589 -0.421,-0.422 -0.993,-0.658 -1.589,-0.658L31.704,16.515c0,-0.596 -0.238,-1.168 -0.659,-1.589 -0.421,-0.422 -0.993,-0.659 -1.589,-0.659zM15.975,16.515L29.456,16.515L29.456,26.626L15.975,26.626ZM18.222,18.739v2.247h3.37v-2.247zM23.84,18.739v2.247h3.37v-2.247z" />
<path
android:fillColor="@color/briar_brand_green"
android:pathData="m47.828,24.463v3.074h18.443l-8.453,8.453 2.182,2.182 12.172,-12.172 -12.172,-12.172 -2.182,2.182 8.453,8.453z" />
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M93.999,0C92.275,0 90.624,0.686 89.405,1.905 88.186,3.124 87.501,4.776 87.501,6.5L87.501,45.498c0,1.724 0.686,3.379 1.905,4.598C90.624,51.314 92.275,52 93.999,52h19.501c1.724,0 3.377,-0.686 4.596,-1.905C119.314,48.877 120,47.222 120,45.498L120,6.5C120,4.776 119.314,3.124 118.095,1.905 116.877,0.686 115.224,0 113.5,0ZM93.999,3.252h19.501c0.862,0 1.689,0.34 2.299,0.949 0.609,0.609 0.951,1.437 0.951,2.299L116.75,45.498c0,0.862 -0.342,1.689 -0.951,2.299 -0.609,0.609 -1.437,0.953 -2.299,0.953L93.999,48.75c-0.862,0 -1.687,-0.344 -2.297,-0.953 -0.609,-0.61 -0.951,-1.437 -0.951,-2.299L90.751,6.5c0,-0.862 0.342,-1.689 0.951,-2.299 0.61,-0.609 1.435,-0.949 2.297,-0.949zM103.749,36.016c-0.862,0 -1.687,0.342 -2.297,0.951 -0.609,0.61 -0.953,1.437 -0.953,2.299 0,0.862 0.344,1.687 0.953,2.297 0.609,0.61 1.435,0.951 2.297,0.951 0.862,0 1.689,-0.342 2.299,-0.951 0.609,-0.609 0.951,-1.435 0.951,-2.297 0,-0.862 -0.342,-1.689 -0.951,-2.299 -0.61,-0.609 -1.437,-0.951 -2.299,-0.951z" />
</vector>

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="120dp"
android:height="52dp"
android:viewportWidth="120"
android:viewportHeight="52">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="m91.223,12.31c-0.596,0 -1.168,0.238 -1.589,0.659 -0.422,0.421 -0.659,0.993 -0.659,1.589v10.111c-0.596,0 -1.166,0.236 -1.587,0.658 -0.422,0.421 -0.659,0.993 -0.659,1.589v12.359h2.247v-12.359h17.977v12.359h2.247L109.199,26.916c0,-0.596 -0.236,-1.168 -0.658,-1.589 -0.421,-0.422 -0.993,-0.658 -1.589,-0.658L106.952,14.558c0,-0.596 -0.238,-1.168 -0.659,-1.589 -0.421,-0.422 -0.993,-0.659 -1.589,-0.659zM91.223,14.558L104.704,14.558L104.704,24.669L91.223,24.669ZM93.47,16.782v2.247h3.37v-2.247zM99.088,16.782v2.247h3.37v-2.247z" />
<path
android:fillColor="@color/briar_brand_green"
android:pathData="m47.828,24.463v3.074h18.443l-8.453,8.453 2.182,2.182 12.172,-12.172 -12.172,-12.172 -2.182,2.182 8.453,8.453z" />
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M6.498,0C4.774,0 3.123,0.686 1.905,1.905 0.686,3.124 0,4.776 0,6.5L0,45.498c0,1.724 0.686,3.379 1.905,4.598C3.123,51.314 4.774,52 6.498,52L25.999,52c1.724,0 3.377,-0.686 4.596,-1.905 1.219,-1.219 1.905,-2.874 1.905,-4.598L32.499,6.5c0,-1.724 -0.686,-3.377 -1.905,-4.596C29.376,0.686 27.723,0 25.999,0ZM6.498,3.252L25.999,3.252c0.862,0 1.689,0.34 2.299,0.949 0.609,0.609 0.951,1.437 0.951,2.299L29.249,45.498c0,0.862 -0.342,1.689 -0.951,2.299 -0.61,0.609 -1.437,0.953 -2.299,0.953L6.498,48.75c-0.862,0 -1.687,-0.344 -2.297,-0.953C3.592,47.187 3.25,46.36 3.25,45.498L3.25,6.5c0,-0.862 0.342,-1.689 0.951,-2.299C4.811,3.592 5.636,3.252 6.498,3.252ZM16.249,36.016c-0.862,0 -1.687,0.342 -2.297,0.951 -0.61,0.61 -0.953,1.437 -0.953,2.299 0,0.862 0.344,1.687 0.953,2.297 0.609,0.61 1.435,0.951 2.297,0.951 0.862,0 1.689,-0.342 2.299,-0.951 0.609,-0.609 0.951,-1.435 0.951,-2.297 0,-0.862 -0.342,-1.689 -0.951,-2.299 -0.61,-0.609 -1.437,-0.951 -2.299,-0.951z" />
</vector>

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iconView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="32dp"
app:layout_constraintBottom_toTopOf="@+id/titleView"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.25"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintWidth_max="200dp"
app:layout_constraintWidth_percent="0.4"
tools:ignore="ContentDescription"
tools:srcCompat="@drawable/alerts_and_states_error"
tools:tint="@color/briar_red_500" />
<TextView
android:id="@+id/titleView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginLeft="32dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="32dp"
android:layout_marginRight="32dp"
android:gravity="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
app:layout_constraintBottom_toTopOf="@+id/textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/iconView"
tools:text="@string/removable_drive_error_send_title" />
<TextView
android:id="@+id/textView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="16dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/titleView"
tools:text="@string/removable_drive_error_send_text" />
<Button
android:id="@+id/button"
style="@style/BriarButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/finish"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/imageView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="32dp"
app:layout_constraintBottom_toTopOf="@+id/introView"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="spread"
app:layout_constraintWidth_percent="0.4"
app:srcCompat="@drawable/ic_transfer_data"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/introView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="16dp"
android:text="@string/removable_drive_intro"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/sendButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />
<Button
android:id="@+id/sendButton"
style="@style/BriarButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/removable_drive_title_send"
app:layout_constraintBottom_toTopOf="@+id/receiveButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/receiveButton"
style="@style/BriarButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/removable_drive_title_receive"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/imageView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="32dp"
app:layout_constraintBottom_toTopOf="@+id/progressBar"
app:layout_constraintDimensionRatio="1,2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="spread"
app:layout_constraintWidth_max="300dp"
app:layout_constraintWidth_percent="0.6"
app:srcCompat="@drawable/ic_transfer_data_send"
tools:ignore="ContentDescription" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="32dp"
android:indeterminate="true"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@+id/introTextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView"
tools:visibility="visible" />
<TextView
android:id="@+id/introTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="16dp"
android:text="@string/removable_drive_receive_intro"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/fileButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/progressBar" />
<Button
android:id="@+id/fileButton"
style="@style/BriarButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:enabled="false"
android:text="@string/removable_drive_receive_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/imageView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="32dp"
app:layout_constraintBottom_toTopOf="@+id/progressBar"
app:layout_constraintDimensionRatio="1,2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintWidth_percent="0.6"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="spread"
app:layout_constraintWidth_max="300dp"
app:srcCompat="@drawable/ic_transfer_data_send"
tools:ignore="ContentDescription" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="32dp"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@+id/introTextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView"
tools:visibility="visible" />
<TextView
android:id="@+id/introTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="16dp"
android:text="@string/removable_drive_send_intro"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/fileButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/progressBar" />
<Button
android:id="@+id/fileButton"
style="@style/BriarButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:enabled="false"
android:text="@string/removable_drive_send_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -10,12 +10,6 @@
android:title="@string/introduction_menu_item"
app:showAsAction="never" />
<item
android:id="@+id/action_set_alias"
android:enabled="false"
android:title="@string/set_contact_alias"
app:showAsAction="never" />
<item
android:id="@+id/action_conversation_settings"
android:title="@string/menu_item_disappearing_messages"
@@ -23,21 +17,43 @@
app:showAsAction="never"
tools:visible="true" />
<item
android:id="@+id/action_connect_via_bluetooth"
android:enabled="false"
android:title="@string/menu_item_connect_via_bluetooth"
app:showAsAction="never" />
<item
android:id="@+id/action_delete_all_messages"
android:title="@string/delete_all_messages"
app:showAsAction="never" />
<item
android:id="@+id/action_social_remove_person"
android:icon="@drawable/action_delete_white"
android:title="@string/delete_contact"
app:showAsAction="never" />
android:title="@string/network_settings_title"
app:showAsAction="never">
<menu>
<item
android:id="@+id/action_connect_via_bluetooth"
android:enabled="false"
android:title="@string/menu_item_connect_via_bluetooth"
app:showAsAction="never" />
<item
android:id="@+id/action_transfer_data"
android:title="@string/removable_drive_menu_title"
android:visible="false"
app:showAsAction="never" />
</menu>
</item>
<item
android:title="@string/menu_contact"
app:showAsAction="never">
<menu>
<item
android:id="@+id/action_set_alias"
android:enabled="false"
android:title="@string/set_contact_alias"
app:showAsAction="never" />
<item
android:id="@+id/action_social_remove_person"
android:title="@string/delete_contact"
app:showAsAction="never" />
</menu>
</item>
</menu>

View File

@@ -147,6 +147,7 @@
<string name="open">Open</string>
<string name="change">Change</string>
<string name="start">Start</string>
<string name="finish">Finish</string>
<string name="no_data">No data</string>
<string name="ellipsis"></string>
<string name="text_too_long">The entered text is too long</string>
@@ -233,6 +234,7 @@
<string name="dialog_title_image_support">You can now send images to this contact</string>
<string name="dialog_message_image_support">Tap this icon to attach images.</string>
<string name="messaging_too_many_attachments_toast">Only the first %d images will be sent</string>
<string name="menu_contact">Contact</string>
<!-- Adding Contacts -->
@@ -685,6 +687,30 @@
<!-- Connections Screen -->
<string name="transports_help_text">Briar can connect to your contacts via the Internet, Wi-Fi or Bluetooth.\n\nAll Internet connections go through the Tor network for privacy.\n\nIf a contact can be reached by multiple methods, Briar uses them in parallel.</string>
<!-- Transfer Data via Removable Drives -->
<string name="removable_drive_menu_title">Transfer data</string>
<string name="removable_drive_intro">You can send encrypted messages to your contact using removable storage such as USB flash drives or SD cards.\n\nIf your contact has sent you a removable drive containing encrypted messages, you can import the messages into Briar by using the receive button below.</string>
<string name="removable_drive_title_send">Send data</string>
<string name="removable_drive_title_receive">Receive data</string>
<string name="removable_drive_send_intro">Tap the button below to create a new file containing the encrypted messages. You can choose where the file will be saved.\n\nIf you want to save the file on a removable drive, insert the drive now.</string>
<string name="removable_drive_send_no_data">There are currently no messages waiting to be sent to this contact.</string>
<string name="removable_drive_send_not_supported">This contact is using an old version of Briar which does not yet support this feature.</string>
<string name="removable_drive_send_button">Choose file for export</string>
<string name="removable_drive_ongoing">Please wait for ongoing task to complete</string>
<string name="removable_drive_receive_intro">Tap the button below to choose the file that your contact sent you.\n\nIf the file is on a removable drive, insert the drive now.</string>
<string name="removable_drive_receive_button">Choose file for import</string>
<string name="removable_drive_success_send_title">Export successful</string>
<string name="removable_drive_success_send_text">Data exported successfully. You now have 14 days to transport the file to your contact.\n\nIf the file is on a removable drive, use the notification in the status bar to eject the drive before unplugging it.</string>
<string name="removable_drive_success_receive_title">Import successful</string>
<string name="removable_drive_success_receive_text">All encrypted messages contained in this file have been received.</string>
<string name="removable_drive_error_send_title">Error exporting data</string>
<string name="removable_drive_error_send_text">There was an error writing data to the file.\n\nIf you are using a removable drive, ensure that it is properly inserted and try again.\n\nIf the error persists, please send feedback to let the Briar team know about the issue.</string>
<string name="removable_drive_error_receive_title">Error importing data</string>
<string name="removable_drive_error_receive_text">The selected file did not contain anything that Briar could recognize.\n\nPlease check that you chose the right file.\n\nIf your contact created the file more than 14 days ago, Briar will not be able to recognize it.</string>
<!-- Screenshots -->
<!-- This is a name to be used in screenshots. Feel free to change it to a local name. -->

View File

@@ -96,5 +96,6 @@ internal class HeadlessModule(private val appDir: File) {
override fun shouldEnableProfilePictures() = false
override fun shouldEnableDisappearingMessages() = false
override fun shouldEnableConnectViaBluetooth() = false
override fun shouldEnableTransferData() = false
}
}