diff --git a/LICENSE.txt b/LICENSE.txt index 1b5f2163e9d346d6e818fbf09a4769ffa8bb8c3a..3d615ecb5d4dfabc0df096c2e22a4319c35d1589 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -13,23 +13,6 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. -------------------------------------------------------------------------- - - All files under the directories briar-android/src, briar-api/src, - briar-core/src, briar-desktop/src and briar-test/src are licensed - under the Apache License, version 2.0 (the "License"); you may not - use these files except in compliance with the License. - - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied. See the License for the specific language governing - permissions and limitations under the License. - ------------------------------------------------------------------------- GNU GENERAL PUBLIC LICENSE diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml index 5da35cdf462510dd5206de26c2692a3ed516497d..25437944268018098ab7ba77ac68f0d62322b8c6 100644 --- a/briar-android/AndroidManifest.xml +++ b/briar-android/AndroidManifest.xml @@ -134,7 +134,8 @@ <activity android:name=".android.forum.ForumActivity" android:label="@string/app_name" - android:parentActivityName=".android.NavDrawerActivity"> + android:parentActivityName=".android.NavDrawerActivity" + android:windowSoftInputMode="adjustResize|stateHidden"> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value=".android.NavDrawerActivity" diff --git a/briar-android/artwork/ic_emoji_emoticons.svg b/briar-android/artwork/ic_emoji_emoticons.svg new file mode 100644 index 0000000000000000000000000000000000000000..7ef60e16b09175f557649d9639d94107f41b7d78 --- /dev/null +++ b/briar-android/artwork/ic_emoji_emoticons.svg @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + id="svg2" + version="1.1" + inkscape:version="0.91 r13725" + width="24" + height="24" + viewBox="0 0 24 24" + sodipodi:docname="ic_emoji_emoticons.svg"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6" /> + <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="1021" + id="namedview4" + showgrid="false" + inkscape:zoom="4.6354778" + inkscape:cx="47.926788" + inkscape:cy="24.127496" + inkscape:window-x="1440" + inkscape:window-y="23" + inkscape:window-maximized="0" + inkscape:current-layer="svg2" /> + <path + style="fill:#000000;fill-opacity:1" + d="m 15.483903,3.8556996 c -0.661546,0.040406 -0.536253,1.2125273 -0.08054,1.6240791 1.361771,1.4519837 1.747379,3.5080793 1.895646,5.4253553 0.109142,2.216286 -0.0846,4.555699 -1.171466,6.533591 -0.361828,0.731167 -1.339597,1.273078 -1.15283,2.195835 0.287109,1.037426 1.187031,0.242862 1.620751,-0.183708 1.991711,-1.742024 2.867744,-4.428018 2.93133,-7.013492 0.02009,-1.918049 -0.231841,-3.9213035 -1.212735,-5.6044037 -0.664187,-1.0906817 -1.39072,-2.2339438 -2.497355,-2.9193489 -0.127976,-0.045915 -0.238296,-0.06368 -0.332802,-0.057908 z M 5.9118212,7.6583077 A 1.3631614,1.3631614 0 0 0 4.54866,9.0214691 1.3631614,1.3631614 0 0 0 5.9118212,10.38463 1.3631614,1.3631614 0 0 0 7.2749824,9.0214691 1.3631614,1.3631614 0 0 0 5.9118212,7.6583077 Z m 3.0731032,3.0012183 0,2.044742 4.7710646,0 0,-2.044742 -4.7710646,0 z m -3.1496485,3.471136 a 1.3631614,1.3631614 0 0 0 -1.3631612,1.363161 1.3631614,1.3631614 0 0 0 1.3631612,1.363161 1.3631614,1.3631614 0 0 0 1.3631612,-1.363161 1.3631614,1.3631614 0 0 0 -1.3631612,-1.363161 z" + id="path4142" + inkscape:connector-curvature="0" /> +</svg> diff --git a/briar-android/assets/emoji_activity.png b/briar-android/assets/emoji_activity.png new file mode 100644 index 0000000000000000000000000000000000000000..908370dd15ec125d7b57b96ae932ee5fbe58a9d9 Binary files /dev/null and b/briar-android/assets/emoji_activity.png differ diff --git a/briar-android/assets/emoji_animals_nature.png b/briar-android/assets/emoji_animals_nature.png new file mode 100644 index 0000000000000000000000000000000000000000..7a0661fe1ff7b9c82e2a36381ce303ceb1246693 Binary files /dev/null and b/briar-android/assets/emoji_animals_nature.png differ diff --git a/briar-android/assets/emoji_flags.png b/briar-android/assets/emoji_flags.png new file mode 100644 index 0000000000000000000000000000000000000000..80f6bbd0db5f9e5e4a9938a344f064372e894606 Binary files /dev/null and b/briar-android/assets/emoji_flags.png differ diff --git a/briar-android/assets/emoji_food_drink.png b/briar-android/assets/emoji_food_drink.png new file mode 100644 index 0000000000000000000000000000000000000000..33d7cd0a6849759c4ccb852a8938afd5354ec25b Binary files /dev/null and b/briar-android/assets/emoji_food_drink.png differ diff --git a/briar-android/assets/emoji_objects.png b/briar-android/assets/emoji_objects.png new file mode 100644 index 0000000000000000000000000000000000000000..f2b6dfbf9d7b20287d6e95a9739373f37f5eaaaa Binary files /dev/null and b/briar-android/assets/emoji_objects.png differ diff --git a/briar-android/assets/emoji_smiley_people.png b/briar-android/assets/emoji_smiley_people.png new file mode 100644 index 0000000000000000000000000000000000000000..9325d703a8010ac1ca938f3a8b20836cc1efc4c0 Binary files /dev/null and b/briar-android/assets/emoji_smiley_people.png differ diff --git a/briar-android/assets/emoji_symbols.png b/briar-android/assets/emoji_symbols.png new file mode 100644 index 0000000000000000000000000000000000000000..e88275b2fc82bc0a03759a642186a8184c8746f9 Binary files /dev/null and b/briar-android/assets/emoji_symbols.png differ diff --git a/briar-android/assets/emoji_travel_places.png b/briar-android/assets/emoji_travel_places.png new file mode 100644 index 0000000000000000000000000000000000000000..16e2be37d680dd410b6856de655770f3c7cbbac8 Binary files /dev/null and b/briar-android/assets/emoji_travel_places.png differ diff --git a/briar-android/build.gradle b/briar-android/build.gradle index dc93b62fb5c70e59fb8a55513742493a91e9efc4..469df63b22a344d580c5f68e37b1d19cbdf57fb5 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -35,6 +35,8 @@ dependencies { compile 'com.google.zxing:core:3.2.1' apt 'com.google.dagger:dagger-compiler:2.0.2' provided 'javax.annotation:jsr250-api:1.0' + compile 'com.jpardogo.materialtabstrip:library:1.1.0' + compile 'com.github.bumptech.glide:glide:3.7.0' testCompile 'junit:junit:4.12' testCompile 'net.jodah:concurrentunit:0.4.2' @@ -60,6 +62,8 @@ dependencyVerification { 'com.android.support:support-vector-drawable:799bafe4c3de812386f0b291f744d5d6876452722dd40189b9ab87dbbf594ea1', 'com.android.support:recyclerview-v7:44040a888e23e0c93162a3377cfe06751080e3c22d369ab0d4301ef60d63b0fe', 'com.android.support:cardview-v7:4595f1c4a28cfa083b6c0920ad4d49e1c2ca4b8302a955e548f68eb63b74931b', + 'com.jpardogo.materialtabstrip:library:24d19232b319f8c73e25793432357919a7ed972186f57a3b2c9093ea74ad8311', + 'com.github.bumptech.glide:glide:76ef123957b5fbaebb05fcbe6606dd58c3bc3fcdadb257f99811d0ac9ea9b88b', ] } diff --git a/briar-android/proguard-rules.txt b/briar-android/proguard-rules.txt index 6a936b7e88129ba55c61805ce199eca7d83e41e7..94cfcea727efe8469baa5c5f3947d2e52ca9ca66 100644 --- a/briar-android/proguard-rules.txt +++ b/briar-android/proguard-rules.txt @@ -60,3 +60,8 @@ -dontwarn java.nio.** -dontwarn org.codehaus.mojo.animal_sniffer.** -dontwarn org.slf4j.impl.** + +-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { + **[] $VALUES; + public *; +} \ No newline at end of file diff --git a/briar-android/res/drawable/ic_backspace_black.xml b/briar-android/res/drawable/ic_backspace_black.xml new file mode 100644 index 0000000000000000000000000000000000000000..b0224405a66c0fc1972b4469daecd4cd11c8db6a --- /dev/null +++ b/briar-android/res/drawable/ic_backspace_black.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:alpha="0.54" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M22,3L7,3c-0.69,0 -1.23,0.35 -1.59,0.88L0,12l5.41,8.11c0.36,0.53 0.9,0.89 1.59,0.89h15c1.1,0 2,-0.9 2,-2L24,5c0,-1.1 -0.9,-2 -2,-2zM19,15.59L17.59,17 14,13.41 10.41,17 9,15.59 12.59,12 9,8.41 10.41,7 14,10.59 17.59,7 19,8.41 15.41,12 19,15.59z"/> +</vector> diff --git a/briar-android/res/drawable/ic_emoji_activity.xml b/briar-android/res/drawable/ic_emoji_activity.xml new file mode 100644 index 0000000000000000000000000000000000000000..cdec171106ede103ee3fe62d539818502c42b33d --- /dev/null +++ b/briar-android/res/drawable/ic_emoji_activity.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#FF2D3E50" + android:pathData="M7.5,7.5C9.17,5.87 11.29,4.69 13.37,4.18C15.46,3.67 17.5,3.83 18.6,4C19.71,4.15 19.87,4.31 20.03,5.41C20.18,6.5 20.33,8.55 19.82,10.63C19.31,12.71 18.13,14.83 16.5,16.5C14.83,18.13 12.71,19.31 10.63,19.82C8.55,20.33 6.5,20.18 5.41,20.03C4.31,19.87 4.15,19.71 4,18.6C3.83,17.5 3.67,15.46 4.18,13.37C4.69,11.29 5.87,9.17 7.5,7.5M7.3,15.79L8.21,16.7L9.42,15.5L10.63,16.7L11.54,15.79L10.34,14.58L12,12.91L13.21,14.12L14.12,13.21L12.91,12L14.58,10.34L15.79,11.54L16.7,10.63L15.5,9.42L16.7,8.21L15.79,7.3L14.58,8.5L13.37,7.3L12.46,8.21L13.66,9.42L12,11.09L10.79,9.88L9.88,10.79L11.09,12L9.42,13.66L8.21,12.46L7.3,13.37L8.5,14.58L7.3,15.79Z"/> +</vector> \ No newline at end of file diff --git a/briar-android/res/drawable/ic_emoji_animals_nature.xml b/briar-android/res/drawable/ic_emoji_animals_nature.xml new file mode 100644 index 0000000000000000000000000000000000000000..d63c313653b45af47964eb454c7f1c77683becc1 --- /dev/null +++ b/briar-android/res/drawable/ic_emoji_animals_nature.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF2D3E50" + android:pathData="M18.7,12.4c-0.28,-0.16 -0.57,-0.29 -0.86,-0.4 0.29,-0.11 0.58,-0.24 0.86,-0.4 1.92,-1.11 2.99,-3.12 3,-5.19 -1.79,-1.03 -4.07,-1.11 -6,0 -0.28,0.16 -0.54,0.35 -0.78,0.54 0.05,-0.31 0.08,-0.63 0.08,-0.95 0,-2.22 -1.21,-4.15 -3,-5.19C10.21,1.85 9,3.78 9,6c0,0.32 0.03,0.64 0.08,0.95 -0.24,-0.2 -0.5,-0.39 -0.78,-0.55 -1.92,-1.11 -4.2,-1.03 -6,0 0,2.07 1.07,4.08 3,5.19 0.28,0.16 0.57,0.29 0.86,0.4 -0.29,0.11 -0.58,0.24 -0.86,0.4 -1.92,1.11 -2.99,3.12 -3,5.19 1.79,1.03 4.07,1.11 6,0 0.28,-0.16 0.54,-0.35 0.78,-0.54 -0.05,0.32 -0.08,0.64 -0.08,0.96 0,2.22 1.21,4.15 3,5.19 1.79,-1.04 3,-2.97 3,-5.19 0,-0.32 -0.03,-0.64 -0.08,-0.95 0.24,0.2 0.5,0.38 0.78,0.54 1.92,1.11 4.2,1.03 6,0 -0.01,-2.07 -1.08,-4.08 -3,-5.19zM12,16c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4z"/> +</vector> diff --git a/briar-android/res/drawable/ic_emoji_emoticons.xml b/briar-android/res/drawable/ic_emoji_emoticons.xml new file mode 100644 index 0000000000000000000000000000000000000000..cb97f4e15aa631a1dfabfed6229c8521e0cd1d7f --- /dev/null +++ b/briar-android/res/drawable/ic_emoji_emoticons.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + + <path + android:fillColor="#FF2D3E50" + android:pathData="M15.4839,3.8557 C14.8224,3.89611,14.9476,5.06823,15.4034,5.47978 +C16.7652,6.93176,17.1508,8.98786,17.299,10.9051 +C17.4081,13.1214,17.2144,15.4608,16.1275,17.4387 +C15.7657,18.1699,14.7879,18.7118,14.9747,19.6345 +C15.2618,20.6719,16.1617,19.8774,16.5955,19.4508 +C18.5872,17.7088,19.4632,15.0228,19.5268,12.4373 +C19.5469,10.5193,19.295,8.516,18.3141,6.8329 +C17.6499,5.74222,16.9234,4.59896,15.8167,3.91355 +C15.6887,3.86763,15.5784,3.84987,15.4839,3.85564 Z M5.91182,7.65831 +A1.3631614,1.3631614,0,0,0,4.54866,9.02147 +A1.3631614,1.3631614,0,0,0,5.91182,10.3846 +A1.3631614,1.3631614,0,0,0,7.27498,9.02147 +A1.3631614,1.3631614,0,0,0,5.91182,7.65831 Z M8.98492,10.6595 L8.98492,12.7042 +L13.756,12.7042 L13.756,10.6595 L8.98494,10.6595 Z M5.83527,14.1306 +A1.3631614,1.3631614,0,0,0,4.47211,15.4938 +A1.3631614,1.3631614,0,0,0,5.83527,16.857 +A1.3631614,1.3631614,0,0,0,7.19843,15.4938 +A1.3631614,1.3631614,0,0,0,5.83527,14.1306 Z"/> +</vector> \ No newline at end of file diff --git a/briar-android/res/drawable/ic_emoji_flags.xml b/briar-android/res/drawable/ic_emoji_flags.xml new file mode 100644 index 0000000000000000000000000000000000000000..68976f28c109287a74d3bb588877f69dc10275b7 --- /dev/null +++ b/briar-android/res/drawable/ic_emoji_flags.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF2D3E50" + android:pathData="M14.4,6L14,4H5v17h2v-7h5.6l0.4,2h7V6z"/> +</vector> diff --git a/briar-android/res/drawable/ic_emoji_food_drink.xml b/briar-android/res/drawable/ic_emoji_food_drink.xml new file mode 100644 index 0000000000000000000000000000000000000000..958664070d98b1b8993907540d94dedf0a709ea6 --- /dev/null +++ b/briar-android/res/drawable/ic_emoji_food_drink.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF2D3E50" + android:pathData="M12,6c1.11,0 2,-0.9 2,-2 0,-0.38 -0.1,-0.73 -0.29,-1.03L12,0l-1.71,2.97c-0.19,0.3 -0.29,0.65 -0.29,1.03 0,1.1 0.9,2 2,2zM16.6,15.99l-1.07,-1.07 -1.08,1.07c-1.3,1.3 -3.58,1.31 -4.89,0l-1.07,-1.07 -1.09,1.07C6.75,16.64 5.88,17 4.96,17c-0.73,0 -1.4,-0.23 -1.96,-0.61L3,21c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1v-4.61c-0.56,0.38 -1.23,0.61 -1.96,0.61 -0.92,0 -1.79,-0.36 -2.44,-1.01zM18,9h-5L13,7h-2v2L6,9c-1.66,0 -3,1.34 -3,3v1.54c0,1.08 0.88,1.96 1.96,1.96 0.52,0 1.02,-0.2 1.38,-0.57l2.14,-2.13 2.13,2.13c0.74,0.74 2.03,0.74 2.77,0l2.14,-2.13 2.13,2.13c0.37,0.37 0.86,0.57 1.38,0.57 1.08,0 1.96,-0.88 1.96,-1.96L20.99,12C21,10.34 19.66,9 18,9z"/> +</vector> diff --git a/briar-android/res/drawable/ic_emoji_objects.xml b/briar-android/res/drawable/ic_emoji_objects.xml new file mode 100644 index 0000000000000000000000000000000000000000..70f0ec6ea51310594bbfbcc54adce7d05f00b457 --- /dev/null +++ b/briar-android/res/drawable/ic_emoji_objects.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#FF2D3E50" + android:pathData="M5,16L3,5L8.5,12L12,5L15.5,12L21,5L19,16H5M19,19A1,1 0 0,1 18,20H6A1,1 0 0,1 5,19V18H19V19Z"/> +</vector> \ No newline at end of file diff --git a/briar-android/res/drawable/ic_emoji_recent.xml b/briar-android/res/drawable/ic_emoji_recent.xml new file mode 100644 index 0000000000000000000000000000000000000000..3f891e2e43241fd849de538faea493738eed1521 --- /dev/null +++ b/briar-android/res/drawable/ic_emoji_recent.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF2D3E50" + android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/> +</vector> diff --git a/briar-android/res/drawable/ic_emoji_smiley_people.xml b/briar-android/res/drawable/ic_emoji_smiley_people.xml new file mode 100644 index 0000000000000000000000000000000000000000..12dfdcd2e065a22ef7e5262179ea16bfb4d90e0f --- /dev/null +++ b/briar-android/res/drawable/ic_emoji_smiley_people.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF2D3E50" + android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM15.5,11c0.83,0 1.5,-0.67 1.5,-1.5S16.33,8 15.5,8 14,8.67 14,9.5s0.67,1.5 1.5,1.5zM8.5,11c0.83,0 1.5,-0.67 1.5,-1.5S9.33,8 8.5,8 7,8.67 7,9.5 7.67,11 8.5,11zM12,17.5c2.33,0 4.31,-1.46 5.11,-3.5L6.89,14c0.8,2.04 2.78,3.5 5.11,3.5z"/> +</vector> diff --git a/briar-android/res/drawable/ic_emoji_symbols.xml b/briar-android/res/drawable/ic_emoji_symbols.xml new file mode 100644 index 0000000000000000000000000000000000000000..28d8ff8cf50e639137b4cb93a06335230a7af4c1 --- /dev/null +++ b/briar-android/res/drawable/ic_emoji_symbols.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF2D3E50" + android:pathData="M12,7.77L18.39,18H5.61L12,7.77M12,4L2,20h20L12,4z"/> +</vector> diff --git a/briar-android/res/drawable/ic_emoji_toggle.xml b/briar-android/res/drawable/ic_emoji_toggle.xml new file mode 100644 index 0000000000000000000000000000000000000000..b497d47fd3d7181ab112b06ed61be9abc1840ff8 --- /dev/null +++ b/briar-android/res/drawable/ic_emoji_toggle.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:alpha="0.54" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM15.5,11c0.83,0 1.5,-0.67 1.5,-1.5S16.33,8 15.5,8 14,8.67 14,9.5s0.67,1.5 1.5,1.5zM8.5,11c0.83,0 1.5,-0.67 1.5,-1.5S9.33,8 8.5,8 7,8.67 7,9.5 7.67,11 8.5,11zM12,17.5c2.33,0 4.31,-1.46 5.11,-3.5L6.89,14c0.8,2.04 2.78,3.5 5.11,3.5z"/> +</vector> diff --git a/briar-android/res/drawable/ic_emoji_travel_places.xml b/briar-android/res/drawable/ic_emoji_travel_places.xml new file mode 100644 index 0000000000000000000000000000000000000000..a0534fbbfc6d4b6a7df18f7caaf2239f6c09e7ed --- /dev/null +++ b/briar-android/res/drawable/ic_emoji_travel_places.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF2D3E50" + android:pathData="M18.92,6.01C18.72,5.42 18.16,5 17.5,5h-11c-0.66,0 -1.21,0.42 -1.42,1.01L3,12v8c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h12v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-8l-2.08,-5.99zM6.5,16c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,13 6.5,13s1.5,0.67 1.5,1.5S7.33,16 6.5,16zM17.5,16c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM5,11l1.5,-4.5h11L19,11L5,11z"/> +</vector> diff --git a/briar-android/res/drawable/ic_keyboard_black.xml b/briar-android/res/drawable/ic_keyboard_black.xml new file mode 100644 index 0000000000000000000000000000000000000000..f1f26791ab498485f2d619a3e0477ce9a1bbe28d --- /dev/null +++ b/briar-android/res/drawable/ic_keyboard_black.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:alpha="0.54" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M20,5L4,5c-1.1,0 -1.99,0.9 -1.99,2L2,17c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,7c0,-1.1 -0.9,-2 -2,-2zM11,8h2v2h-2L11,8zM11,11h2v2h-2v-2zM8,8h2v2L8,10L8,8zM8,11h2v2L8,13v-2zM7,13L5,13v-2h2v2zM7,10L5,10L5,8h2v2zM16,17L8,17v-2h8v2zM16,13h-2v-2h2v2zM16,10h-2L14,8h2v2zM19,13h-2v-2h2v2zM19,10h-2L17,8h2v2z"/> +</vector> diff --git a/briar-android/res/layout/activity_conversation.xml b/briar-android/res/layout/activity_conversation.xml index 8f0fa1a349091f735aea2c492be12237dded4a8f..cfd7cd4d27cd249b541dd1c0e7618adc9f300008 100644 --- a/briar-android/res/layout/activity_conversation.xml +++ b/briar-android/res/layout/activity_conversation.xml @@ -3,9 +3,9 @@ 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:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" + android:orientation="vertical" tools:context=".android.contact.ConversationActivity"> <android.support.v7.widget.Toolbar @@ -38,17 +38,18 @@ </android.support.v7.widget.Toolbar> - <org.briarproject.android.util.BriarRecyclerView + <org.briarproject.android.view.BriarRecyclerView android:id="@+id/conversationView" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_weight="1"/> + android:layout_weight="1" + android:background="@color/window_background"/> - <View style="@style/Divider.Horizontal"/> - - <include - layout="@layout/text_input_field" + <org.briarproject.android.view.TextInputView + android:id="@+id/text_input_container" android:layout_width="match_parent" - android:layout_height="wrap_content"/> + android:layout_height="wrap_content" + android:background="@color/button_bar_background" + android:elevation="@dimen/margin_tiny"/> </LinearLayout> \ No newline at end of file diff --git a/briar-android/res/layout/activity_forum.xml b/briar-android/res/layout/activity_forum.xml index 946ce34e8a3ac99193729a6f6354ab540935b1ca..f8e1b609ce4c0084c6b8bd6586e375c24289a1c5 100644 --- a/briar-android/res/layout/activity_forum.xml +++ b/briar-android/res/layout/activity_forum.xml @@ -6,16 +6,18 @@ android:layout_height="match_parent" android:orientation="vertical"> - <org.briarproject.android.util.BriarRecyclerView + <org.briarproject.android.view.BriarRecyclerView android:id="@+id/forum_discussion_list" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" app:scrollToEnd="false"/> - <include - layout="@layout/text_input_field" + <org.briarproject.android.view.TextInputView + android:id="@+id/text_input_container" android:layout_width="match_parent" - android:layout_height="wrap_content"/> + android:layout_height="wrap_content" + android:background="@color/button_bar_background" + android:elevation="@dimen/margin_tiny"/> </LinearLayout> \ No newline at end of file diff --git a/briar-android/res/layout/activity_invitations.xml b/briar-android/res/layout/activity_invitations.xml index 25cd76af43f190df62a3fb69ecae4705af87b7f0..4d3d61cea8a12c6d62b84193ee0866489f6d54e3 100644 --- a/briar-android/res/layout/activity_invitations.xml +++ b/briar-android/res/layout/activity_invitations.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<org.briarproject.android.util.BriarRecyclerView +<org.briarproject.android.view.BriarRecyclerView android:id="@+id/invitationsView" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" diff --git a/briar-android/res/layout/activity_rss_feed_manage.xml b/briar-android/res/layout/activity_rss_feed_manage.xml index a9fc464d9c1b0ec17177d723c7d397d86babe579..8c9a0c468181d7e78c4aa1c33c51476494017d0a 100644 --- a/briar-android/res/layout/activity_rss_feed_manage.xml +++ b/briar-android/res/layout/activity_rss_feed_manage.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<org.briarproject.android.util.BriarRecyclerView +<org.briarproject.android.view.BriarRecyclerView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" diff --git a/briar-android/res/layout/activity_sharing_status.xml b/briar-android/res/layout/activity_sharing_status.xml index efc4a8fc0890c75e89ca787d6de43e31f0a2e34d..48d2b7263a2bd3e27d48d5728c7fa43e81f5ebaf 100644 --- a/briar-android/res/layout/activity_sharing_status.xml +++ b/briar-android/res/layout/activity_sharing_status.xml @@ -19,7 +19,7 @@ <View style="@style/Divider.ForumList"/> - <org.briarproject.android.util.BriarRecyclerView + <org.briarproject.android.view.BriarRecyclerView android:id="@+id/sharedByView" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -36,7 +36,7 @@ <View style="@style/Divider.ForumList"/> - <org.briarproject.android.util.BriarRecyclerView + <org.briarproject.android.view.BriarRecyclerView android:id="@+id/sharedWithView" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/briar-android/res/layout/author_view.xml b/briar-android/res/layout/author_view.xml index 405376fac9b46eeec2780eddbf3e44d9a356dc9c..42a5655a5062b11ebff29ef6bfd4b28a74438e90 100644 --- a/briar-android/res/layout/author_view.xml +++ b/briar-android/res/layout/author_view.xml @@ -37,7 +37,7 @@ android:textSize="@dimen/text_size_small" tools:text="Author Name"/> - <org.briarproject.android.util.TrustIndicatorView + <org.briarproject.android.view.TrustIndicatorView android:id="@+id/trustIndicator" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/briar-android/res/layout/emoji_drawer.xml b/briar-android/res/layout/emoji_drawer.xml new file mode 100644 index 0000000000000000000000000000000000000000..37f3723339a2cf728ecbe4ae61d59f5916275cce --- /dev/null +++ b/briar-android/res/layout/emoji_drawer.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <View + style="@style/Divider.Horizontal"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="40dp" + android:background="@color/emoji_pager_background" + android:orientation="horizontal"> + + <com.astuetz.PagerSlidingTabStrip + android:id="@+id/tabs" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + app:pstsIndicatorColor="@color/briar_accent" + app:pstsIndicatorHeight="@dimen/emoji_drawer_indicator_height" + app:pstsShouldExpand="true" + app:pstsTabPaddingLeftRight="@dimen/emoji_drawer_left_right_padding"/> + + <View + android:layout_width="@dimen/margin_separator" + android:layout_height="match_parent" + android:layout_marginBottom="10dp" + android:layout_marginTop="10dp" + android:background="@color/divider"/> + + <org.thoughtcrime.securesms.components.RepeatableImageKey + android:id="@+id/backspace" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:background="@color/emoji_pager_background" + android:paddingLeft="@dimen/margin_medium" + android:paddingRight="@dimen/margin_medium" + android:src="@drawable/ic_backspace_black"/> + + </LinearLayout> + + <View + style="@style/Divider.Horizontal"/> + + <android.support.v4.view.ViewPager + android:id="@+id/emoji_pager" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/window_background" + android:visibility="visible"/> + +</merge> diff --git a/briar-android/res/layout/emoji_grid_layout.xml b/briar-android/res/layout/emoji_grid_layout.xml new file mode 100644 index 0000000000000000000000000000000000000000..c1486e2dcb94a9f7ff14ce5dc7bc4504528372da --- /dev/null +++ b/briar-android/res/layout/emoji_grid_layout.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <GridView + android:id="@+id/emoji" + android:layout_width="fill_parent" + android:layout_height="match_parent" + android:columnWidth="@dimen/emoji_drawer_size" + android:gravity="center" + android:horizontalSpacing="0dp" + android:numColumns="auto_fit" + android:stretchMode="columnWidth" + android:verticalSpacing="0dp" + android:visibility="visible"/> + +</FrameLayout> \ No newline at end of file diff --git a/briar-android/res/layout/fragment_blog.xml b/briar-android/res/layout/fragment_blog.xml index c0ac19fbe0eac0b76817a2c7bc9bb8566f6aa840..bfc1c777a24cf170c6addd1cdc0c37b1ebe7e810 100644 --- a/briar-android/res/layout/fragment_blog.xml +++ b/briar-android/res/layout/fragment_blog.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<org.briarproject.android.util.BriarRecyclerView +<org.briarproject.android.view.BriarRecyclerView android:id="@+id/postList" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" diff --git a/briar-android/res/layout/fragment_contact_list.xml b/briar-android/res/layout/fragment_contact_list.xml index 787d4dff960f06c4f8d8b0c4d56fe73aa61baef0..de3bddebe14b129d135d400ace09f2d6081a9f4e 100644 --- a/briar-android/res/layout/fragment_contact_list.xml +++ b/briar-android/res/layout/fragment_contact_list.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<org.briarproject.android.util.BriarRecyclerView +<org.briarproject.android.view.BriarRecyclerView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/contactList" android:layout_width="match_parent" diff --git a/briar-android/res/layout/fragment_forum_list.xml b/briar-android/res/layout/fragment_forum_list.xml index bb4e4ae761bc2d706a7e8e21636305f5271ba60e..ca4fb452052bd7180b1b4c9dc4e9f63a454e97de 100644 --- a/briar-android/res/layout/fragment_forum_list.xml +++ b/briar-android/res/layout/fragment_forum_list.xml @@ -5,11 +5,11 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <org.briarproject.android.util.BriarRecyclerView + <org.briarproject.android.view.BriarRecyclerView android:id="@+id/forumList" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - app:layout_behavior="org.briarproject.android.util.BriarRecyclerViewBehavior"/> + app:layout_behavior="org.briarproject.android.view.BriarRecyclerViewBehavior"/> </android.support.design.widget.CoordinatorLayout> diff --git a/briar-android/res/layout/fragment_keyagreement_qr.xml b/briar-android/res/layout/fragment_keyagreement_qr.xml index 5f1b175ed567f5c794729da1aa04516f2996a9c6..993045ef9b3b78d10dc2193a86e9c18cad8a8024 100644 --- a/briar-android/res/layout/fragment_keyagreement_qr.xml +++ b/briar-android/res/layout/fragment_keyagreement_qr.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <org.briarproject.android.util.CameraView + <org.briarproject.android.view.CameraView android:id="@+id/camera_view" android:layout_width="match_parent" android:layout_height="match_parent"/> diff --git a/briar-android/res/layout/introduction_contact_chooser.xml b/briar-android/res/layout/introduction_contact_chooser.xml index 8363191ac0db145c3086706f2f9408fbc479a1f6..4a99ecbed9378374dfb47cf3e3d355d52e564569 100644 --- a/briar-android/res/layout/introduction_contact_chooser.xml +++ b/briar-android/res/layout/introduction_contact_chooser.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<org.briarproject.android.util.BriarRecyclerView +<org.briarproject.android.view.BriarRecyclerView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/contactList" diff --git a/briar-android/res/layout/list_item_blog.xml b/briar-android/res/layout/list_item_blog.xml index 1fe1f22db024383b68baea541fbfea8e11eb88e3..24127ced5f70ac916727cc76d014aaff267fbb4f 100644 --- a/briar-android/res/layout/list_item_blog.xml +++ b/briar-android/res/layout/list_item_blog.xml @@ -8,7 +8,7 @@ android:layout_marginStart="@dimen/listitem_horizontal_margin" android:background="?attr/selectableItemBackground"> - <org.briarproject.android.util.TextAvatarView + <org.briarproject.android.view.TextAvatarView android:id="@+id/avatarView" android:layout_width="@dimen/listitem_picture_frame_size" android:layout_height="@dimen/listitem_picture_frame_size" diff --git a/briar-android/res/layout/list_item_blog_comment.xml b/briar-android/res/layout/list_item_blog_comment.xml index 1be478ab2fd9f60b3e7235034c1ee8f9e52544d3..c6ff62916ed93058a1951af0010a87fe242242ce 100644 --- a/briar-android/res/layout/list_item_blog_comment.xml +++ b/briar-android/res/layout/list_item_blog_comment.xml @@ -11,7 +11,7 @@ android:id="@+id/inputDivider" style="@style/Divider.Horizontal"/> - <org.briarproject.android.util.AuthorView + <org.briarproject.android.view.AuthorView android:id="@+id/authorView" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/briar-android/res/layout/list_item_blog_post.xml b/briar-android/res/layout/list_item_blog_post.xml index d3cd14d489ab70c9b6dced4a313af848eb68807a..0b6803eedb1a9cb792e133d6f76220d016ac8173 100644 --- a/briar-android/res/layout/list_item_blog_post.xml +++ b/briar-android/res/layout/list_item_blog_post.xml @@ -19,7 +19,7 @@ android:layout_height="wrap_content" android:padding="@dimen/listitem_vertical_margin"> - <org.briarproject.android.util.AuthorView + <org.briarproject.android.view.AuthorView android:id="@+id/rebloggerView" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -29,7 +29,7 @@ android:layout_toLeftOf="@+id/commentView" app:persona="reblogger"/> - <org.briarproject.android.util.AuthorView + <org.briarproject.android.view.AuthorView android:id="@+id/authorView" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/briar-android/res/layout/list_item_forum.xml b/briar-android/res/layout/list_item_forum.xml index 0652bec6b01927253e4bbe35986de7a1ace3cb24..3dc74083fe11a6aed64df05066e110193ce5ff27 100644 --- a/briar-android/res/layout/list_item_forum.xml +++ b/briar-android/res/layout/list_item_forum.xml @@ -8,7 +8,7 @@ android:layout_marginStart="@dimen/listitem_horizontal_margin" android:background="?attr/selectableItemBackground"> - <org.briarproject.android.util.TextAvatarView + <org.briarproject.android.view.TextAvatarView android:id="@+id/avatarView" android:layout_width="@dimen/listitem_picture_frame_size" android:layout_height="@dimen/listitem_picture_frame_size" diff --git a/briar-android/res/layout/forum_discussion_cell.xml b/briar-android/res/layout/list_item_forum_post.xml similarity index 72% rename from briar-android/res/layout/forum_discussion_cell.xml rename to briar-android/res/layout/list_item_forum_post.xml index fc6e1a20adae36e573632e38439200360b3eb671..71b64b3194ecf611b172c665f6fbaacbf5f12708 100644 --- a/briar-android/res/layout/forum_discussion_cell.xml +++ b/briar-android/res/layout/list_item_forum_post.xml @@ -72,7 +72,7 @@ android:layout_marginLeft="@dimen/margin_medium" android:layout_weight="1"> - <TextView + <org.thoughtcrime.securesms.components.emoji.EmojiTextView android:id="@+id/text" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -85,59 +85,13 @@ android:textColor="@color/briar_text_primary" tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."/> - <de.hdodenhof.circleimageview.CircleImageView - android:id="@+id/avatar" - android:layout_width="@dimen/forum_avatar_size" - android:layout_height="@dimen/forum_avatar_size" - android:layout_alignLeft="@id/text" - android:layout_below="@id/text" - android:layout_marginRight="@dimen/margin_small" - android:layout_marginTop="@dimen/margin_small" - android:src="@drawable/ic_launcher" - app:civ_border_color="@color/briar_primary" - app:civ_border_width="@dimen/avatar_border_width" - tools:src="@drawable/ic_launcher" - /> - - <TextView + <org.briarproject.android.view.AuthorView android:id="@+id/author" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_toRightOf="@id/avatar" - android:layout_alignBottom="@+id/avatar" - android:layout_alignTop="@+id/avatar" - android:gravity="center" - android:ellipsize="end" - android:maxLines="1" - android:textSize="@dimen/text_size_tiny" - tools:text="John Smith"/> - - <org.briarproject.android.util.TrustIndicatorView - android:id="@+id/trustIndicator" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignBottom="@+id/avatar" - android:layout_alignTop="@+id/avatar" - android:scaleType="center" - android:layout_marginLeft="@dimen/margin_small" - android:layout_marginStart="@dimen/margin_small" - android:layout_toRightOf="@+id/author" - tools:src="@drawable/trust_indicator_verified"/> - - <TextView - android:id="@+id/date" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignBottom="@+id/avatar" - android:layout_alignTop="@+id/avatar" - android:gravity="center" - android:layout_toRightOf="@+id/trustIndicator" - android:layout_marginLeft="@dimen/margin_small" - android:layout_marginStart="@dimen/margin_small" - android:ellipsize="end" - android:maxLines="1" - android:textSize="@dimen/text_size_tiny" - tools:text="09:09"/> + android:layout_alignLeft="@id/text" + android:layout_below="@id/text" + app:persona="commenter"/> <ImageView android:id="@+id/chevron" diff --git a/briar-android/res/layout/list_item_introduction_in.xml b/briar-android/res/layout/list_item_introduction_in.xml index b3a74b6c9b3fb698c00dde72a18e836be9725162..de94d9d788c1a6e020a84cbad177c3ddde4ee573 100644 --- a/briar-android/res/layout/list_item_introduction_in.xml +++ b/briar-android/res/layout/list_item_introduction_in.xml @@ -6,7 +6,7 @@ android:layout_height="wrap_content" android:orientation="vertical"> - <TextView + <org.thoughtcrime.securesms.components.emoji.EmojiTextView android:id="@+id/msgBody" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/briar-android/res/layout/list_item_invitations.xml b/briar-android/res/layout/list_item_invitations.xml index 536c8080d1e09cacf217117ae9f26e811244ebd4..6cfa235b6bf06e6ecd475d5396cb955d102f3a1f 100644 --- a/briar-android/res/layout/list_item_invitations.xml +++ b/briar-android/res/layout/list_item_invitations.xml @@ -9,7 +9,7 @@ android:background="?attr/selectableItemBackground" android:paddingTop="@dimen/listitem_horizontal_margin"> - <org.briarproject.android.util.TextAvatarView + <org.briarproject.android.view.TextAvatarView android:id="@+id/avatarView" android:layout_width="@dimen/listitem_picture_frame_size" android:layout_height="@dimen/listitem_picture_frame_size" diff --git a/briar-android/res/layout/list_item_msg_in.xml b/briar-android/res/layout/list_item_msg_in.xml index 1858c1cc8693786c933093d20379bcc5425d6b9f..3c18ca1a88c15d4e3f2c22a97c109343c68c6f43 100644 --- a/briar-android/res/layout/list_item_msg_in.xml +++ b/briar-android/res/layout/list_item_msg_in.xml @@ -10,7 +10,7 @@ android:background="@drawable/msg_in" android:orientation="vertical"> - <TextView + <org.thoughtcrime.securesms.components.emoji.EmojiTextView android:id="@+id/msgBody" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/briar-android/res/layout/list_item_msg_notice_out.xml b/briar-android/res/layout/list_item_msg_notice_out.xml index 1418e0603251ad97eef8c11e490bf780e3cce331..6efb99c523316d9da267f1e44d9f2a9231a537a0 100644 --- a/briar-android/res/layout/list_item_msg_notice_out.xml +++ b/briar-android/res/layout/list_item_msg_notice_out.xml @@ -6,7 +6,7 @@ android:layout_height="wrap_content" android:orientation="vertical"> - <TextView + <org.thoughtcrime.securesms.components.emoji.EmojiTextView android:id="@+id/msgBody" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/briar-android/res/layout/list_item_msg_out.xml b/briar-android/res/layout/list_item_msg_out.xml index 34de5a9dd240b30144d90f96dce0be5aa806b15e..7b7f4f65cee607313a5bee6079cd26806337d3e3 100644 --- a/briar-android/res/layout/list_item_msg_out.xml +++ b/briar-android/res/layout/list_item_msg_out.xml @@ -15,7 +15,7 @@ android:layout_marginRight="@dimen/message_bubble_margin_tail" android:background="@drawable/msg_out"> - <TextView + <org.thoughtcrime.securesms.components.emoji.EmojiTextView android:id="@+id/msgBody" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/briar-android/res/layout/list_item_shareable_invitation_in.xml b/briar-android/res/layout/list_item_shareable_invitation_in.xml index 5d707e3612fdd95c74ddfcb3c0189a466596e57a..f70ac34a0e89570d675c862a7f72c03c2fa83b21 100644 --- a/briar-android/res/layout/list_item_shareable_invitation_in.xml +++ b/briar-android/res/layout/list_item_shareable_invitation_in.xml @@ -6,7 +6,7 @@ android:layout_height="wrap_content" android:orientation="vertical"> - <TextView + <org.thoughtcrime.securesms.components.emoji.EmojiTextView android:id="@+id/msgBody" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/briar-android/res/layout/text_input_field.xml b/briar-android/res/layout/text_input_field.xml deleted file mode 100644 index 8b60f7b449e150f74565d0a7a7df7048f94347b8..0000000000000000000000000000000000000000 --- a/briar-android/res/layout/text_input_field.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout - android:id="@+id/text_input_container" - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@color/button_bar_background" - android:elevation="@dimen/margin_tiny" - android:gravity="bottom" - android:orientation="horizontal" - android:paddingLeft="@dimen/margin_large" - android:paddingStart="@dimen/margin_large"> - - <EditText - android:id="@+id/input_text" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/margin_small" - android:layout_weight="1" - android:inputType="textMultiLine|textCapSentences" - android:maxLines="5"/> - - <ImageView - android:id="@+id/btn_send" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_margin="@dimen/margin_medium" - android:background="?attr/selectableItemBackground" - android:clickable="true" - android:contentDescription="@string/send" - android:onClick="sendMessage" - android:src="@drawable/social_send_now_white" - android:tint="@color/briar_primary" - /> - -</LinearLayout> diff --git a/briar-android/res/layout/text_input_view.xml b/briar-android/res/layout/text_input_view.xml new file mode 100644 index 0000000000000000000000000000000000000000..6b1aeba51528e188c326dbc208ba59b8868e9aef --- /dev/null +++ b/briar-android/res/layout/text_input_view.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:showIn="@layout/activity_conversation"> + + <View + style="@style/Divider.Horizontal" + android:layout_alignParentTop="true"/> + + <org.thoughtcrime.securesms.components.emoji.EmojiToggle + android:id="@+id/emoji_toggle" + android:layout_width="42dp" + android:layout_height="42dp" + android:layout_above="@+id/emoji_drawer" + android:layout_alignBottom="@+id/input_text" + android:layout_alignParentLeft="true" + android:background="?attr/selectableItemBackground" + android:padding="@dimen/margin_small" + android:scaleType="center"/> + + <org.thoughtcrime.securesms.components.emoji.EmojiEditText + android:id="@+id/input_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toLeftOf="@+id/btn_send" + android:layout_toRightOf="@+id/emoji_toggle" + android:inputType="textMultiLine|textCapSentences" + android:maxLines="5" + android:minHeight="42dp"/> + + <ImageButton + android:id="@+id/btn_send" + android:layout_width="wrap_content" + android:layout_height="42dp" + android:layout_above="@+id/emoji_drawer" + android:layout_alignBottom="@+id/input_text" + android:layout_alignParentRight="true" + android:background="?attr/selectableItemBackground" + android:clickable="true" + android:contentDescription="@string/send" + android:enabled="false" + android:padding="@dimen/margin_small" + android:src="@drawable/social_send_now_white" + android:tint="@color/briar_primary"/> + + <org.thoughtcrime.securesms.components.emoji.EmojiDrawer + android:id="@+id/emoji_drawer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/input_text" + android:visibility="gone"/> + +</merge> diff --git a/briar-android/res/values/color.xml b/briar-android/res/values/color.xml index 0ac3140eeef7b00dadaa8cddaf3302d4d34c22eb..daf3f8b3b2b07393fc0fb0993dd6931ac21b25ee 100644 --- a/briar-android/res/values/color.xml +++ b/briar-android/res/values/color.xml @@ -27,6 +27,9 @@ <color name="briar_text_tertiary_inverse">#80ffffff</color> <color name="briar_button_positive">#06b9ff</color> <color name="briar_button_negative">#ff0000</color> + <color name="emoji_text_color">#ff000000</color> + + <color name="emoji_pager_background">@color/window_background</color> <!-- this is needed as preference_category_material layout uses this color as the text color --> <color name="preference_fallback_accent_color">@color/briar_accent</color> diff --git a/briar-android/res/values/dimens.xml b/briar-android/res/values/dimens.xml index c8e7eca7d0dfa6c3a8b2a51123547edf60fb748c..f09530cc4b1300b0e72b533f7e3177196ed7a571 100644 --- a/briar-android/res/values/dimens.xml +++ b/briar-android/res/values/dimens.xml @@ -46,4 +46,17 @@ <dimen name="blogs_avatar_icon_size">15dp</dimen> <dimen name="blogs_avatar_comment_size">20dp</dimen> + <!-- Emoji --> + <dimen name="conversation_item_body_text_size">16sp</dimen> + <dimen name="emoji_drawer_size">32sp</dimen> + <dimen name="emoji_drawer_indicator_height">2dp</dimen> + <dimen name="emoji_drawer_item_padding">5dp</dimen> + <dimen name="emoji_drawer_left_right_padding">2dp</dimen> + + <!-- Keyboard Sizes --> + <dimen name="min_keyboard_size">50dp</dimen> + <dimen name="default_custom_keyboard_size">220dp</dimen> + <dimen name="min_custom_keyboard_size">110dp</dimen> + <dimen name="min_custom_keyboard_top_margin">170dp</dimen> + </resources> diff --git a/briar-android/res/values/emoji.xml b/briar-android/res/values/emoji.xml new file mode 100644 index 0000000000000000000000000000000000000000..ab8e62d011f28e504cc41b3f8c78d371226021d0 --- /dev/null +++ b/briar-android/res/values/emoji.xml @@ -0,0 +1,1352 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <array + name="emoji_symbols" + format="string"> + <item>1f3c1</item> + <item>1f3f3</item> + <item>1f3f4</item> + <item>1f6a9</item> + <item>1f3e7</item> + <item>1f6ae</item> + <item>1f6b0</item> + <item>267f</item> + <item>1f6b9</item> + <item>1f6ba</item> + <item>1f6bb</item> + <item>1f6bc</item> + <item>1f6be</item> + <item>1f6c2</item> + <item>1f6c3</item> + <item>1f6c4</item> + <item>1f6c5</item> + <item>26a0</item> + <item>1f6b8</item> + <item>26d4</item> + <item>1f6ab</item> + <item>1f6b3</item> + <item>1f6ad</item> + <item>1f6af</item> + <item>1f6b1</item> + <item>1f6b7</item> + <item>2622</item> + <item>2623</item> + <item>2b06</item> + <item>2197</item> + <item>27a1</item> + <item>2198</item> + <item>2b07</item> + <item>2199</item> + <item>2b05</item> + <item>2196</item> + <item>2195</item> + <item>2194</item> + <item>21a9</item> + <item>21aa</item> + <item>2934</item> + <item>2935</item> + <item>1f503</item> + <item>1f504</item> + <item>1f519</item> + <item>1f51a</item> + <item>1f51b</item> + <item>1f51c</item> + <item>1f51d</item> + <item>1f6d0</item> + <item>269b</item> + <item>1f549</item> + <item>2721</item> + <item>2638</item> + <item>262f</item> + <item>271d</item> + <item>2626</item> + <item>262a</item> + <item>262e</item> + <item>1f54e</item> + <item>1f52f</item> + <item>267b</item> + <item>1f4db</item> + <item>269c</item> + <item>1f530</item> + <item>1f531</item> + <item>2b55</item> + <item>2705</item> + <item>2611</item> + <item>2714</item> + <item>2716</item> + <item>274c</item> + <item>274e</item> + <item>2795</item> + <item>2796</item> + <item>2797</item> + <item>27b0</item> + <item>27bf</item> + <item>303d</item> + <item>2733</item> + <item>2734</item> + <item>2747</item> + <item>1f4b1</item> + <item>1f4b2</item> + <item>203c</item> + <item>2049</item> + <item>2753</item> + <item>2754</item> + <item>2755</item> + <item>2757</item> + <item>3030</item> + <item>a9</item> + <item>ae</item> + <item>2122</item> + <item>2648</item> + <item>2649</item> + <item>264a</item> + <item>264b</item> + <item>264c</item> + <item>264d</item> + <item>264e</item> + <item>264f</item> + <item>2650</item> + <item>2651</item> + <item>2652</item> + <item>2653</item> + <item>26ce</item> + <item>1f500</item> + <item>1f501</item> + <item>1f502</item> + <item>25b6</item> + <item>23e9</item> + <item>23ed</item> + <item>23ef</item> + <item>25c0</item> + <item>23ea</item> + <item>23ee</item> + <item>1f53c</item> + <item>23eb</item> + <item>1f53d</item> + <item>23ec</item> + <item>23f8</item> + <item>23f9</item> + <item>23fa</item> + <item>23cf</item> + <item>1f3a6</item> + <item>1f505</item> + <item>1f506</item> + <item>1f4f6</item> + <item>1f4f5</item> + <item>1f4f3</item> + <item>1f4f4</item> + <item>23,20e3</item> + <item>2a,20e3</item> + <item>30,20e3</item> + <item>31,20e3</item> + <item>32,20e3</item> + <item>33,20e3</item> + <item>34,20e3</item> + <item>35,20e3</item> + <item>36,20e3</item> + <item>37,20e3</item> + <item>38,20e3</item> + <item>39,20e3</item> + <item>1f51f</item> + <item>1f4af</item> + <item>1f51e</item> + <item>1f520</item> + <item>1f521</item> + <item>1f522</item> + <item>1f523</item> + <item>1f524</item> + <item>1f170</item> + <item>1f18e</item> + <item>1f171</item> + <item>1f191</item> + <item>1f192</item> + <item>1f193</item> + <item>2139</item> + <item>1f194</item> + <item>24c2</item> + <item>1f195</item> + <item>1f196</item> + <item>1f17e</item> + <item>1f197</item> + <item>1f17f</item> + <item>1f198</item> + <item>1f199</item> + <item>1f19a</item> + <item>1f201</item> + <item>1f202</item> + <item>1f237</item> + <item>1f236</item> + <item>1f22f</item> + <item>1f250</item> + <item>1f239</item> + <item>1f21a</item> + <item>1f232</item> + <item>1f251</item> + <item>1f238</item> + <item>1f234</item> + <item>1f233</item> + <item>3297</item> + <item>3299</item> + <item>1f23a</item> + <item>1f235</item> + <item>25aa</item> + <item>25ab</item> + <item>25fb</item> + <item>25fc</item> + <item>25fd</item> + <item>25fe</item> + <item>2b1b</item> + <item>2b1c</item> + <item>1f536</item> + <item>1f537</item> + <item>1f538</item> + <item>1f539</item> + <item>1f53a</item> + <item>1f53b</item> + <item>1f4a0</item> + <item>1f518</item> + <item>1f532</item> + <item>1f533</item> + <item>26aa</item> + <item>26ab</item> + <item>1f534</item> + <item>1f535</item> + </array> + <array + name="emoji_animals_nature" + format="string"> + <item>1f435</item> + <item>1f412</item> + <item>1f436</item> + <item>1f415</item> + <item>1f429</item> + <item>1f43a</item> + <item>1f431</item> + <item>1f408</item> + <item>1f981</item> + <item>1f42f</item> + <item>1f405</item> + <item>1f406</item> + <item>1f434</item> + <item>1f40e</item> + <item>1f984</item> + <item>1f42e</item> + <item>1f402</item> + <item>1f403</item> + <item>1f404</item> + <item>1f437</item> + <item>1f416</item> + <item>1f417</item> + <item>1f43d</item> + <item>1f40f</item> + <item>1f411</item> + <item>1f410</item> + <item>1f42a</item> + <item>1f42b</item> + <item>1f418</item> + <item>1f42d</item> + <item>1f401</item> + <item>1f400</item> + <item>1f439</item> + <item>1f430</item> + <item>1f407</item> + <item>1f43f</item> + <item>1f43b</item> + <item>1f428</item> + <item>1f43c</item> + <item>1f43e</item> + <item>1f983</item> + <item>1f414</item> + <item>1f413</item> + <item>1f423</item> + <item>1f424</item> + <item>1f425</item> + <item>1f426</item> + <item>1f427</item> + <item>1f54a</item> + <item>1f438</item> + <item>1f40a</item> + <item>1f422</item> + <item>1f40d</item> + <item>1f432</item> + <item>1f409</item> + <item>1f433</item> + <item>1f40b</item> + <item>1f42c</item> + <item>1f41f</item> + <item>1f420</item> + <item>1f421</item> + <item>1f419</item> + <item>1f41a</item> + <item>1f980</item> + <item>1f40c</item> + <item>1f41b</item> + <item>1f41c</item> + <item>1f41d</item> + <item>1f41e</item> + <item>1f577</item> + <item>1f578</item> + <item>1f982</item> + <item>1f490</item> + <item>1f338</item> + <item>1f4ae</item> + <item>1f3f5</item> + <item>1f339</item> + <item>1f33a</item> + <item>1f33b</item> + <item>1f33c</item> + <item>1f337</item> + <item>2618</item> + <item>1f331</item> + <item>1f332</item> + <item>1f333</item> + <item>1f334</item> + <item>1f335</item> + <item>1f33e</item> + <item>1f33f</item> + <item>1f340</item> + <item>1f341</item> + <item>1f342</item> + <item>1f343</item> + </array> + <array + name="emoji_smiley_people" + format="string"> + <item>1f600</item> + <item>1f601</item> + <item>1f602</item> + <item>1f603</item> + <item>1f604</item> + <item>1f605</item> + <item>1f606</item> + <item>1f609</item> + <item>1f60a</item> + <item>1f60b</item> + <item>1f60e</item> + <item>1f60d</item> + <item>1f618</item> + <item>1f617</item> + <item>1f619</item> + <item>1f61a</item> + <item>263a</item> + <item>1f642</item> + <item>1f917</item> + <item>1f607</item> + <item>1f914</item> + <item>1f610</item> + <item>1f611</item> + <item>1f636</item> + <item>1f644</item> + <item>1f60f</item> + <item>1f623</item> + <item>1f625</item> + <item>1f62e</item> + <item>1f910</item> + <item>1f62f</item> + <item>1f62a</item> + <item>1f62b</item> + <item>1f634</item> + <item>1f60c</item> + <item>1f913</item> + <item>1f61b</item> + <item>1f61c</item> + <item>1f61d</item> + <item>2639</item> + <item>1f641</item> + <item>1f612</item> + <item>1f613</item> + <item>1f614</item> + <item>1f615</item> + <item>1f616</item> + <item>1f643</item> + <item>1f637</item> + <item>1f912</item> + <item>1f915</item> + <item>1f911</item> + <item>1f632</item> + <item>1f61e</item> + <item>1f61f</item> + <item>1f624</item> + <item>1f622</item> + <item>1f62d</item> + <item>1f626</item> + <item>1f627</item> + <item>1f628</item> + <item>1f629</item> + <item>1f62c</item> + <item>1f630</item> + <item>1f631</item> + <item>1f633</item> + <item>1f635</item> + <item>1f621</item> + <item>1f620</item> + <item>1f608</item> + <item>1f47f</item> + <item>1f479</item> + <item>1f47a</item> + <item>1f480</item> + <item>2620</item> + <item>1f47b</item> + <item>1f47d</item> + <item>1f47e</item> + <item>1f916</item> + <item>1f4a9</item> + <item>1f63a</item> + <item>1f638</item> + <item>1f639</item> + <item>1f63b</item> + <item>1f63c</item> + <item>1f63d</item> + <item>1f640</item> + <item>1f63f</item> + <item>1f63e</item> + <item>1f648</item> + <item>1f649</item> + <item>1f64a</item> + <item>1f466</item> + <item>1f467</item> + <item>1f468</item> + <item>1f469</item> + <item>1f474</item> + <item>1f475</item> + <item>1f476</item> + <item>1f471</item> + <item>1f46e</item> + <item>1f472</item> + <item>1f473</item> + <item>1f477</item> + <item>26d1</item> + <item>1f478</item> + <item>1f482</item> + <item>1f575</item> + <item>1f385</item> + <item>1f47c</item> + <item>1f46f</item> + <item>1f486</item> + <item>1f487</item> + <item>1f470</item> + <item>1f64d</item> + <item>1f64e</item> + <item>1f645</item> + <item>1f646</item> + <item>1f481</item> + <item>1f64b</item> + <item>1f647</item> + <item>1f64c</item> + <item>1f64f</item> + <item>1f5e3</item> + <item>1f464</item> + <item>1f465</item> + <item>1f6b6</item> + <item>1f3c3</item> + <item>1f483</item> + <item>1f574</item> + <item>1f46b</item> + <item>1f46c</item> + <item>1f46d</item> + <item>1f48f</item> + <item>1f468,200d,2764,fe0f,200d,1f48b,200d,1f468</item> + <item>1f469,200d,2764,fe0f,200d,1f48b,200d,1f469</item> + <item>1f491</item> + <item>1f468,200d,2764,fe0f,200d,1f468</item> + <item>1f469,200d,2764,fe0f,200d,1f469</item> + <item>1f46a</item> + <item>1f468,200d,1f468,200d,1f466</item> + <item>1f468,200d,1f468,200d,1f466,200d,1f466</item> + <item>1f468,200d,1f468,200d,1f467</item> + <item>1f468,200d,1f468,200d,1f467,200d,1f466</item> + <item>1f468,200d,1f468,200d,1f467,200d,1f467</item> + <item>1f468,200d,1f469,200d,1f466</item> + <item>1f468,200d,1f469,200d,1f466,200d,1f466</item> + <item>1f468,200d,1f469,200d,1f467</item> + <item>1f468,200d,1f469,200d,1f467,200d,1f466</item> + <item>1f468,200d,1f469,200d,1f467,200d,1f467</item> + <item>1f469,200d,1f469,200d,1f466</item> + <item>1f469,200d,1f469,200d,1f466,200d,1f466</item> + <item>1f469,200d,1f469,200d,1f467</item> + <item>1f469,200d,1f469,200d,1f467,200d,1f466</item> + <item>1f469,200d,1f469,200d,1f467,200d,1f467</item> + <item>1f3fb</item> + <item>1f3fc</item> + <item>1f3fd</item> + <item>1f3fe</item> + <item>1f3ff</item> + <item>1f4aa</item> + <item>1f448</item> + <item>1f449</item> + <item>261d</item> + <item>1f446</item> + <item>1f595</item> + <item>1f447</item> + <item>270c</item> + <item>1f596</item> + <item>1f918</item> + <item>1f590</item> + <item>270a</item> + <item>270b</item> + <item>1f44a</item> + <item>1f44c</item> + <item>1f44d</item> + <item>1f44e</item> + <item>1f44b</item> + <item>1f44f</item> + <item>1f450</item> + <item>270d</item> + <item>1f485</item> + <item>1f442</item> + <item>1f443</item> + <item>1f463</item> + <item>1f440</item> + <item>1f441</item> + <item>1f445</item> + <item>1f444</item> + <item>1f48b</item> + <item>1f498</item> + <item>2764</item> + <item>1f493</item> + <item>1f494</item> + <item>1f495</item> + <item>1f496</item> + <item>1f497</item> + <item>1f499</item> + <item>1f49a</item> + <item>1f49b</item> + <item>1f49c</item> + <item>1f49d</item> + <item>1f49e</item> + <item>1f49f</item> + <item>2763</item> + <item>1f48c</item> + <item>1f4a4</item> + <item>1f4a2</item> + <item>1f4a3</item> + <item>1f4a5</item> + <item>1f4a6</item> + <item>1f4a8</item> + <item>1f4ab</item> + <item>1f4ac</item> + <item>1f5e8</item> + <item>1f5ef</item> + <item>1f4ad</item> + <item>1f441,200d,1f5e8</item> + <item>1f573</item> + <item>1f453</item> + <item>1f576</item> + <item>1f454</item> + <item>1f455</item> + <item>1f456</item> + <item>1f457</item> + <item>1f458</item> + <item>1f459</item> + <item>1f45a</item> + <item>1f45b</item> + <item>1f45c</item> + <item>1f45d</item> + <item>1f6cd</item> + <item>1f392</item> + <item>1f45e</item> + <item>1f45f</item> + <item>1f460</item> + <item>1f461</item> + <item>1f462</item> + <item>1f451</item> + <item>1f452</item> + <item>1f3a9</item> + <item>1f393</item> + <item>1f4ff</item> + <item>1f484</item> + <item>1f48d</item> + <item>1f48e</item> + </array> + <array + name="emoji_food_drink" + format="string"> + <item>1f347</item> + <item>1f348</item> + <item>1f349</item> + <item>1f34a</item> + <item>1f34b</item> + <item>1f34c</item> + <item>1f34d</item> + <item>1f34e</item> + <item>1f34f</item> + <item>1f350</item> + <item>1f351</item> + <item>1f352</item> + <item>1f353</item> + <item>1f345</item> + <item>1f346</item> + <item>1f33d</item> + <item>1f336</item> + <item>1f344</item> + <item>1f330</item> + <item>1f35e</item> + <item>1f9c0</item> + <item>1f356</item> + <item>1f357</item> + <item>1f354</item> + <item>1f35f</item> + <item>1f355</item> + <item>1f32d</item> + <item>1f32e</item> + <item>1f32f</item> + <item>1f37f</item> + <item>1f372</item> + <item>1f371</item> + <item>1f358</item> + <item>1f359</item> + <item>1f35a</item> + <item>1f35b</item> + <item>1f35c</item> + <item>1f35d</item> + <item>1f360</item> + <item>1f362</item> + <item>1f363</item> + <item>1f364</item> + <item>1f365</item> + <item>1f361</item> + <item>1f366</item> + <item>1f367</item> + <item>1f368</item> + <item>1f369</item> + <item>1f36a</item> + <item>1f382</item> + <item>1f370</item> + <item>1f36b</item> + <item>1f36c</item> + <item>1f36d</item> + <item>1f36e</item> + <item>1f36f</item> + <item>1f37c</item> + <item>2615</item> + <item>1f375</item> + <item>1f376</item> + <item>1f37e</item> + <item>1f377</item> + <item>1f378</item> + <item>1f379</item> + <item>1f37a</item> + <item>1f37b</item> + <item>1f37d</item> + <item>1f374</item> + <item>1f373</item> + <item>1f3fa</item> + </array> + <array + name="emoji_objects" + format="string"> + <item>1f507</item> + <item>1f508</item> + <item>1f509</item> + <item>1f50a</item> + <item>1f4e2</item> + <item>1f4e3</item> + <item>1f4ef</item> + <item>1f514</item> + <item>1f515</item> + <item>1f3bc</item> + <item>1f3b5</item> + <item>1f3b6</item> + <item>1f399</item> + <item>1f39a</item> + <item>1f39b</item> + <item>1f3a4</item> + <item>1f3a7</item> + <item>1f3b7</item> + <item>1f3b8</item> + <item>1f3b9</item> + <item>1f3ba</item> + <item>1f3bb</item> + <item>1f4fb</item> + <item>1f4f1</item> + <item>1f4f2</item> + <item>260e</item> + <item>1f4de</item> + <item>1f4df</item> + <item>1f4e0</item> + <item>1f50b</item> + <item>1f50c</item> + <item>1f4bb</item> + <item>1f5a5</item> + <item>1f5a8</item> + <item>2328</item> + <item>1f5b1</item> + <item>1f5b2</item> + <item>1f4bd</item> + <item>1f4be</item> + <item>1f4bf</item> + <item>1f4c0</item> + <item>1f3a5</item> + <item>1f3ac</item> + <item>1f4fd</item> + <item>1f4fa</item> + <item>1f4f7</item> + <item>1f4f8</item> + <item>1f4f9</item> + <item>1f4fc</item> + <item>1f50d</item> + <item>1f50e</item> + <item>1f52c</item> + <item>1f52d</item> + <item>1f4e1</item> + <item>1f56f</item> + <item>1f4a1</item> + <item>1f526</item> + <item>1f3ee</item> + <item>1f4d4</item> + <item>1f4d5</item> + <item>1f4d6</item> + <item>1f4d7</item> + <item>1f4d8</item> + <item>1f4d9</item> + <item>1f4da</item> + <item>1f4d3</item> + <item>1f4d2</item> + <item>1f4c3</item> + <item>1f4dc</item> + <item>1f4c4</item> + <item>1f4f0</item> + <item>1f5de</item> + <item>1f4d1</item> + <item>1f516</item> + <item>1f4b0</item> + <item>1f4b4</item> + <item>1f4b5</item> + <item>1f4b6</item> + <item>1f4b7</item> + <item>1f4b8</item> + <item>1f4b3</item> + <item>1f4b9</item> + <item>2709</item> + <item>1f4e7</item> + <item>1f4e8</item> + <item>1f4e9</item> + <item>1f4e4</item> + <item>1f4e5</item> + <item>1f4e6</item> + <item>1f4eb</item> + <item>1f4ea</item> + <item>1f4ec</item> + <item>1f4ed</item> + <item>1f4ee</item> + <item>1f5f3</item> + <item>270f</item> + <item>2712</item> + <item>1f58b</item> + <item>1f58a</item> + <item>1f58c</item> + <item>1f58d</item> + <item>1f4dd</item> + <item>1f4bc</item> + <item>1f4c1</item> + <item>1f4c2</item> + <item>1f5c2</item> + <item>1f4c5</item> + <item>1f4c6</item> + <item>1f5d2</item> + <item>1f5d3</item> + <item>1f4c7</item> + <item>1f4c8</item> + <item>1f4c9</item> + <item>1f4ca</item> + <item>1f4cb</item> + <item>1f4cc</item> + <item>1f4cd</item> + <item>1f4ce</item> + <item>1f587</item> + <item>1f4cf</item> + <item>1f4d0</item> + <item>2702</item> + <item>1f5c3</item> + <item>1f5c4</item> + <item>1f5d1</item> + <item>1f512</item> + <item>1f513</item> + <item>1f50f</item> + <item>1f510</item> + <item>1f511</item> + <item>1f5dd</item> + <item>1f528</item> + <item>26cf</item> + <item>2692</item> + <item>1f6e0</item> + <item>1f527</item> + <item>1f529</item> + <item>2699</item> + <item>1f5dc</item> + <item>2697</item> + <item>2696</item> + <item>1f517</item> + <item>26d3</item> + <item>1f489</item> + <item>1f48a</item> + <item>1f5e1</item> + <item>1f52a</item> + <item>2694</item> + <item>1f52b</item> + <item>1f6e1</item> + <item>1f3f9</item> + <item>1f6ac</item> + <item>26b0</item> + <item>26b1</item> + <item>1f5ff</item> + <item>1f6e2</item> + <item>1f52e</item> + </array> + <array + name="emoji_activity" + format="string"> + <item>1f383</item> + <item>1f384</item> + <item>1f386</item> + <item>1f387</item> + <item>2728</item> + <item>1f388</item> + <item>1f389</item> + <item>1f38a</item> + <item>1f38b</item> + <item>1f38c</item> + <item>1f38d</item> + <item>1f38e</item> + <item>1f38f</item> + <item>1f390</item> + <item>1f391</item> + <item>1f380</item> + <item>1f381</item> + <item>1f396</item> + <item>1f397</item> + <item>1f39e</item> + <item>1f39f</item> + <item>1f3ab</item> + <item>1f3f7</item> + <item>26bd</item> + <item>26be</item> + <item>1f3c0</item> + <item>1f3c8</item> + <item>1f3c9</item> + <item>1f3be</item> + <item>1f3b1</item> + <item>1f3b3</item> + <item>26f3</item> + <item>1f3cc</item> + <item>26f8</item> + <item>1f3a3</item> + <item>1f3bd</item> + <item>1f3bf</item> + <item>26f7</item> + <item>1f3c2</item> + <item>1f3c4</item> + <item>1f3c7</item> + <item>1f3ca</item> + <item>26f9</item> + <item>1f3cb</item> + <item>1f6b4</item> + <item>1f6b5</item> + <item>1f3ce</item> + <item>1f3cd</item> + <item>1f3c5</item> + <item>1f3c6</item> + <item>1f3cf</item> + <item>1f3d0</item> + <item>1f3d1</item> + <item>1f3d2</item> + <item>1f3d3</item> + <item>1f3f8</item> + <item>1f3af</item> + <item>1f3ae</item> + <item>1f579</item> + <item>1f3b2</item> + <item>2660</item> + <item>2665</item> + <item>2666</item> + <item>2663</item> + <item>1f0cf</item> + <item>1f004</item> + <item>1f3b4</item> + </array> + <array + name="emoji_travel_places" + format="string"> + <item>1f30d</item> + <item>1f30e</item> + <item>1f30f</item> + <item>1f310</item> + <item>1f5fa</item> + <item>1f3d4</item> + <item>26f0</item> + <item>1f30b</item> + <item>1f5fb</item> + <item>1f3d5</item> + <item>1f3d6</item> + <item>1f3dc</item> + <item>1f3dd</item> + <item>1f3de</item> + <item>1f3df</item> + <item>1f3db</item> + <item>1f3d7</item> + <item>1f3d8</item> + <item>1f3d9</item> + <item>1f3da</item> + <item>1f3e0</item> + <item>1f3e1</item> + <item>26ea</item> + <item>1f54b</item> + <item>1f54c</item> + <item>1f54d</item> + <item>26e9</item> + <item>1f3e2</item> + <item>1f3e3</item> + <item>1f3e4</item> + <item>1f3e5</item> + <item>1f3e6</item> + <item>1f3e8</item> + <item>1f3e9</item> + <item>1f3ea</item> + <item>1f3eb</item> + <item>1f3ec</item> + <item>1f3ed</item> + <item>1f3ef</item> + <item>1f3f0</item> + <item>1f492</item> + <item>1f5fc</item> + <item>1f5fd</item> + <item>1f5fe</item> + <item>26f2</item> + <item>26fa</item> + <item>1f301</item> + <item>1f303</item> + <item>1f304</item> + <item>1f305</item> + <item>1f306</item> + <item>1f307</item> + <item>1f309</item> + <item>2668</item> + <item>1f30c</item> + <item>1f3a0</item> + <item>1f3a1</item> + <item>1f3a2</item> + <item>1f488</item> + <item>1f3aa</item> + <item>1f3ad</item> + <item>1f5bc</item> + <item>1f3a8</item> + <item>1f3b0</item> + <item>1f682</item> + <item>1f683</item> + <item>1f684</item> + <item>1f685</item> + <item>1f686</item> + <item>1f687</item> + <item>1f688</item> + <item>1f689</item> + <item>1f68a</item> + <item>1f69d</item> + <item>1f69e</item> + <item>1f68b</item> + <item>1f68c</item> + <item>1f68d</item> + <item>1f68e</item> + <item>1f68f</item> + <item>1f690</item> + <item>1f691</item> + <item>1f692</item> + <item>1f693</item> + <item>1f694</item> + <item>1f695</item> + <item>1f696</item> + <item>1f697</item> + <item>1f698</item> + <item>1f699</item> + <item>1f69a</item> + <item>1f69b</item> + <item>1f69c</item> + <item>1f6b2</item> + <item>26fd</item> + <item>1f6e3</item> + <item>1f6e4</item> + <item>1f6a8</item> + <item>1f6a5</item> + <item>1f6a6</item> + <item>1f6a7</item> + <item>2693</item> + <item>26f5</item> + <item>1f6a3</item> + <item>1f6a4</item> + <item>1f6f3</item> + <item>26f4</item> + <item>1f6e5</item> + <item>1f6a2</item> + <item>2708</item> + <item>1f6e9</item> + <item>1f6eb</item> + <item>1f6ec</item> + <item>1f4ba</item> + <item>1f681</item> + <item>1f69f</item> + <item>1f6a0</item> + <item>1f6a1</item> + <item>1f680</item> + <item>1f6f0</item> + <item>1f6ce</item> + <item>1f6aa</item> + <item>1f6cc</item> + <item>1f6cf</item> + <item>1f6cb</item> + <item>1f6bd</item> + <item>1f6bf</item> + <item>1f6c0</item> + <item>1f6c1</item> + <item>231b</item> + <item>23f3</item> + <item>231a</item> + <item>23f0</item> + <item>23f1</item> + <item>23f2</item> + <item>1f570</item> + <item>1f55b</item> + <item>1f567</item> + <item>1f550</item> + <item>1f55c</item> + <item>1f551</item> + <item>1f55d</item> + <item>1f552</item> + <item>1f55e</item> + <item>1f553</item> + <item>1f55f</item> + <item>1f554</item> + <item>1f560</item> + <item>1f555</item> + <item>1f561</item> + <item>1f556</item> + <item>1f562</item> + <item>1f557</item> + <item>1f563</item> + <item>1f558</item> + <item>1f564</item> + <item>1f559</item> + <item>1f565</item> + <item>1f55a</item> + <item>1f566</item> + <item>1f311</item> + <item>1f312</item> + <item>1f313</item> + <item>1f314</item> + <item>1f315</item> + <item>1f316</item> + <item>1f317</item> + <item>1f318</item> + <item>1f319</item> + <item>1f31a</item> + <item>1f31b</item> + <item>1f31c</item> + <item>1f321</item> + <item>2600</item> + <item>1f31d</item> + <item>1f31e</item> + <item>2b50</item> + <item>1f31f</item> + <item>1f320</item> + <item>2601</item> + <item>26c5</item> + <item>26c8</item> + <item>1f324</item> + <item>1f325</item> + <item>1f326</item> + <item>1f327</item> + <item>1f328</item> + <item>1f329</item> + <item>1f32a</item> + <item>1f32b</item> + <item>1f32c</item> + <item>1f300</item> + <item>1f308</item> + <item>1f302</item> + <item>2602</item> + <item>2614</item> + <item>26f1</item> + <item>26a1</item> + <item>2744</item> + <item>2603</item> + <item>26c4</item> + <item>2604</item> + <item>1f525</item> + <item>1f4a7</item> + <item>1f30a</item> + </array> + <array + name="emoji_flags" + format="string"> + <item>1f1e6,1f1e8</item> + <item>1f1e6,1f1e9</item> + <item>1f1e6,1f1ea</item> + <item>1f1e6,1f1eb</item> + <item>1f1e6,1f1ec</item> + <item>1f1e6,1f1ee</item> + <item>1f1e6,1f1f1</item> + <item>1f1e6,1f1f2</item> + <item>1f1e6,1f1f4</item> + <item>1f1e6,1f1f6</item> + <item>1f1e6,1f1f7</item> + <item>1f1e6,1f1f8</item> + <item>1f1e6,1f1f9</item> + <item>1f1e6,1f1fa</item> + <item>1f1e6,1f1fc</item> + <item>1f1e6,1f1fd</item> + <item>1f1e6,1f1ff</item> + <item>1f1e7,1f1e6</item> + <item>1f1e7,1f1e7</item> + <item>1f1e7,1f1e9</item> + <item>1f1e7,1f1ea</item> + <item>1f1e7,1f1eb</item> + <item>1f1e7,1f1ec</item> + <item>1f1e7,1f1ed</item> + <item>1f1e7,1f1ee</item> + <item>1f1e7,1f1ef</item> + <item>1f1e7,1f1f1</item> + <item>1f1e7,1f1f2</item> + <item>1f1e7,1f1f3</item> + <item>1f1e7,1f1f4</item> + <item>1f1e7,1f1f6</item> + <item>1f1e7,1f1f7</item> + <item>1f1e7,1f1f8</item> + <item>1f1e7,1f1f9</item> + <item>1f1e7,1f1fb</item> + <item>1f1e7,1f1fc</item> + <item>1f1e7,1f1fe</item> + <item>1f1e7,1f1ff</item> + <item>1f1e8,1f1e6</item> + <item>1f1e8,1f1e8</item> + <item>1f1e8,1f1e9</item> + <item>1f1e8,1f1eb</item> + <item>1f1e8,1f1ec</item> + <item>1f1e8,1f1ed</item> + <item>1f1e8,1f1ee</item> + <item>1f1e8,1f1f0</item> + <item>1f1e8,1f1f1</item> + <item>1f1e8,1f1f2</item> + <item>1f1e8,1f1f3</item> + <item>1f1e8,1f1f4</item> + <item>1f1e8,1f1f5</item> + <item>1f1e8,1f1f7</item> + <item>1f1e8,1f1fa</item> + <item>1f1e8,1f1fb</item> + <item>1f1e8,1f1fc</item> + <item>1f1e8,1f1fd</item> + <item>1f1e8,1f1fe</item> + <item>1f1e8,1f1ff</item> + <item>1f1e9,1f1ea</item> + <item>1f1e9,1f1ec</item> + <item>1f1e9,1f1ef</item> + <item>1f1e9,1f1f0</item> + <item>1f1e9,1f1f2</item> + <item>1f1e9,1f1f4</item> + <item>1f1e9,1f1ff</item> + <item>1f1ea,1f1e6</item> + <item>1f1ea,1f1e8</item> + <item>1f1ea,1f1ea</item> + <item>1f1ea,1f1ec</item> + <item>1f1ea,1f1ed</item> + <item>1f1ea,1f1f7</item> + <item>1f1ea,1f1f8</item> + <item>1f1ea,1f1f9</item> + <item>1f1ea,1f1fa</item> + <item>1f1eb,1f1ee</item> + <item>1f1eb,1f1ef</item> + <item>1f1eb,1f1f0</item> + <item>1f1eb,1f1f2</item> + <item>1f1eb,1f1f4</item> + <item>1f1eb,1f1f7</item> + <item>1f1ec,1f1e6</item> + <item>1f1ec,1f1e7</item> + <item>1f1ec,1f1e9</item> + <item>1f1ec,1f1ea</item> + <item>1f1ec,1f1eb</item> + <item>1f1ec,1f1ec</item> + <item>1f1ec,1f1ed</item> + <item>1f1ec,1f1ee</item> + <item>1f1ec,1f1f1</item> + <item>1f1ec,1f1f2</item> + <item>1f1ec,1f1f3</item> + <item>1f1ec,1f1f5</item> + <item>1f1ec,1f1f6</item> + <item>1f1ec,1f1f7</item> + <item>1f1ec,1f1f8</item> + <item>1f1ec,1f1f9</item> + <item>1f1ec,1f1fa</item> + <item>1f1ec,1f1fc</item> + <item>1f1ec,1f1fe</item> + <item>1f1ed,1f1f0</item> + <item>1f1ed,1f1f2</item> + <item>1f1ed,1f1f3</item> + <item>1f1ed,1f1f7</item> + <item>1f1ed,1f1f9</item> + <item>1f1ed,1f1fa</item> + <item>1f1ee,1f1e8</item> + <item>1f1ee,1f1e9</item> + <item>1f1ee,1f1ea</item> + <item>1f1ee,1f1f1</item> + <item>1f1ee,1f1f2</item> + <item>1f1ee,1f1f3</item> + <item>1f1ee,1f1f4</item> + <item>1f1ee,1f1f6</item> + <item>1f1ee,1f1f7</item> + <item>1f1ee,1f1f8</item> + <item>1f1ee,1f1f9</item> + <item>1f1ef,1f1ea</item> + <item>1f1ef,1f1f2</item> + <item>1f1ef,1f1f4</item> + <item>1f1ef,1f1f5</item> + <item>1f1f0,1f1ea</item> + <item>1f1f0,1f1ec</item> + <item>1f1f0,1f1ed</item> + <item>1f1f0,1f1ee</item> + <item>1f1f0,1f1f2</item> + <item>1f1f0,1f1f3</item> + <item>1f1f0,1f1f5</item> + <item>1f1f0,1f1f7</item> + <item>1f1f0,1f1fc</item> + <item>1f1f0,1f1fe</item> + <item>1f1f0,1f1ff</item> + <item>1f1f1,1f1e6</item> + <item>1f1f1,1f1e7</item> + <item>1f1f1,1f1e8</item> + <item>1f1f1,1f1ee</item> + <item>1f1f1,1f1f0</item> + <item>1f1f1,1f1f7</item> + <item>1f1f1,1f1f8</item> + <item>1f1f1,1f1f9</item> + <item>1f1f1,1f1fa</item> + <item>1f1f1,1f1fb</item> + <item>1f1f1,1f1fe</item> + <item>1f1f2,1f1e6</item> + <item>1f1f2,1f1e8</item> + <item>1f1f2,1f1e9</item> + <item>1f1f2,1f1ea</item> + <item>1f1f2,1f1eb</item> + <item>1f1f2,1f1ec</item> + <item>1f1f2,1f1ed</item> + <item>1f1f2,1f1f0</item> + <item>1f1f2,1f1f1</item> + <item>1f1f2,1f1f2</item> + <item>1f1f2,1f1f3</item> + <item>1f1f2,1f1f4</item> + <item>1f1f2,1f1f5</item> + <item>1f1f2,1f1f6</item> + <item>1f1f2,1f1f7</item> + <item>1f1f2,1f1f8</item> + <item>1f1f2,1f1f9</item> + <item>1f1f2,1f1fa</item> + <item>1f1f2,1f1fb</item> + <item>1f1f2,1f1fc</item> + <item>1f1f2,1f1fd</item> + <item>1f1f2,1f1fe</item> + <item>1f1f2,1f1ff</item> + <item>1f1f3,1f1e6</item> + <item>1f1f3,1f1e8</item> + <item>1f1f3,1f1ea</item> + <item>1f1f3,1f1eb</item> + <item>1f1f3,1f1ec</item> + <item>1f1f3,1f1ee</item> + <item>1f1f3,1f1f1</item> + <item>1f1f3,1f1f4</item> + <item>1f1f3,1f1f5</item> + <item>1f1f3,1f1f7</item> + <item>1f1f3,1f1fa</item> + <item>1f1f3,1f1ff</item> + <item>1f1f4,1f1f2</item> + <item>1f1f5,1f1e6</item> + <item>1f1f5,1f1ea</item> + <item>1f1f5,1f1eb</item> + <item>1f1f5,1f1ec</item> + <item>1f1f5,1f1ed</item> + <item>1f1f5,1f1f0</item> + <item>1f1f5,1f1f1</item> + <item>1f1f5,1f1f2</item> + <item>1f1f5,1f1f3</item> + <item>1f1f5,1f1f7</item> + <item>1f1f5,1f1f8</item> + <item>1f1f5,1f1f9</item> + <item>1f1f5,1f1fc</item> + <item>1f1f5,1f1fe</item> + <item>1f1f6,1f1e6</item> + <item>1f1f7,1f1ea</item> + <item>1f1f7,1f1f4</item> + <item>1f1f7,1f1f8</item> + <item>1f1f7,1f1fa</item> + <item>1f1f7,1f1fc</item> + <item>1f1f8,1f1e6</item> + <item>1f1f8,1f1e7</item> + <item>1f1f8,1f1e8</item> + <item>1f1f8,1f1e9</item> + <item>1f1f8,1f1ea</item> + <item>1f1f8,1f1ec</item> + <item>1f1f8,1f1ed</item> + <item>1f1f8,1f1ee</item> + <item>1f1f8,1f1ef</item> + <item>1f1f8,1f1f0</item> + <item>1f1f8,1f1f1</item> + <item>1f1f8,1f1f2</item> + <item>1f1f8,1f1f3</item> + <item>1f1f8,1f1f4</item> + <item>1f1f8,1f1f7</item> + <item>1f1f8,1f1f8</item> + <item>1f1f8,1f1f9</item> + <item>1f1f8,1f1fb</item> + <item>1f1f8,1f1fd</item> + <item>1f1f8,1f1fe</item> + <item>1f1f8,1f1ff</item> + <item>1f1f9,1f1e6</item> + <item>1f1f9,1f1e8</item> + <item>1f1f9,1f1e9</item> + <item>1f1f9,1f1eb</item> + <item>1f1f9,1f1ec</item> + <item>1f1f9,1f1ed</item> + <item>1f1f9,1f1ef</item> + <item>1f1f9,1f1f0</item> + <item>1f1f9,1f1f1</item> + <item>1f1f9,1f1f2</item> + <item>1f1f9,1f1f3</item> + <item>1f1f9,1f1f4</item> + <item>1f1f9,1f1f7</item> + <item>1f1f9,1f1f9</item> + <item>1f1f9,1f1fb</item> + <item>1f1f9,1f1fc</item> + <item>1f1f9,1f1ff</item> + <item>1f1fa,1f1e6</item> + <item>1f1fa,1f1ec</item> + <item>1f1fa,1f1f2</item> + <item>1f1fa,1f1f8</item> + <item>1f1fa,1f1fe</item> + <item>1f1fa,1f1ff</item> + <item>1f1fb,1f1e6</item> + <item>1f1fb,1f1e8</item> + <item>1f1fb,1f1ea</item> + <item>1f1fb,1f1ec</item> + <item>1f1fb,1f1ee</item> + <item>1f1fb,1f1f3</item> + <item>1f1fb,1f1fa</item> + <item>1f1fc,1f1eb</item> + <item>1f1fc,1f1f8</item> + <item>1f1fd,1f1f0</item> + <item>1f1fe,1f1ea</item> + <item>1f1fe,1f1f9</item> + <item>1f1ff,1f1e6</item> + <item>1f1ff,1f1f2</item> + <item>1f1ff,1f1fc</item> + </array> +</resources> diff --git a/briar-android/src/org/briarproject/android/ActivityComponent.java b/briar-android/src/org/briarproject/android/ActivityComponent.java index baee29d41e90080be85f317de5c3130415935ff9..3fbe8d3f27d289d0ada890690b6ca6c565a08c64 100644 --- a/briar-android/src/org/briarproject/android/ActivityComponent.java +++ b/briar-android/src/org/briarproject/android/ActivityComponent.java @@ -40,6 +40,8 @@ import org.briarproject.android.sharing.ShareForumActivity; import org.briarproject.android.sharing.ShareForumMessageFragment; import org.briarproject.android.sharing.SharingStatusBlogActivity; import org.briarproject.android.sharing.SharingStatusForumActivity; +import org.thoughtcrime.securesms.components.emoji.EmojiProvider; +import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel; import dagger.Component; @@ -114,6 +116,9 @@ public interface ActivityComponent { void inject(RssFeedManageActivity activity); + void inject(EmojiProvider emojiProvider); + void inject(RecentEmojiPageModel recentEmojiPageModel); + // Fragments void inject(ContactListFragment fragment); void inject(ForumListFragment fragment); diff --git a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java index 4ff2695e3d8ddfbefbaba41dc4952c683048865a..c2bb93fbb6dbfb49c02e3c4fd19b434b8ac5c9f4 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java @@ -25,7 +25,7 @@ import org.briarproject.android.controller.handler.UiResultExceptionHandler; import org.briarproject.android.fragment.BaseFragment; import org.briarproject.android.sharing.ShareBlogActivity; import org.briarproject.android.sharing.SharingStatusBlogActivity; -import org.briarproject.android.util.BriarRecyclerView; +import org.briarproject.android.view.BriarRecyclerView; import org.briarproject.api.blogs.BlogPostHeader; import org.briarproject.api.db.DbException; import org.briarproject.api.identity.Author; diff --git a/briar-android/src/org/briarproject/android/blogs/BlogListAdapter.java b/briar-android/src/org/briarproject/android/blogs/BlogListAdapter.java index b3e55f8482f2c812d82ea76fd228bd0cb294bbfa..590a24fba8db3e64bc001cb5288623cbf34161d6 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogListAdapter.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogListAdapter.java @@ -15,7 +15,7 @@ import android.widget.TextView; import org.briarproject.R; import org.briarproject.android.util.AndroidUtils; -import org.briarproject.android.util.TextAvatarView; +import org.briarproject.android.view.TextAvatarView; import org.briarproject.api.blogs.Blog; import org.briarproject.api.sync.GroupId; diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java b/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java index d870db1370f04851180dac7c4b4d9b75cab3b3a2..a25bcd76306234e9b80cda1039099e8918d854d0 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java @@ -18,7 +18,7 @@ import android.widget.TextView; import org.briarproject.R; import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener; -import org.briarproject.android.util.AuthorView; +import org.briarproject.android.view.AuthorView; import org.briarproject.api.blogs.BlogCommentHeader; import org.briarproject.api.blogs.BlogPostHeader; import org.briarproject.api.identity.Author; diff --git a/briar-android/src/org/briarproject/android/blogs/FeedFragment.java b/briar-android/src/org/briarproject/android/blogs/FeedFragment.java index c7ed7062211c8169284d52089d007522bb7b2f06..a8a8cea9d7e71bfb566133494d97a73240deb3e7 100644 --- a/briar-android/src/org/briarproject/android/blogs/FeedFragment.java +++ b/briar-android/src/org/briarproject/android/blogs/FeedFragment.java @@ -22,7 +22,7 @@ import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener; import org.briarproject.android.controller.handler.UiResultExceptionHandler; import org.briarproject.android.controller.handler.UiResultHandler; import org.briarproject.android.fragment.BaseFragment; -import org.briarproject.android.util.BriarRecyclerView; +import org.briarproject.android.view.BriarRecyclerView; import org.briarproject.api.blogs.Blog; import org.briarproject.api.blogs.BlogPostHeader; import org.briarproject.api.db.DbException; diff --git a/briar-android/src/org/briarproject/android/blogs/RssFeedManageActivity.java b/briar-android/src/org/briarproject/android/blogs/RssFeedManageActivity.java index 69c50dde66a78669e83cefcdd211e768bb9c633e..60230717bbc2263001c8a505b523ea352206be3b 100644 --- a/briar-android/src/org/briarproject/android/blogs/RssFeedManageActivity.java +++ b/briar-android/src/org/briarproject/android/blogs/RssFeedManageActivity.java @@ -14,7 +14,7 @@ import org.briarproject.R; import org.briarproject.android.ActivityComponent; import org.briarproject.android.BriarActivity; import org.briarproject.android.blogs.RssFeedAdapter.RssFeedListener; -import org.briarproject.android.util.BriarRecyclerView; +import org.briarproject.android.view.BriarRecyclerView; import org.briarproject.api.db.DbException; import org.briarproject.api.feed.Feed; import org.briarproject.api.feed.FeedManager; diff --git a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java index bead9c4db6c61e226db713f546155dd44e7122c0..312184f77d9f6d19d22cdc8b8c1d0b5095e11ea4 100644 --- a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java +++ b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java @@ -20,7 +20,7 @@ import org.briarproject.android.ActivityComponent; import org.briarproject.android.api.AndroidNotificationManager; import org.briarproject.android.fragment.BaseFragment; import org.briarproject.android.keyagreement.KeyAgreementActivity; -import org.briarproject.android.util.BriarRecyclerView; +import org.briarproject.android.view.BriarRecyclerView; import org.briarproject.api.blogs.BlogSharingManager; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java index a85723601f0390936947b2d139659d604516f202..3055777301450e5826bd96c2326bcb208f23d374 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java @@ -16,9 +16,6 @@ import android.util.SparseArray; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; @@ -27,8 +24,11 @@ import org.briarproject.R; import org.briarproject.android.ActivityComponent; import org.briarproject.android.BriarActivity; import org.briarproject.android.api.AndroidNotificationManager; +import org.briarproject.android.contact.ConversationAdapter.IntroductionHandler; import org.briarproject.android.introduction.IntroductionActivity; -import org.briarproject.android.util.BriarRecyclerView; +import org.briarproject.android.view.BriarRecyclerView; +import org.briarproject.android.view.TextInputView; +import org.briarproject.android.view.TextInputView.TextInputListener; import org.briarproject.api.FormatException; import org.briarproject.api.blogs.BlogSharingManager; import org.briarproject.api.clients.SessionId; @@ -93,8 +93,7 @@ import static org.briarproject.android.contact.ConversationItem.IncomingItem; import static org.briarproject.android.contact.ConversationItem.OutgoingItem; public class ConversationActivity extends BriarActivity - implements EventListener, OnClickListener, - ConversationAdapter.IntroductionHandler { + implements EventListener, IntroductionHandler, TextInputListener { private static final Logger LOG = Logger.getLogger(ConversationActivity.class.getName()); @@ -113,8 +112,7 @@ public class ConversationActivity extends BriarActivity private ImageView toolbarStatus; private TextView toolbarTitle; private BriarRecyclerView list; - private EditText content; - private View sendButton; + private TextInputView textInputView; // Fields that are accessed from background threads must be volatile @Inject @@ -139,6 +137,7 @@ public class ConversationActivity extends BriarActivity private volatile boolean connected = false; private volatile Map<MessageId, byte[]> bodyCache = new HashMap<>(); + @SuppressWarnings("ConstantConditions") @Override public void onCreate(Bundle state) { super.onCreate(state); @@ -177,13 +176,8 @@ public class ConversationActivity extends BriarActivity list.setAdapter(adapter); list.setEmptyText(getString(R.string.no_private_messages)); - content = (EditText) findViewById(R.id.input_text); - sendButton = findViewById(R.id.btn_send); - if (sendButton != null) { - // Enabled after loading the conversation - sendButton.setEnabled(false); - sendButton.setOnClickListener(this); - } + textInputView = (TextInputView) findViewById(R.id.text_input_container); + textInputView.setListener(this); } @Override @@ -262,6 +256,10 @@ public class ConversationActivity extends BriarActivity @Override public void onBackPressed() { + if (textInputView.isEmojiDrawerOpen()) { + textInputView.hideEmojiDrawer(); + return; + } // FIXME disabled exit transition, because it doesn't work for some reason #318 //supportFinishAfterTransition(); finish(); @@ -367,7 +365,7 @@ public class ConversationActivity extends BriarActivity runOnUiThread(new Runnable() { @Override public void run() { - sendButton.setEnabled(true); + textInputView.setSendButtonEnabled(true); if (headers.isEmpty() && introductions.isEmpty() && invitations.isEmpty()) { // we have no messages, @@ -637,14 +635,12 @@ public class ConversationActivity extends BriarActivity } @Override - public void onClick(View view) { + public void onSendClick(String text) { markMessagesRead(); - String message = content.getText().toString(); - if (message.equals("")) return; + if (text.equals("")) return; long timestamp = System.currentTimeMillis(); timestamp = Math.max(timestamp, getMinTimestampForNewMessage()); - createMessage(StringUtils.toUtf8(message), timestamp); - content.setText(""); + createMessage(StringUtils.toUtf8(text), timestamp); } private long getMinTimestampForNewMessage() { diff --git a/briar-android/src/org/briarproject/android/forum/ForumActivity.java b/briar-android/src/org/briarproject/android/forum/ForumActivity.java index 5f141948fed7b374c4f9941c3be479a5ba9d10c1..6b8c5a2b5e9d1e4cf0a09465b96b57c9693b604b 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumActivity.java +++ b/briar-android/src/org/briarproject/android/forum/ForumActivity.java @@ -21,8 +21,6 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.EditText; -import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; @@ -31,11 +29,13 @@ import org.briarproject.android.ActivityComponent; import org.briarproject.android.BriarActivity; import org.briarproject.android.api.AndroidNotificationManager; import org.briarproject.android.controller.handler.UiResultHandler; +import org.briarproject.android.forum.ForumController.ForumPostListener; import org.briarproject.android.sharing.ShareForumActivity; import org.briarproject.android.sharing.SharingStatusForumActivity; -import org.briarproject.android.util.AndroidUtils; -import org.briarproject.android.util.BriarRecyclerView; -import org.briarproject.android.util.TrustIndicatorView; +import org.briarproject.android.view.AuthorView; +import org.briarproject.android.view.BriarRecyclerView; +import org.briarproject.android.view.TextInputView; +import org.briarproject.android.view.TextInputView.TextInputListener; import org.briarproject.api.forum.Forum; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.MessageId; @@ -48,8 +48,6 @@ import java.util.Map; import javax.inject.Inject; -import im.delight.android.identicons.IdenticonDrawable; - import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP; import static android.support.v7.widget.RecyclerView.NO_POSITION; @@ -59,7 +57,7 @@ import static android.view.View.VISIBLE; import static android.widget.Toast.LENGTH_SHORT; public class ForumActivity extends BriarActivity implements - ForumController.ForumPostListener { + ForumPostListener, TextInputListener { static final String FORUM_NAME = "briar.FORUM_NAME"; @@ -80,8 +78,7 @@ public class ForumActivity extends BriarActivity implements protected ForumAdapter forumAdapter; private BriarRecyclerView recyclerView; - private EditText textInput; - private ViewGroup inputContainer; + private TextInputView textInput; private LinearLayoutManager linearLayoutManager; private volatile GroupId groupId = null; @@ -101,9 +98,9 @@ public class ForumActivity extends BriarActivity implements forumAdapter = new ForumAdapter(); - inputContainer = (ViewGroup) findViewById(R.id.text_input_container); - inputContainer.setVisibility(GONE); - textInput = (EditText) findViewById(R.id.input_text); + textInput = (TextInputView) findViewById(R.id.text_input_container); + textInput.setVisibility(GONE); + textInput.setListener(this); recyclerView = (BriarRecyclerView) findViewById(R.id.forum_discussion_list); recyclerView.setAdapter(forumAdapter); @@ -140,7 +137,7 @@ public class ForumActivity extends BriarActivity implements @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); - inputContainer.setVisibility( + textInput.setVisibility( savedInstanceState.getBoolean(KEY_INPUT_VISIBILITY) ? VISIBLE : GONE); } @@ -150,7 +147,7 @@ public class ForumActivity extends BriarActivity implements protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(KEY_INPUT_VISIBILITY, - inputContainer.getVisibility() == VISIBLE); + textInput.getVisibility() == VISIBLE); ForumEntry replyEntry = forumAdapter.getReplyEntry(); if (replyEntry != null) { outState.putByteArray(KEY_REPLY_ID, @@ -190,8 +187,10 @@ public class ForumActivity extends BriarActivity implements @Override public void onBackPressed() { - if (inputContainer.getVisibility() == VISIBLE) { - inputContainer.setVisibility(GONE); + if (textInput.isEmojiDrawerOpen()) { + textInput.hideEmojiDrawer(); + } else if (textInput.getVisibility() == VISIBLE) { + textInput.setVisibility(GONE); forumAdapter.setReplyEntry(null); } else { super.onBackPressed(); @@ -202,8 +201,8 @@ public class ForumActivity extends BriarActivity implements // An animation here would be an overkill because of the keyboard // popping up. // only clear the text when the input container was not visible - if (inputContainer.getVisibility() != VISIBLE) { - inputContainer.setVisibility(VISIBLE); + if (textInput.getVisibility() != VISIBLE) { + textInput.setVisibility(VISIBLE); textInput.setText(""); } textInput.requestFocus(); @@ -260,8 +259,8 @@ public class ForumActivity extends BriarActivity implements recyclerView.stopPeriodicUpdate(); } - public void sendMessage(View view) { - String text = textInput.getText().toString(); + @Override + public void onSendClick(String text) { if (text.trim().length() == 0) return; if (forumController.getForum() == null) return; @@ -274,7 +273,7 @@ public class ForumActivity extends BriarActivity implements replyEntry.getMessageId()); } hideSoftKeyboard(textInput); - inputContainer.setVisibility(GONE); + textInput.setVisibility(GONE); forumAdapter.setReplyEntry(null); } @@ -334,10 +333,9 @@ public class ForumActivity extends BriarActivity implements static class ForumViewHolder extends RecyclerView.ViewHolder { - final TextView textView, lvlText, authorText, dateText, repliesText; + final TextView textView, lvlText, repliesText; + final AuthorView author; final View[] lvls; - public final ImageView avatar; - final TrustIndicatorView trust; final View chevron, replyButton; final ViewGroup cell; final View topDivider; @@ -347,8 +345,7 @@ public class ForumActivity extends BriarActivity implements textView = (TextView) v.findViewById(R.id.text); lvlText = (TextView) v.findViewById(R.id.nested_line_text); - authorText = (TextView) v.findViewById(R.id.author); - dateText = (TextView) v.findViewById(R.id.date); + author = (AuthorView) v.findViewById(R.id.author); repliesText = (TextView) v.findViewById(R.id.replies); int[] nestedLineIds = { R.id.nested_line_1, R.id.nested_line_2, R.id.nested_line_3, @@ -358,8 +355,6 @@ public class ForumActivity extends BriarActivity implements for (int i = 0; i < lvls.length; i++) { lvls[i] = v.findViewById(nestedLineIds[i]); } - avatar = (ImageView) v.findViewById(R.id.avatar); - trust = (TrustIndicatorView) v.findViewById(R.id.trustIndicator); chevron = v.findViewById(R.id.chevron); replyButton = v.findViewById(R.id.btn_reply); cell = (ViewGroup) v.findViewById(R.id.forum_cell); @@ -604,7 +599,7 @@ public class ForumActivity extends BriarActivity implements public ForumViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.forum_discussion_cell, parent, false); + .inflate(R.layout.list_item_forum_post, parent, false); return new ForumViewHolder(v); } @@ -635,10 +630,9 @@ public class ForumActivity extends BriarActivity implements } else { ui.lvlText.setVisibility(GONE); } - ui.authorText.setText(data.getAuthor()); - ui.dateText.setText(AndroidUtils - .formatDate(ForumActivity.this, data.getTimestamp())); - ui.trust.setTrustLevel(data.getStatus()); + ui.author.setAuthor(data.getAuthor()); + ui.author.setDate(data.getTimestamp()); + ui.author.setAuthorStatus(data.getStatus()); int replies = getReplyCount(data); if (replies == 0) { @@ -648,8 +642,6 @@ public class ForumActivity extends BriarActivity implements .getQuantityString(R.plurals.message_replies, replies, replies)); } - ui.avatar.setImageDrawable( - new IdenticonDrawable(data.getAuthorId().getBytes())); if (hasDescendants(data)) { ui.chevron.setVisibility(VISIBLE); diff --git a/briar-android/src/org/briarproject/android/forum/ForumEntry.java b/briar-android/src/org/briarproject/android/forum/ForumEntry.java index b3f2aed40ba472efd6256072799288f36525b61c..f809d72394ddf9144ff1fc0642b32d34459673dd 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumEntry.java +++ b/briar-android/src/org/briarproject/android/forum/ForumEntry.java @@ -1,6 +1,7 @@ package org.briarproject.android.forum; import org.briarproject.api.forum.ForumPostHeader; +import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author.Status; import org.briarproject.api.identity.AuthorId; import org.briarproject.api.sync.MessageId; @@ -11,26 +12,24 @@ public class ForumEntry { private final String text; private final int level; private final long timestamp; - private final String author; - private final AuthorId authorId; + private final Author author; private Status status; private boolean isShowingDescendants = true; private boolean isRead = true; ForumEntry(ForumPostHeader h, String text, int level) { - this(h.getId(), text, level, h.getTimestamp(), h.getAuthor().getName(), - h.getAuthor().getId(), h.getAuthorStatus()); + this(h.getId(), text, level, h.getTimestamp(), h.getAuthor(), + h.getAuthorStatus()); this.isRead = h.isRead(); } public ForumEntry(MessageId messageId, String text, int level, - long timestamp, String author, AuthorId authorId, Status status) { + long timestamp, Author author, Status status) { this.messageId = messageId; this.text = text; this.level = level; this.timestamp = timestamp; this.author = author; - this.authorId = authorId; this.status = status; } @@ -46,14 +45,10 @@ public class ForumEntry { return timestamp; } - public String getAuthor() { + public Author getAuthor() { return author; } - AuthorId getAuthorId() { - return authorId; - } - public Status getStatus() { return status; } diff --git a/briar-android/src/org/briarproject/android/forum/ForumListAdapter.java b/briar-android/src/org/briarproject/android/forum/ForumListAdapter.java index 88759804aefa31203ede1b5047da0ab2becd6e48..d37f23a02933377cf35ee112103910c2abe6d585 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumListAdapter.java +++ b/briar-android/src/org/briarproject/android/forum/ForumListAdapter.java @@ -13,7 +13,7 @@ import android.widget.TextView; import org.briarproject.R; import org.briarproject.android.util.AndroidUtils; -import org.briarproject.android.util.TextAvatarView; +import org.briarproject.android.view.TextAvatarView; import org.briarproject.api.forum.Forum; import org.briarproject.api.sync.GroupId; diff --git a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java index 11c9bd293099b9dc9c4e9b429055c03d0a5b4ea4..fc9f63b37d7436c8a8499e553d0f0779e12cd1ef 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java +++ b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java @@ -18,7 +18,7 @@ import org.briarproject.android.ActivityComponent; import org.briarproject.android.api.AndroidNotificationManager; import org.briarproject.android.fragment.BaseEventFragment; import org.briarproject.android.sharing.InvitationsForumActivity; -import org.briarproject.android.util.BriarRecyclerView; +import org.briarproject.android.view.BriarRecyclerView; import org.briarproject.api.db.DbException; import org.briarproject.api.db.NoSuchGroupException; import org.briarproject.api.event.ContactRemovedEvent; diff --git a/briar-android/src/org/briarproject/android/forum/ForumTestControllerImpl.java b/briar-android/src/org/briarproject/android/forum/ForumTestControllerImpl.java index d443a65b83ddde537ee4795333af0ca7fea78f79..a5b89405bf703b0bcf619a9226f58b371f4fb840 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumTestControllerImpl.java +++ b/briar-android/src/org/briarproject/android/forum/ForumTestControllerImpl.java @@ -3,7 +3,8 @@ package org.briarproject.android.forum; import org.briarproject.android.controller.handler.ResultHandler; import org.briarproject.api.UniqueId; import org.briarproject.api.forum.Forum; -import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.MessageId; @@ -20,34 +21,27 @@ import static org.briarproject.api.identity.Author.Status.UNVERIFIED; public class ForumTestControllerImpl implements ForumController { + @Inject + AuthorFactory authorFactory; + private static final Logger LOG = Logger.getLogger(ForumControllerImpl.class.getName()); - private final static String[] AUTHORS = { - "Guðmundur", - "Jónas", - "Geir Þorsteinn GÃÂsli Máni Halldórsson Guðjónsson Mogensen", - "Baldur Friðrik", - "Anna KatrÃÂn", - "Þór", - "Anna Þorbjörg", - "Guðrún", - "Helga", - "Haraldur" + private final Author[] AUTHORS = { + authorFactory.createAuthor("Guðmundur", new byte[42]), + authorFactory.createAuthor("Jónas", new byte[42]), + authorFactory.createAuthor( + "Geir Þorsteinn GÃÂsli Máni Halldórsson Guðjónsson Mogensen", + new byte[42]), + authorFactory.createAuthor("Baldur Friðrik", new byte[42]), + authorFactory.createAuthor("Anna KatrÃÂn", new byte[42]), + authorFactory.createAuthor("Þór", new byte[42]), + authorFactory.createAuthor("Anna Þorbjörg", new byte[42]), + authorFactory.createAuthor("Guðrún", new byte[42]), + authorFactory.createAuthor("Helga", new byte[42]), + authorFactory.createAuthor("Haraldur", new byte[42]) }; - private final static AuthorId[] AUTHOR_ID = new AuthorId[AUTHORS.length]; - - static { - SecureRandom random = new SecureRandom(); - for (int i = 0; i < AUTHOR_ID.length; i++) { - byte[] b = new byte[UniqueId.LENGTH]; - random.nextBytes(b); - AUTHOR_ID[i] = new AuthorId(b); - - } - } - private final static String SAGA = "Það er upphaf á sögu þessari að Hákon konungur " + "Aðalsteinsfóstri réð fyrir Noregi og var þetta á ofanverðum " + @@ -117,8 +111,7 @@ public class ForumTestControllerImpl implements ForumController { random.nextBytes(b); forumEntries[e] = new ForumEntry(new MessageId(b), SAGA.substring(0, i[e]), - l[e], timestamp, AUTHORS[authorIndex], - AUTHOR_ID[authorIndex], UNVERIFIED); + l[e], timestamp, AUTHORS[authorIndex], UNVERIFIED); } LOG.info("forum entries: " + forumEntries.length); resultHandler.onResult(true); diff --git a/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java b/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java index bbbb14e460b70ce95416bd60b61d1a591102fa4b..ce25c8836ac5618799b7080453b1d1b8cdeaa674 100644 --- a/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java +++ b/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java @@ -17,7 +17,7 @@ import org.briarproject.android.contact.ContactListAdapter; import org.briarproject.android.contact.ContactListItem; import org.briarproject.android.contact.ConversationItem; import org.briarproject.android.fragment.BaseFragment; -import org.briarproject.android.util.BriarRecyclerView; +import org.briarproject.android.view.BriarRecyclerView; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactManager; diff --git a/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java b/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java index 268b16b85ecc8822b0ae808fdc742bfa58540cd7..ef8dcc55ebf9f4fca1ae931b7d239a0bbfa02d04 100644 --- a/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java +++ b/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java @@ -23,7 +23,7 @@ import org.briarproject.R; import org.briarproject.android.ActivityComponent; import org.briarproject.android.api.AndroidExecutor; import org.briarproject.android.fragment.BaseEventFragment; -import org.briarproject.android.util.CameraView; +import org.briarproject.android.view.CameraView; import org.briarproject.android.util.QrCodeDecoder; import org.briarproject.android.util.QrCodeUtils; import org.briarproject.api.event.Event; diff --git a/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java b/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java index a1671e2e24b16dfb78487e59b88b55ab704ba8cf..951b49c09809d6eced379b3ac61ea5cfe5f1d69b 100644 --- a/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java +++ b/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java @@ -17,7 +17,7 @@ import org.briarproject.android.ActivityComponent; import org.briarproject.android.contact.BaseContactListAdapter; import org.briarproject.android.contact.ContactListItem; import org.briarproject.android.fragment.BaseFragment; -import org.briarproject.android.util.BriarRecyclerView; +import org.briarproject.android.view.BriarRecyclerView; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactManager; diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationAdapter.java b/briar-android/src/org/briarproject/android/sharing/InvitationAdapter.java index f4c988454856d1f8f59c582f37f6639cc4fda225..00852f7e9cbb66b90e20cf9fa310b0c7cb27f105 100644 --- a/briar-android/src/org/briarproject/android/sharing/InvitationAdapter.java +++ b/briar-android/src/org/briarproject/android/sharing/InvitationAdapter.java @@ -10,7 +10,7 @@ import android.widget.Button; import android.widget.TextView; import org.briarproject.R; -import org.briarproject.android.util.TextAvatarView; +import org.briarproject.android.view.TextAvatarView; import org.briarproject.api.contact.Contact; import org.briarproject.api.sharing.InvitationItem; import org.briarproject.util.StringUtils; diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java b/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java index fc6761e058ee516f8cb7f5ed7da6cee0b6d19189..c7d99b14bd0f8990fd0d8617be028fd00e20dce9 100644 --- a/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java +++ b/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java @@ -7,7 +7,7 @@ import android.widget.Toast; import org.briarproject.R; import org.briarproject.android.BriarActivity; -import org.briarproject.android.util.BriarRecyclerView; +import org.briarproject.android.view.BriarRecyclerView; import org.briarproject.api.event.ContactRemovedEvent; import org.briarproject.api.event.Event; import org.briarproject.api.event.EventBus; diff --git a/briar-android/src/org/briarproject/android/sharing/SharingStatusActivity.java b/briar-android/src/org/briarproject/android/sharing/SharingStatusActivity.java index 6a67448c9e2f911f5e3a5a5a4a2992758c91c572..0d69fe6d293dfce17e233b07def8bf030a649ae2 100644 --- a/briar-android/src/org/briarproject/android/sharing/SharingStatusActivity.java +++ b/briar-android/src/org/briarproject/android/sharing/SharingStatusActivity.java @@ -8,7 +8,7 @@ import android.view.MenuItem; import org.briarproject.R; import org.briarproject.android.BriarActivity; import org.briarproject.android.contact.ContactListItem; -import org.briarproject.android.util.BriarRecyclerView; +import org.briarproject.android.view.BriarRecyclerView; import org.briarproject.api.contact.Contact; import org.briarproject.api.db.DbException; import org.briarproject.api.identity.IdentityManager; diff --git a/briar-android/src/org/briarproject/android/util/AndroidUtils.java b/briar-android/src/org/briarproject/android/util/AndroidUtils.java index 759a25e6b47b56bb36af1cb51fba0bd364f3dd89..319c1854fc7a25cb738934cbcafd9dc1fdf38c0d 100644 --- a/briar-android/src/org/briarproject/android/util/AndroidUtils.java +++ b/briar-android/src/org/briarproject/android/util/AndroidUtils.java @@ -15,7 +15,6 @@ import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.format.DateUtils; -import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; import android.text.style.ForegroundColorSpan; import android.text.style.URLSpan; @@ -23,6 +22,7 @@ import android.view.View; import android.widget.TextView; import org.briarproject.R; +import org.briarproject.android.view.ArticleMovementMethod; import org.briarproject.android.widget.LinkDialogFragment; import org.briarproject.util.IoUtils; import org.briarproject.util.StringUtils; diff --git a/briar-android/src/org/briarproject/android/util/ArticleMovementMethod.java b/briar-android/src/org/briarproject/android/view/ArticleMovementMethod.java similarity index 97% rename from briar-android/src/org/briarproject/android/util/ArticleMovementMethod.java rename to briar-android/src/org/briarproject/android/view/ArticleMovementMethod.java index ad148a719d9e78e6e7f822c6724bd7bec9268804..d02ca1081e7904d00c7ca0d4c9d24c37e7bd7680 100644 --- a/briar-android/src/org/briarproject/android/util/ArticleMovementMethod.java +++ b/briar-android/src/org/briarproject/android/view/ArticleMovementMethod.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.briarproject.android.util; +package org.briarproject.android.view; import android.text.Layout; import android.text.Spannable; diff --git a/briar-android/src/org/briarproject/android/util/AuthorView.java b/briar-android/src/org/briarproject/android/view/AuthorView.java similarity index 95% rename from briar-android/src/org/briarproject/android/util/AuthorView.java rename to briar-android/src/org/briarproject/android/view/AuthorView.java index 45ab5d34741fc519a45c92f2c371b99851b1b004..40f8e50693e96fc29f25b385c6cfc0242c535dbe 100644 --- a/briar-android/src/org/briarproject/android/util/AuthorView.java +++ b/briar-android/src/org/briarproject/android/view/AuthorView.java @@ -1,10 +1,11 @@ -package org.briarproject.android.util; +package org.briarproject.android.view; import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; import android.graphics.Typeface; import android.support.annotation.Nullable; +import android.support.annotation.UiThread; import android.support.v4.app.ActivityOptionsCompat; import android.support.v4.content.ContextCompat; import android.util.AttributeSet; @@ -18,6 +19,8 @@ import android.widget.TextView; import org.briarproject.R; import org.briarproject.android.blogs.BlogActivity; +import org.briarproject.android.util.AndroidUtils; +import org.briarproject.android.view.TrustIndicatorView; import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author.Status; import org.briarproject.api.sync.GroupId; @@ -34,6 +37,7 @@ import static android.util.TypedValue.COMPLEX_UNIT_PX; import static org.briarproject.android.BriarActivity.GROUP_ID; import static org.briarproject.api.identity.Author.Status.OURSELVES; +@UiThread public class AuthorView extends RelativeLayout { private final CircleImageView avatar; diff --git a/briar-android/src/org/briarproject/android/util/BriarRecyclerView.java b/briar-android/src/org/briarproject/android/view/BriarRecyclerView.java similarity index 96% rename from briar-android/src/org/briarproject/android/util/BriarRecyclerView.java rename to briar-android/src/org/briarproject/android/view/BriarRecyclerView.java index affbe9e13b62331b7672de62a10323ab4d7a646a..40f455404d96575b3a5a838361b01c9cf2bb7a81 100644 --- a/briar-android/src/org/briarproject/android/util/BriarRecyclerView.java +++ b/briar-android/src/org/briarproject/android/view/BriarRecyclerView.java @@ -1,7 +1,8 @@ -package org.briarproject.android.util; +package org.briarproject.android.view; import android.content.Context; import android.content.res.TypedArray; +import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.Adapter; import android.util.AttributeSet; @@ -34,11 +35,11 @@ public class BriarRecyclerView extends FrameLayout { this(context, null, 0); } - public BriarRecyclerView(Context context, AttributeSet attrs) { + public BriarRecyclerView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } - public BriarRecyclerView(Context context, AttributeSet attrs, + public BriarRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); diff --git a/briar-android/src/org/briarproject/android/util/BriarRecyclerViewBehavior.java b/briar-android/src/org/briarproject/android/view/BriarRecyclerViewBehavior.java similarity index 96% rename from briar-android/src/org/briarproject/android/util/BriarRecyclerViewBehavior.java rename to briar-android/src/org/briarproject/android/view/BriarRecyclerViewBehavior.java index fc57a27f0f300b519bd2ae094587597350c71a83..cfaa352cd430dfa8895f6ebf48a06a61947ecfb4 100644 --- a/briar-android/src/org/briarproject/android/util/BriarRecyclerViewBehavior.java +++ b/briar-android/src/org/briarproject/android/view/BriarRecyclerViewBehavior.java @@ -1,4 +1,4 @@ -package org.briarproject.android.util; +package org.briarproject.android.view; import android.content.Context; import android.support.design.widget.CoordinatorLayout; diff --git a/briar-android/src/org/briarproject/android/util/CameraView.java b/briar-android/src/org/briarproject/android/view/CameraView.java similarity index 98% rename from briar-android/src/org/briarproject/android/util/CameraView.java rename to briar-android/src/org/briarproject/android/view/CameraView.java index 79ea7eaddf21a36139608d7a2115d8da12517a72..0ce1ad13627837ac2a3bda936a99ff8bc3eb16b0 100644 --- a/briar-android/src/org/briarproject/android/util/CameraView.java +++ b/briar-android/src/org/briarproject/android/view/CameraView.java @@ -1,4 +1,4 @@ -package org.briarproject.android.util; +package org.briarproject.android.view; import android.content.Context; import android.hardware.Camera; @@ -12,6 +12,8 @@ import android.util.DisplayMetrics; import android.view.SurfaceHolder; import android.view.SurfaceView; +import org.briarproject.android.util.PreviewConsumer; + import java.io.IOException; import java.util.Collections; import java.util.List; diff --git a/briar-android/src/org/briarproject/android/util/TextAvatarView.java b/briar-android/src/org/briarproject/android/view/TextAvatarView.java similarity index 92% rename from briar-android/src/org/briarproject/android/util/TextAvatarView.java rename to briar-android/src/org/briarproject/android/view/TextAvatarView.java index b48d0e7ad1a0280b30f544677ad7ed1297d7357c..70ab3f3981bc18138cce89dfee9a5d6c791c0199 100644 --- a/briar-android/src/org/briarproject/android/util/TextAvatarView.java +++ b/briar-android/src/org/briarproject/android/view/TextAvatarView.java @@ -1,8 +1,10 @@ -package org.briarproject.android.util; +package org.briarproject.android.view; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.Drawable; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; import android.support.v4.content.ContextCompat; import android.support.v7.widget.AppCompatTextView; import android.util.AttributeSet; @@ -16,6 +18,7 @@ import org.briarproject.api.identity.Author; import de.hdodenhof.circleimageview.CircleImageView; import im.delight.android.identicons.IdenticonDrawable; +@UiThread public class TextAvatarView extends FrameLayout { final private AppCompatTextView character; @@ -23,7 +26,7 @@ public class TextAvatarView extends FrameLayout { final private TextView badge; private int unreadCount; - public TextAvatarView(Context context, AttributeSet attrs) { + public TextAvatarView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); LayoutInflater inflater = (LayoutInflater) context diff --git a/briar-android/src/org/briarproject/android/view/TextInputView.java b/briar-android/src/org/briarproject/android/view/TextInputView.java new file mode 100644 index 0000000000000000000000000000000000000000..e257a1915efe96a9272023a6ba2ebefa1256003e --- /dev/null +++ b/briar-android/src/org/briarproject/android/view/TextInputView.java @@ -0,0 +1,171 @@ +package org.briarproject.android.view; + +import android.content.Context; +import android.os.IBinder; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.annotation.UiThread; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +import org.briarproject.R; +import org.thoughtcrime.securesms.components.KeyboardAwareRelativeLayout; +import org.thoughtcrime.securesms.components.emoji.EmojiDrawer; +import org.thoughtcrime.securesms.components.emoji.EmojiDrawer.EmojiEventListener; +import org.thoughtcrime.securesms.components.emoji.EmojiEditText; +import org.thoughtcrime.securesms.components.emoji.EmojiToggle; + +import java.util.logging.Logger; + +import static android.content.Context.INPUT_METHOD_SERVICE; + +@UiThread +public class TextInputView extends KeyboardAwareRelativeLayout + implements EmojiEventListener { + + private static final String TAG = TextInputView.class.getName(); + private static final Logger LOG = Logger.getLogger(TAG); + + private EmojiEditText editText; + private View sendButton; + private EmojiDrawer emojiDrawer; + + private TextInputListener listener; + + public TextInputView(Context context) { + this(context, null); + } + + public TextInputView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public TextInputView(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.text_input_view, this, true); + + // find views + EmojiToggle emojiToggle = (EmojiToggle) findViewById(R.id.emoji_toggle); + editText = (EmojiEditText) findViewById(R.id.input_text); + emojiDrawer = (EmojiDrawer) findViewById(R.id.emoji_drawer); + sendButton = findViewById(R.id.btn_send); + + emojiToggle.attach(emojiDrawer); + emojiToggle.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + onEmojiToggleClicked(); + } + }); + editText.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + showSoftKeyboard(); + } + }); + sendButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (listener != null) { + listener.onSendClick(editText.getText().toString()); + editText.setText(""); + } + } + }); + emojiDrawer.setEmojiEventListener(this); + } + + @Override + public void onKeyEvent(KeyEvent keyEvent) { + editText.dispatchKeyEvent(keyEvent); + } + + @Override + public void onEmojiSelected(String emoji) { + editText.insertEmoji(emoji); + } + + private void onEmojiToggleClicked() { + if (isEmojiDrawerOpen()) { + showSoftKeyboard(); + } else { + showEmojiDrawer(); + } + } + + public void setText(String text) { + editText.setText(text); + } + + public void setHint(@StringRes int res) { + editText.setHint(res); + } + + public void setSendButtonEnabled(boolean enabled) { + sendButton.setEnabled(enabled); + } + + public void setListener(TextInputListener listener) { + this.listener = listener; + } + + public interface TextInputListener { + void onSendClick(String text); + } + + public void showSoftKeyboard() { + if (isKeyboardOpen()) return; + + if (emojiDrawer.isShowing()) { + postOnKeyboardOpen(new Runnable() { + @Override + public void run() { + hideEmojiDrawer(); + } + }); + } + editText.post(new Runnable() { + @Override + public void run() { + editText.requestFocus(); + Object o = getContext().getSystemService(INPUT_METHOD_SERVICE); + ((InputMethodManager) o).showSoftInput(editText, 0); + } + }); + } + + public void hideSoftKeyboard() { + IBinder token = editText.getWindowToken(); + Object o = getContext().getSystemService(INPUT_METHOD_SERVICE); + ((InputMethodManager) o).hideSoftInputFromWindow(token, 0); + } + + public void showEmojiDrawer() { + if (isKeyboardOpen()) { + postOnKeyboardClose(new Runnable() { + @Override public void run() { + emojiDrawer.show(getKeyboardHeight()); + } + }); + hideSoftKeyboard(); + } else { + emojiDrawer.show(getKeyboardHeight()); + } + } + + public void hideEmojiDrawer() { + emojiDrawer.hide(); + } + + public boolean isEmojiDrawerOpen() { + return emojiDrawer.isShowing(); + } + +} diff --git a/briar-android/src/org/briarproject/android/util/TrustIndicatorView.java b/briar-android/src/org/briarproject/android/view/TrustIndicatorView.java similarity index 92% rename from briar-android/src/org/briarproject/android/util/TrustIndicatorView.java rename to briar-android/src/org/briarproject/android/view/TrustIndicatorView.java index 97d8e7763609a175d86a9e18199098239720bfbf..5a88acdf6850fc5244e1c690e8fb897a60cd9d67 100644 --- a/briar-android/src/org/briarproject/android/util/TrustIndicatorView.java +++ b/briar-android/src/org/briarproject/android/view/TrustIndicatorView.java @@ -1,6 +1,7 @@ -package org.briarproject.android.util; +package org.briarproject.android.view; import android.content.Context; +import android.support.annotation.UiThread; import android.support.v4.content.ContextCompat; import android.util.AttributeSet; import android.widget.ImageView; @@ -10,6 +11,7 @@ import org.briarproject.api.identity.Author.Status; import static org.briarproject.api.identity.Author.Status.OURSELVES; +@UiThread public class TrustIndicatorView extends ImageView { public TrustIndicatorView(Context context) { diff --git a/briar-android/src/org/thoughtcrime/securesms/LICENSE b/briar-android/src/org/thoughtcrime/securesms/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..94a045322262546cfb9d72561e1d587b5c2ffb1e --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/LICENSE @@ -0,0 +1,621 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS diff --git a/briar-android/src/org/thoughtcrime/securesms/components/KeyboardAwareRelativeLayout.java b/briar-android/src/org/thoughtcrime/securesms/components/KeyboardAwareRelativeLayout.java new file mode 100644 index 0000000000000000000000000000000000000000..5217078252324fab5d8e0ce730cbfca0e5ec25f3 --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/components/KeyboardAwareRelativeLayout.java @@ -0,0 +1,270 @@ +package org.thoughtcrime.securesms.components; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Rect; +import android.os.Build; +import android.preference.PreferenceManager; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.util.AttributeSet; +import android.view.View; +import android.view.WindowManager; +import android.widget.RelativeLayout; + +import org.briarproject.R; + +import java.lang.reflect.Field; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Logger; + +import static android.content.Context.WINDOW_SERVICE; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; + +/** + * RelativeLayout that, when a view container, will report back when it thinks + * a soft keyboard has been opened and what its height would be. + */ +@UiThread +public class KeyboardAwareRelativeLayout extends RelativeLayout { + + private static final Logger LOG = + Logger.getLogger(KeyboardAwareRelativeLayout.class.getName()); + + private final Rect rect = new Rect(); + private final Set<OnKeyboardHiddenListener> hiddenListeners = + new HashSet<>(); + private final Set<OnKeyboardShownListener> shownListeners = new HashSet<>(); + private final int minKeyboardSize; + private final int minCustomKeyboardSize; + private final int defaultCustomKeyboardSize; + private final int minCustomKeyboardTopMargin; + private final int statusBarHeight; + + private int viewInset; + + private boolean keyboardOpen = false; + private int rotation = -1; + + public KeyboardAwareRelativeLayout(Context context) { + this(context, null); + } + + public KeyboardAwareRelativeLayout(Context context, + @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public KeyboardAwareRelativeLayout(Context context, + @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + final int statusBarRes = getResources() + .getIdentifier("status_bar_height", "dimen", "android"); + minKeyboardSize = + getResources().getDimensionPixelSize(R.dimen.min_keyboard_size); + minCustomKeyboardSize = getResources() + .getDimensionPixelSize(R.dimen.min_custom_keyboard_size); + defaultCustomKeyboardSize = getResources() + .getDimensionPixelSize(R.dimen.default_custom_keyboard_size); + minCustomKeyboardTopMargin = getResources() + .getDimensionPixelSize(R.dimen.min_custom_keyboard_top_margin); + statusBarHeight = statusBarRes > 0 ? + getResources().getDimensionPixelSize(statusBarRes) : 0; + viewInset = getViewInset(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + updateRotation(); + updateKeyboardState(); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + private void updateRotation() { + int oldRotation = rotation; + rotation = getDeviceRotation(); + if (oldRotation != rotation) { + LOG.info("Rotation changed"); + onKeyboardClose(); + } + } + + private void updateKeyboardState() { + if (isLandscape()) { + if (keyboardOpen) onKeyboardClose(); + return; + } + + if (viewInset == 0 && Build.VERSION.SDK_INT >= 21) + viewInset = getViewInset(); + int availableHeight = + getRootView().getHeight() - statusBarHeight - viewInset; + getWindowVisibleDisplayFrame(rect); + + int keyboardHeight = availableHeight - (rect.bottom - rect.top); + + if (keyboardHeight > minKeyboardSize) { + if (getKeyboardHeight() != keyboardHeight) + setKeyboardPortraitHeight(keyboardHeight); + if (!keyboardOpen) onKeyboardOpen(keyboardHeight); + } else if (keyboardOpen) { + onKeyboardClose(); + } + } + + @TargetApi(21) + private int getViewInset() { + try { + Field attachInfoField = View.class.getDeclaredField("mAttachInfo"); + attachInfoField.setAccessible(true); + Object attachInfo = attachInfoField.get(this); + if (attachInfo != null) { + Field stableInsetsField = + attachInfo.getClass().getDeclaredField("mStableInsets"); + stableInsetsField.setAccessible(true); + Rect insets = (Rect) stableInsetsField.get(attachInfo); + return insets.bottom; + } + } catch (NoSuchFieldException e) { + LOG.log(WARNING, + "field reflection error when measuring view inset", e); + } catch (IllegalAccessException e) { + LOG.log(WARNING, + "access reflection error when measuring view inset", e); + } + return 0; + } + + protected void onKeyboardOpen(int keyboardHeight) { + if (LOG.isLoggable(INFO)) + LOG.info("onKeyboardOpen(" + keyboardHeight + ")"); + keyboardOpen = true; + + notifyShownListeners(); + } + + protected void onKeyboardClose() { + LOG.info("onKeyboardClose()"); + keyboardOpen = false; + notifyHiddenListeners(); + } + + public boolean isKeyboardOpen() { + return keyboardOpen; + } + + public int getKeyboardHeight() { + return isLandscape() ? getKeyboardLandscapeHeight() : + getKeyboardPortraitHeight(); + } + + public boolean isLandscape() { + int rotation = getDeviceRotation(); + return rotation == ROTATION_90 || rotation == ROTATION_270; + } + + private int getDeviceRotation() { + WindowManager windowManager = + (WindowManager) getContext().getSystemService(WINDOW_SERVICE); + return windowManager.getDefaultDisplay().getRotation(); + } + + private int getKeyboardLandscapeHeight() { + return Math.max(getHeight(), getRootView().getHeight()) / 2; + } + + private int getKeyboardPortraitHeight() { + SharedPreferences prefs = + PreferenceManager.getDefaultSharedPreferences(getContext()); + int keyboardHeight = prefs.getInt("keyboard_height_portrait", + defaultCustomKeyboardSize); + return clamp(keyboardHeight, minCustomKeyboardSize, + getRootView().getHeight() - minCustomKeyboardTopMargin); + } + + private int clamp(int value, int min, int max) { + return Math.min(Math.max(value, min), max); + } + + private void setKeyboardPortraitHeight(int height) { + SharedPreferences prefs = + PreferenceManager.getDefaultSharedPreferences(getContext()); + prefs.edit().putInt("keyboard_height_portrait", height).apply(); + } + + public void postOnKeyboardClose(final Runnable runnable) { + if (keyboardOpen) { + addOnKeyboardHiddenListener(new OnKeyboardHiddenListener() { + @Override + public void onKeyboardHidden() { + removeOnKeyboardHiddenListener(this); + runnable.run(); + } + }); + } else { + runnable.run(); + } + } + + public void postOnKeyboardOpen(final Runnable runnable) { + if (!keyboardOpen) { + addOnKeyboardShownListener(new OnKeyboardShownListener() { + @Override + public void onKeyboardShown() { + removeOnKeyboardShownListener(this); + runnable.run(); + } + }); + } else { + runnable.run(); + } + } + + public void addOnKeyboardHiddenListener(OnKeyboardHiddenListener listener) { + hiddenListeners.add(listener); + } + + public void removeOnKeyboardHiddenListener( + OnKeyboardHiddenListener listener) { + hiddenListeners.remove(listener); + } + + public void addOnKeyboardShownListener(OnKeyboardShownListener listener) { + shownListeners.add(listener); + } + + public void removeOnKeyboardShownListener( + OnKeyboardShownListener listener) { + shownListeners.remove(listener); + } + + private void notifyHiddenListeners() { + // Make a copy as listeners may remove themselves when called + Set<OnKeyboardHiddenListener> listeners = + new HashSet<>(hiddenListeners); + for (OnKeyboardHiddenListener listener : listeners) { + listener.onKeyboardHidden(); + } + } + + private void notifyShownListeners() { + // Make a copy as listeners may remove themselves when called + Set<OnKeyboardShownListener> listeners = new HashSet<>(shownListeners); + for (OnKeyboardShownListener listener : listeners) { + listener.onKeyboardShown(); + } + } + + public interface OnKeyboardHiddenListener { + void onKeyboardHidden(); + } + + public interface OnKeyboardShownListener { + void onKeyboardShown(); + } +} diff --git a/briar-android/src/org/thoughtcrime/securesms/components/RepeatableImageKey.java b/briar-android/src/org/thoughtcrime/securesms/components/RepeatableImageKey.java new file mode 100644 index 0000000000000000000000000000000000000000..453f82f8cf0d79feeca1b74fd27625f30c74ac4c --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/components/RepeatableImageKey.java @@ -0,0 +1,94 @@ +package org.thoughtcrime.securesms.components; + +import android.content.Context; +import android.support.annotation.UiThread; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.widget.ImageButton; + +import static android.view.HapticFeedbackConstants.KEYBOARD_TAP; +import static android.view.MotionEvent.ACTION_CANCEL; +import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_UP; + +@UiThread +public class RepeatableImageKey extends ImageButton { + + private KeyEventListener listener; + + public RepeatableImageKey(Context context) { + super(context); + init(); + } + + public RepeatableImageKey(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public RepeatableImageKey(Context context, AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + setOnClickListener(new RepeaterClickListener()); + setOnTouchListener(new RepeaterTouchListener()); + } + + public void setOnKeyEventListener(KeyEventListener listener) { + this.listener = listener; + } + + private void notifyListener() { + if (listener != null) listener.onKeyEvent(); + } + + private class RepeaterClickListener implements OnClickListener { + @Override + public void onClick(View v) { + notifyListener(); + } + } + + private class Repeater implements Runnable { + @Override + public void run() { + notifyListener(); + postDelayed(this, ViewConfiguration.getKeyRepeatDelay()); + } + } + + private class RepeaterTouchListener implements OnTouchListener { + + private final Repeater repeater; + + private RepeaterTouchListener() { + repeater = new Repeater(); + } + + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + switch (motionEvent.getAction()) { + case ACTION_DOWN: + view.postDelayed(repeater, + ViewConfiguration.getKeyRepeatTimeout()); + performHapticFeedback(KEYBOARD_TAP); + return false; + case ACTION_CANCEL: + case ACTION_UP: + view.removeCallbacks(repeater); + return false; + default: + return false; + } + } + } + + public interface KeyEventListener { + void onKeyEvent(); + } +} diff --git a/briar-android/src/org/thoughtcrime/securesms/components/emoji/AnimatingImageSpan.java b/briar-android/src/org/thoughtcrime/securesms/components/emoji/AnimatingImageSpan.java new file mode 100644 index 0000000000000000000000000000000000000000..28a71933f274d115dbf4e438a4db2c79ffcc257f --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/components/emoji/AnimatingImageSpan.java @@ -0,0 +1,15 @@ +package org.thoughtcrime.securesms.components.emoji; + +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Drawable.Callback; +import android.support.annotation.UiThread; +import android.text.style.ImageSpan; + +@UiThread +class AnimatingImageSpan extends ImageSpan { + + AnimatingImageSpan(Drawable drawable, Callback callback) { + super(drawable, ALIGN_BOTTOM); + drawable.setCallback(callback); + } +} diff --git a/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiDrawer.java b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiDrawer.java new file mode 100644 index 0000000000000000000000000000000000000000..69234600f6b1c5e70799b4cf7b30edf0bc98d791 --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiDrawer.java @@ -0,0 +1,201 @@ +package org.thoughtcrime.securesms.components.emoji; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import com.astuetz.PagerSlidingTabStrip; +import com.astuetz.PagerSlidingTabStrip.CustomTabProvider; + +import org.briarproject.R; +import org.thoughtcrime.securesms.components.RepeatableImageKey; +import org.thoughtcrime.securesms.components.RepeatableImageKey.KeyEventListener; +import org.thoughtcrime.securesms.components.emoji.EmojiPageView.EmojiSelectionListener; + +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Logger; + +import static android.view.KeyEvent.ACTION_DOWN; +import static android.view.KeyEvent.KEYCODE_DEL; +import static android.widget.ImageView.ScaleType.CENTER_INSIDE; +import static java.util.logging.Level.INFO; + +@UiThread +public class EmojiDrawer extends LinearLayout { + + private static final Logger LOG = + Logger.getLogger(EmojiDrawer.class.getName()); + private static final KeyEvent DELETE_KEY_EVENT = + new KeyEvent(ACTION_DOWN, KEYCODE_DEL); + + private ViewPager pager; + private List<EmojiPageModel> models; + private PagerSlidingTabStrip strip; + private RecentEmojiPageModel recentModel; + private EmojiEventListener listener; + private EmojiDrawerListener drawerListener; + + public EmojiDrawer(Context context) { + this(context, null); + } + + public EmojiDrawer(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + setOrientation(VERTICAL); + } + + private void initView() { + final View v = LayoutInflater.from(getContext()) + .inflate(R.layout.emoji_drawer, this, true); + initializeResources(v); + initializePageModels(); + initializeEmojiGrid(); + } + + public void setEmojiEventListener(EmojiEventListener listener) { + this.listener = listener; + } + + public void setDrawerListener(EmojiDrawerListener listener) { + this.drawerListener = listener; + } + + private void initializeResources(View v) { + this.pager = (ViewPager) v.findViewById(R.id.emoji_pager); + this.strip = (PagerSlidingTabStrip) v.findViewById(R.id.tabs); + + RepeatableImageKey backspace = + (RepeatableImageKey) v.findViewById(R.id.backspace); + backspace.setOnKeyEventListener(new KeyEventListener() { + @Override + public void onKeyEvent() { + if (listener != null) listener.onKeyEvent(DELETE_KEY_EVENT); + } + }); + } + + public boolean isShowing() { + return getVisibility() == VISIBLE; + } + + public void show(int height) { + if (this.pager == null) initView(); + ViewGroup.LayoutParams params = getLayoutParams(); + params.height = height; + if (LOG.isLoggable(INFO)) + LOG.info("Showing emoji drawer with height " + params.height); + setLayoutParams(params); + setVisibility(VISIBLE); + if (drawerListener != null) drawerListener.onShown(); + } + + public void hide() { + setVisibility(GONE); + if (drawerListener != null) drawerListener.onHidden(); + } + + private void initializeEmojiGrid() { + pager.setAdapter(new EmojiPagerAdapter(getContext(), + models, + new EmojiSelectionListener() { + @Override + public void onEmojiSelected(String emoji) { + recentModel.onCodePointSelected(emoji); + if (listener != null) listener.onEmojiSelected(emoji); + } + })); + + if (recentModel.getEmoji().length == 0) { + pager.setCurrentItem(1); + } + strip.setViewPager(pager); + } + + private void initializePageModels() { + this.models = new LinkedList<>(); + this.recentModel = new RecentEmojiPageModel(getContext()); + this.models.add(recentModel); + this.models.addAll(EmojiProvider.getInstance(getContext()) + .getStaticPages()); + } + + public static class EmojiPagerAdapter extends PagerAdapter + implements CustomTabProvider { + private Context context; + private List<EmojiPageModel> pages; + private EmojiSelectionListener listener; + + private EmojiPagerAdapter(@NonNull Context context, + @NonNull List<EmojiPageModel> pages, + @Nullable EmojiSelectionListener listener) { + super(); + this.context = context; + this.pages = pages; + this.listener = listener; + } + + @Override + public int getCount() { + return pages.size(); + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + EmojiPageView page = new EmojiPageView(context); + page.setModel(pages.get(position)); + page.setEmojiSelectedListener(listener); + container.addView(page); + return page; + } + + @Override + public void destroyItem(ViewGroup container, int position, + Object object) { + container.removeView((View) object); + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return view == object; + } + + @Override + public View getCustomTabView(ViewGroup viewGroup, int i) { + ImageView image = new ImageView(context); + image.setScaleType(CENTER_INSIDE); + image.setImageResource(pages.get(i).getIcon()); + return image; + } + + @Override + public void tabSelected(View view) { + view.animate().setDuration(300).alpha(1); + } + + @Override + public void tabUnselected(View view) { + view.animate().setDuration(400).alpha(0.4f); + } + } + + public interface EmojiEventListener extends EmojiSelectionListener { + void onKeyEvent(KeyEvent keyEvent); + } + + public interface EmojiDrawerListener { + void onShown(); + + void onHidden(); + } +} diff --git a/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiEditText.java b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiEditText.java new file mode 100644 index 0000000000000000000000000000000000000000..a8a25671ed4821551c53ee4eec5f80063f7061fd --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiEditText.java @@ -0,0 +1,47 @@ +package org.thoughtcrime.securesms.components.emoji; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.support.v7.widget.AppCompatEditText; +import android.text.InputFilter; +import android.util.AttributeSet; + +import org.briarproject.R; +import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable; + +@UiThread +public class EmojiEditText extends AppCompatEditText { + + public EmojiEditText(Context context) { + this(context, null); + } + + public EmojiEditText(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, R.attr.editTextStyle); + } + + public EmojiEditText(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + // this ensures the view is redrawn when invalidated + setLayerType(LAYER_TYPE_SOFTWARE, null); + setFilters(new InputFilter[] {new EmojiFilter(this)}); + } + + public void insertEmoji(String emoji) { + final int start = getSelectionStart(); + final int end = getSelectionEnd(); + + getText().replace(Math.min(start, end), Math.max(start, end), emoji); + setSelection(start + emoji.length()); + } + + @Override + public void invalidateDrawable(@NonNull Drawable drawable) { + if (drawable instanceof EmojiDrawable) invalidate(); + else super.invalidateDrawable(drawable); + } +} diff --git a/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiFilter.java b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..603544869f954448a22f35059f34b61198853e17 --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiFilter.java @@ -0,0 +1,35 @@ +package org.thoughtcrime.securesms.components.emoji; + +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.text.InputFilter; +import android.text.Spannable; +import android.text.Spanned; +import android.text.TextUtils; +import android.widget.TextView; + +@UiThread +class EmojiFilter implements InputFilter { + + private final TextView view; + + public EmojiFilter(TextView view) { + this.view = view; + } + + @Nullable + @Override + public CharSequence filter(CharSequence source, int start, int end, + Spanned dest, int dstart, int dend) { + + char[] v = new char[end - start]; + TextUtils.getChars(source, start, end, v, 0); + Spannable emojified = EmojiProvider.getInstance(view.getContext()) + .emojify(new String(v), view); + if (source instanceof Spanned && emojified != null) { + TextUtils.copySpansFrom((Spanned) source, start, end, null, + emojified, 0); + } + return emojified; + } +} diff --git a/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiPageModel.java b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiPageModel.java new file mode 100644 index 0000000000000000000000000000000000000000..2e0b899d54fddb7cb928a081515a018d482469e5 --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiPageModel.java @@ -0,0 +1,19 @@ +package org.thoughtcrime.securesms.components.emoji; + +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +interface EmojiPageModel { + + @DrawableRes + int getIcon(); + + @NonNull + String[] getEmoji(); + + boolean hasSpriteMap(); + + @Nullable + String getSprite(); +} diff --git a/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiPageView.java b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiPageView.java new file mode 100644 index 0000000000000000000000000000000000000000..fa5e97c8eef6983e1fda830c524c5eb2ee3345ae --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiPageView.java @@ -0,0 +1,115 @@ +package org.thoughtcrime.securesms.components.emoji; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.BaseAdapter; +import android.widget.FrameLayout; +import android.widget.GridView; + +import org.briarproject.R; + +@UiThread +public class EmojiPageView extends FrameLayout { + + private final GridView grid; + + private EmojiSelectionListener listener; + + public EmojiPageView(Context context) { + this(context, null); + } + + public EmojiPageView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public EmojiPageView(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + final View view = LayoutInflater.from(getContext()) + .inflate(R.layout.emoji_grid_layout, this, true); + grid = (GridView) view.findViewById(R.id.emoji); + grid.setColumnWidth(getResources() + .getDimensionPixelSize(R.dimen.emoji_drawer_size) + 2 * + getResources().getDimensionPixelSize( + R.dimen.emoji_drawer_item_padding)); + grid.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, + int position, long id) { + if (listener != null) + listener.onEmojiSelected(((EmojiView) view).getEmoji()); + } + }); + } + + public void setModel(EmojiPageModel model) { + grid.setAdapter(new EmojiGridAdapter(getContext(), model)); + } + + public void setEmojiSelectedListener(EmojiSelectionListener listener) { + this.listener = listener; + } + + private static class EmojiGridAdapter extends BaseAdapter { + + private final Context context; + private final EmojiPageModel model; + private final int emojiSize; + + private EmojiGridAdapter(Context context, EmojiPageModel model) { + this.context = context; + this.model = model; + emojiSize = (int) context.getResources() + .getDimension(R.dimen.emoji_drawer_size); + } + + @Override + public int getCount() { + return model.getEmoji().length; + } + + @Nullable + @Override + public Object getItem(int position) { + return null; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + EmojiView view; + int pad = context.getResources() + .getDimensionPixelSize(R.dimen.emoji_drawer_item_padding); + if (convertView != null && convertView instanceof EmojiView) { + view = (EmojiView) convertView; + } else { + EmojiView emojiView = new EmojiView(context); + emojiView.setPadding(pad, pad, pad, pad); + emojiView.setLayoutParams( + new AbsListView.LayoutParams(emojiSize + 2 * pad, + emojiSize + 2 * pad)); + view = emojiView; + } + + view.setEmoji(model.getEmoji()[position]); + return view; + } + } + + interface EmojiSelectionListener { + void onEmojiSelected(String emoji); + } +} diff --git a/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiPages.java b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiPages.java new file mode 100644 index 0000000000000000000000000000000000000000..0544de13af91fd1374ce81b5e0033bb151b4115f --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiPages.java @@ -0,0 +1,65 @@ +package org.thoughtcrime.securesms.components.emoji; + +import android.content.Context; + +import org.briarproject.R; + +import java.util.Arrays; +import java.util.List; + +class EmojiPages { + + static List<EmojiPageModel> getPages(Context ctx) { + return Arrays.<EmojiPageModel>asList( + new StaticEmojiPageModel(ctx, R.drawable.ic_emoji_smiley_people, + R.array.emoji_smiley_people, + "emoji_smiley_people.png"), + new StaticEmojiPageModel(ctx, + R.drawable.ic_emoji_animals_nature, + R.array.emoji_animals_nature, + "emoji_animals_nature.png"), + new StaticEmojiPageModel(ctx, R.drawable.ic_emoji_food_drink, + R.array.emoji_food_drink, + "emoji_food_drink.png"), + new StaticEmojiPageModel(ctx, R.drawable.ic_emoji_travel_places, + R.array.emoji_travel_places, + "emoji_travel_places.png"), + new StaticEmojiPageModel(ctx, R.drawable.ic_emoji_activity, + R.array.emoji_activity, + "emoji_activity.png"), + new StaticEmojiPageModel(ctx, R.drawable.ic_emoji_objects, + R.array.emoji_objects, + "emoji_objects.png"), + new StaticEmojiPageModel(ctx, R.drawable.ic_emoji_symbols, + R.array.emoji_symbols, + "emoji_symbols.png"), + new StaticEmojiPageModel(ctx, R.drawable.ic_emoji_flags, + R.array.emoji_flags, + "emoji_flags.png"), + + new StaticEmojiPageModel(R.drawable.ic_emoji_emoticons, + new String[] { + ":-)", ";-)", "(-:", ":->", ":-D", "\\o/", + ":-P", "B-)", ":-$", ":-*", "O:-)", "=-O", + "O_O", "O_o", "o_O", ":O", ":-!", ":-x", + ":-|", ":-\\", ":-(", ":'(", ":-[", ">:-(", + "^.^", "^_^", "\\(\u02c6\u02da\u02c6)/", + "\u30fd(\u00b0\u25c7\u00b0 )\u30ce", + "\u00af\\(\u00b0_o)/\u00af", + "\u00af\\_(\u30c4)_/\u00af", "(\u00ac_\u00ac)", + "(>_<)", "(\u2565\ufe4f\u2565)", + "(\u261e\uff9f\u30ee\uff9f)\u261e", + "\u261c(\uff9f\u30ee\uff9f\u261c)", + "\u261c(\u2312\u25bd\u2312)\u261e", + "(\u256f\u00b0\u25a1\u00b0)\u256f\ufe35", + "\u253b\u2501\u253b", + "\u252c\u2500\u252c", + "\u30ce(\u00b0\u2013\u00b0\u30ce)", + "(^._.^)\uff89", + "\u0e05^\u2022\ufecc\u2022^\u0e05", + "(\u2022_\u2022)", + " \u25a0-\u25a0\u00ac <(\u2022_\u2022) ", + "(\u25a0_\u25a0\u00ac)" + }, null)); + } +} diff --git a/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..2ea99864e9755732f8555d27c481f06fc3436afb --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java @@ -0,0 +1,310 @@ +package org.thoughtcrime.securesms.components.emoji; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.util.SparseArray; +import android.widget.TextView; + +import org.briarproject.R; +import org.briarproject.android.BaseActivity; +import org.briarproject.android.api.AndroidExecutor; +import org.thoughtcrime.securesms.components.util.FutureTaskListener; +import org.thoughtcrime.securesms.components.util.ListenableFutureTask; +import org.thoughtcrime.securesms.util.BitmapDecodingException; +import org.thoughtcrime.securesms.util.BitmapUtil; + +import java.io.IOException; +import java.lang.ref.SoftReference; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.inject.Inject; + +import static android.graphics.PixelFormat.TRANSLUCENT; +import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE; +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; + +public class EmojiProvider { + + private static volatile EmojiProvider INSTANCE = null; + + private static final Paint PAINT = + new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG); + + @Inject + AndroidExecutor androidExecutor; + + private static final Logger LOG = + Logger.getLogger(EmojiProvider.class.getName()); + + private final SparseArray<DrawInfo> offsets = new SparseArray<>(); + + private static final Pattern EMOJI_RANGE = Pattern.compile( + // 0x203c,0x2049 0x20a0-0x32ff 0x1f00-0x1fff 0xfe4e5-0xfe4ee + // |=== !!, ?! ===||==== misc ===||========= emoticons =======||========== flags ==========| + "[\\u203c\\u2049\\u20a0-\\u32ff\\ud83c\\udc00-\\ud83f\\udfff\\udbb9\\udce5-\\udbb9\\udcee]"); + + private static final int EMOJI_RAW_HEIGHT = 64; + private static final int EMOJI_RAW_WIDTH = 64; + private static final int EMOJI_VERT_PAD = 0; + private static final int EMOJI_PER_ROW = 32; + + private final Context context; + private final float decodeScale, verticalPad; + private final List<EmojiPageModel> staticPages; + + static EmojiProvider getInstance(Context context) { + if (INSTANCE == null) { + synchronized (EmojiProvider.class) { + if (INSTANCE == null) { + LOG.info("Creating new instance of EmojiProvider"); + INSTANCE = new EmojiProvider(context); + ((BaseActivity) context).getActivityComponent() + .inject(INSTANCE); + } + } + } + return INSTANCE; + } + + private EmojiProvider(Context context) { + this.context = context.getApplicationContext(); + float drawerSize = + context.getResources().getDimension(R.dimen.emoji_drawer_size); + decodeScale = Math.min(1f, drawerSize / EMOJI_RAW_HEIGHT); + verticalPad = EMOJI_VERT_PAD * this.decodeScale; + staticPages = EmojiPages.getPages(context); + for (EmojiPageModel page : staticPages) { + if (page.hasSpriteMap()) { + final EmojiPageBitmap pageBitmap = new EmojiPageBitmap(page); + for (int i = 0; i < page.getEmoji().length; i++) { + offsets.put(Character.codePointAt(page.getEmoji()[i], 0), + new DrawInfo(pageBitmap, i)); + } + } + } + } + + @Nullable + Spannable emojify(@Nullable CharSequence text, + @NonNull TextView tv) { + if (text == null) return null; + Matcher matches = EMOJI_RANGE.matcher(text); + SpannableStringBuilder builder = new SpannableStringBuilder(text); + + while (matches.find()) { + int codePoint = matches.group().codePointAt(0); + Drawable drawable = getEmojiDrawable(codePoint); + if (drawable != null) { + builder.setSpan(new EmojiSpan(drawable, tv), matches.start(), + matches.end(), SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + return builder; + } + + @Nullable + Drawable getEmojiDrawable(int emojiCode) { + return getEmojiDrawable(offsets.get(emojiCode)); + } + + @Nullable + private Drawable getEmojiDrawable(DrawInfo drawInfo) { + if (drawInfo == null) { + return null; + } + + final EmojiDrawable drawable = new EmojiDrawable(drawInfo, decodeScale); + drawInfo.page.get().addListener(new FutureTaskListener<Bitmap>() { + @Override + public void onSuccess(final Bitmap result) { + androidExecutor.runOnUiThread(new Runnable() { + @Override + public void run() { + drawable.setBitmap(result); + } + }); + } + + @Override + public void onFailure(Throwable error) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, error.toString(), error); + } + }); + return drawable; + } + + List<EmojiPageModel> getStaticPages() { + return staticPages; + } + + + class EmojiDrawable extends Drawable { + + private final DrawInfo info; + private final float intrinsicWidth, intrinsicHeight; + + private Bitmap bmp; + + private EmojiDrawable(DrawInfo info, float decodeScale) { + this.info = info; + intrinsicWidth = EMOJI_RAW_WIDTH * decodeScale; + intrinsicHeight = EMOJI_RAW_HEIGHT * decodeScale; + } + + @Override + public int getIntrinsicWidth() { + return (int) intrinsicWidth; + } + + @Override + public int getIntrinsicHeight() { + return (int) intrinsicHeight; + } + + @Override + public void draw(@NonNull Canvas canvas) { + if (bmp == null) { + return; + } + + int row = info.index / EMOJI_PER_ROW; + int rowIndex = info.index % EMOJI_PER_ROW; + + int left = (int) (rowIndex * intrinsicWidth); + int top = (int) (row * intrinsicHeight + row * verticalPad); + int right = (int) ((rowIndex + 1) * intrinsicWidth); + int bottom = + (int) ((row + 1) * intrinsicHeight + row * verticalPad); + canvas.drawBitmap(bmp, new Rect(left, top, right, bottom), + getBounds(), PAINT); + } + + void setBitmap(Bitmap bitmap) { + if (bmp == null || !bmp.sameAs(bitmap)) { + bmp = bitmap; + invalidateSelf(); + } + } + + @Override + public int getOpacity() { + return TRANSLUCENT; + } + + @Override + public void setAlpha(int alpha) { + } + + @Override + public void setColorFilter(ColorFilter cf) { + } + } + + + private static class DrawInfo { + + private final EmojiPageBitmap page; + private final int index; + + private DrawInfo(EmojiPageBitmap page, int index) { + this.page = page; + this.index = index; + } + + @Override + public String toString() { + return "DrawInfo{ " + "page = " + page + ", index = " + index + '}'; + } + } + + private class EmojiPageBitmap { + + private final EmojiPageModel model; + + private ListenableFutureTask<Bitmap> task; + + private volatile SoftReference<Bitmap> bitmapReference; + + private EmojiPageBitmap(EmojiPageModel model) { + this.model = model; + } + + private ListenableFutureTask<Bitmap> get() { + if (bitmapReference != null && bitmapReference.get() != null) { + return new ListenableFutureTask<>(bitmapReference.get()); + } else if (task != null) { + return task; + } else { + Callable<Bitmap> callable = new Callable<Bitmap>() { + @Override + @Nullable + public Bitmap call() throws Exception { + try { + if (LOG.isLoggable(INFO)) + LOG.info("Loading page " + model.getSprite()); + return loadPage(); + } catch (IOException ioe) { + LOG.log(WARNING, ioe.toString(), ioe); + } + return null; + } + }; + task = new ListenableFutureTask<>(callable); + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + task.run(); + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + task = null; + } + }.execute(); + } + return task; + } + + private Bitmap loadPage() throws IOException { + if (bitmapReference != null && bitmapReference.get() != null) + return bitmapReference.get(); + + try { + final Bitmap bitmap = BitmapUtil.createScaledBitmap(context, + "file:///android_asset/" + model.getSprite(), + decodeScale); + bitmapReference = new SoftReference<>(bitmap); + if (LOG.isLoggable(INFO)) + LOG.info("Loaded page " + model.getSprite()); + return bitmap; + } catch (BitmapDecodingException e) { + LOG.log(WARNING, e.toString(), e); + throw new IOException(e); + } + } + + @Nullable + @Override + public String toString() { + return model.getSprite(); + } + } + +} diff --git a/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiSpan.java b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiSpan.java new file mode 100644 index 0000000000000000000000000000000000000000..6168ee2733501703687499453ff3f0725a8679ff --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiSpan.java @@ -0,0 +1,40 @@ +package org.thoughtcrime.securesms.components.emoji; + +import android.graphics.Paint; +import android.graphics.Paint.FontMetricsInt; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.annotation.UiThread; +import android.widget.TextView; + +import org.briarproject.R; + +@UiThread +class EmojiSpan extends AnimatingImageSpan { + + private final int size; + private final FontMetricsInt fm; + + EmojiSpan(@NonNull Drawable drawable, @NonNull TextView tv) { + super(drawable, tv); + fm = tv.getPaint().getFontMetricsInt(); + size = fm != null ? Math.abs(fm.descent) + Math.abs(fm.ascent) + : tv.getResources().getDimensionPixelSize( + R.dimen.conversation_item_body_text_size); + getDrawable().setBounds(0, 0, size, size); + } + + @Override + public int getSize(Paint paint, CharSequence text, int start, int end, + FontMetricsInt fm) { + if (fm != null && this.fm != null) { + fm.ascent = this.fm.ascent; + fm.descent = this.fm.descent; + fm.top = this.fm.top; + fm.bottom = this.fm.bottom; + return size; + } else { + return super.getSize(paint, text, start, end, fm); + } + } +} diff --git a/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java new file mode 100644 index 0000000000000000000000000000000000000000..7d10a6393df448b2655151033c635c8e277a7f39 --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java @@ -0,0 +1,95 @@ +package org.thoughtcrime.securesms.components.emoji; + +import android.content.Context; +import android.graphics.Paint.FontMetricsInt; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.widget.TextView; + +import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable; + +import static android.text.TextUtils.TruncateAt.END; +import static android.view.View.MeasureSpec.AT_MOST; +import static android.view.View.MeasureSpec.EXACTLY; +import static android.widget.TextView.BufferType.SPANNABLE; + +@UiThread +public class EmojiTextView extends TextView { + + private CharSequence source; + private boolean needsEllipsizing; + + public EmojiTextView(Context context) { + this(context, null); + } + + public EmojiTextView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public EmojiTextView(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + // this ensures the view is redrawn when invalidated + setLayerType(LAYER_TYPE_SOFTWARE, null); + } + + @Override + public void setText(@Nullable CharSequence text, BufferType type) { + source = EmojiProvider.getInstance(getContext()).emojify(text, this); + setTextEllipsized(source); + } + + private void setTextEllipsized(final @Nullable CharSequence source) { + super.setText(needsEllipsizing ? ellipsize(source) : source, SPANNABLE); + } + + @Override + public void invalidateDrawable(@NonNull Drawable drawable) { + if (drawable instanceof EmojiDrawable) invalidate(); + else super.invalidateDrawable(drawable); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int size = MeasureSpec.getSize(widthMeasureSpec); + final int mode = MeasureSpec.getMode(widthMeasureSpec); + if (getEllipsize() == END && + !TextUtils.isEmpty(source) && + (mode == AT_MOST || mode == EXACTLY) && + getPaint().breakText(source, 0, source.length() - 1, true, size, + null) != source.length()) { + needsEllipsizing = true; + FontMetricsInt font = getPaint().getFontMetricsInt(); + int height = Math.abs(font.top - font.bottom); + super.onMeasure(MeasureSpec.makeMeasureSpec(size, EXACTLY), + MeasureSpec.makeMeasureSpec(height, EXACTLY)); + } else { + needsEllipsizing = false; + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, + int bottom) { + if (changed) setTextEllipsized(source); + super.onLayout(changed, left, top, right, bottom); + } + + @Nullable + public CharSequence ellipsize(@Nullable CharSequence text) { + if (TextUtils.isEmpty(text) || getWidth() == 0 || + getEllipsize() != END) { + return text; + } else { + return TextUtils.ellipsize(text, getPaint(), + getWidth() - getPaddingRight() - getPaddingLeft(), END); + } + } + +} diff --git a/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiToggle.java b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiToggle.java new file mode 100644 index 0000000000000000000000000000000000000000..d1f47224320cd306a5a68ab2ed88a0e562f44697 --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiToggle.java @@ -0,0 +1,60 @@ +package org.thoughtcrime.securesms.components.emoji; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.support.v4.content.ContextCompat; +import android.util.AttributeSet; +import android.widget.ImageButton; + +import org.briarproject.R; +import org.thoughtcrime.securesms.components.emoji.EmojiDrawer.EmojiDrawerListener; + +@UiThread +public class EmojiToggle extends ImageButton implements EmojiDrawerListener { + + private final Drawable emojiToggle; + private final Drawable imeToggle; + + public EmojiToggle(Context context) { + this(context, null); + } + + public EmojiToggle(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public EmojiToggle(Context context, @Nullable AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + + emojiToggle = ContextCompat + .getDrawable(getContext(), R.drawable.ic_emoji_toggle); + imeToggle = ContextCompat + .getDrawable(getContext(), R.drawable.ic_keyboard_black); + setToEmoji(); + } + + public void setToEmoji() { + setImageDrawable(emojiToggle); + } + + public void setToIme() { + setImageDrawable(imeToggle); + } + + public void attach(EmojiDrawer drawer) { + drawer.setDrawerListener(this); + } + + @Override + public void onShown() { + setToIme(); + } + + @Override + public void onHidden() { + setToEmoji(); + } +} diff --git a/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiView.java b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiView.java new file mode 100644 index 0000000000000000000000000000000000000000..c4f064d2ad9e5e2300d7730323ceb80934d30202 --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/components/emoji/EmojiView.java @@ -0,0 +1,88 @@ +package org.thoughtcrime.securesms.components.emoji; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.support.v4.content.ContextCompat; +import android.util.AttributeSet; +import android.view.View; + +import org.briarproject.R; + +import static android.graphics.Paint.ANTI_ALIAS_FLAG; +import static android.graphics.Paint.Align.CENTER; +import static android.graphics.Paint.FILTER_BITMAP_FLAG; + +@UiThread +public class EmojiView extends View implements Drawable.Callback { + + private final Paint paint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG); + + private String emoji; + private Drawable drawable; + + public EmojiView(Context context) { + this(context, null); + } + + public EmojiView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public EmojiView(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void setEmoji(String emoji) { + this.emoji = emoji; + this.drawable = EmojiProvider.getInstance(getContext()) + .getEmojiDrawable(Character.codePointAt(emoji, 0)); + postInvalidate(); + } + + public String getEmoji() { + return emoji; + } + + @Override + protected void onDraw(Canvas canvas) { + if (drawable != null) { + drawable.setBounds(getPaddingLeft(), + getPaddingTop(), + getWidth() - getPaddingRight(), + getHeight() - getPaddingBottom()); + drawable.setCallback(this); + drawable.draw(canvas); + } else { + float targetFontSize = + 0.75f * getHeight() - getPaddingTop() - getPaddingBottom(); + paint.setTextSize(targetFontSize); + paint.setColor(ContextCompat + .getColor(getContext(), R.color.emoji_text_color)); + paint.setTextAlign(CENTER); + int xPos = (canvas.getWidth() / 2); + int yPos = (int) ((canvas.getHeight() / 2) - + ((paint.descent() + paint.ascent()) / 2)); + + float overflow = paint.measureText(emoji) / + (getWidth() - getPaddingLeft() - getPaddingRight()); + if (overflow > 1f) { + paint.setTextSize(targetFontSize / overflow); + yPos = (int) ((canvas.getHeight() / 2) - + ((paint.descent() + paint.ascent()) / 2)); + } + canvas.drawText(emoji, xPos, yPos, paint); + } + } + + @Override + public void invalidateDrawable(@NonNull Drawable drawable) { + super.invalidateDrawable(drawable); + postInvalidate(); + } +} diff --git a/briar-android/src/org/thoughtcrime/securesms/components/emoji/RecentEmojiPageModel.java b/briar-android/src/org/thoughtcrime/securesms/components/emoji/RecentEmojiPageModel.java new file mode 100644 index 0000000000000000000000000000000000000000..1c16d9c87732d8b3c1101f375bcba91fdab2d287 --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/components/emoji/RecentEmojiPageModel.java @@ -0,0 +1,140 @@ +package org.thoughtcrime.securesms.components.emoji; + +import android.content.Context; +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; + +import org.briarproject.R; +import org.briarproject.android.BaseActivity; +import org.briarproject.android.controller.DbController; +import org.briarproject.api.db.DbException; +import org.briarproject.api.settings.Settings; +import org.briarproject.api.settings.SettingsManager; + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static java.util.logging.Level.WARNING; +import static org.briarproject.android.fragment.SettingsFragment.SETTINGS_NAMESPACE; + +@UiThread +public class RecentEmojiPageModel implements EmojiPageModel { + + private static final Logger LOG = + Logger.getLogger(RecentEmojiPageModel.class.getName()); + + private static final String EMOJI_LRU_PREFERENCE = "pref_emoji_recent"; + private static final int EMOJI_LRU_SIZE = 50; + + private final LinkedHashSet<String> recentlyUsed; + private Settings settings; + + @Inject + SettingsManager settingsManager; + @Inject + DbController dbController; + + RecentEmojiPageModel(Context context) { + if (!(context instanceof BaseActivity)) { + throw new IllegalArgumentException( + "Needs to be created from BaseActivity"); + } + ((BaseActivity) context).getActivityComponent().inject(this); + recentlyUsed = getPersistedCache(); + } + + private LinkedHashSet<String> getPersistedCache() { + String serialized; + try { + settings = settingsManager.getSettings(SETTINGS_NAMESPACE); + serialized = settings.get(EMOJI_LRU_PREFERENCE); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + serialized = null; + } + return deserialize(serialized); + } + + @DrawableRes + @Override + public int getIcon() { + return R.drawable.ic_emoji_recent; + } + + @NonNull + @Override + public String[] getEmoji() { + return toReversePrimitiveArray(recentlyUsed); + } + + @Override + public boolean hasSpriteMap() { + return false; + } + + @Override + public String getSprite() { + return null; + } + + void onCodePointSelected(String emoji) { + recentlyUsed.remove(emoji); + recentlyUsed.add(emoji); + + if (recentlyUsed.size() > EMOJI_LRU_SIZE) { + Iterator<String> iterator = recentlyUsed.iterator(); + iterator.next(); + iterator.remove(); + } + save(recentlyUsed); + } + + private String serialize(LinkedHashSet<String> emojis) { + String result = ""; + for (String emoji : emojis) { + result += emoji + ";"; + } + if (!emojis.isEmpty()) + result = result.substring(0, result.length() - 1); + return result; + } + + private LinkedHashSet<String> deserialize(@Nullable String str) { + String[] list = str == null ? new String[] {} : str.split(";"); + LinkedHashSet<String> result = new LinkedHashSet<>(list.length); + Collections.addAll(result, list); + return result; + } + + private void save(final LinkedHashSet<String> recentlyUsed) { + dbController.runOnDbThread(new Runnable() { + @Override + public void run() { + String serialized = serialize(recentlyUsed); + settings.put(EMOJI_LRU_PREFERENCE, serialized); + try { + settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + } + } + }); + } + + private String[] toReversePrimitiveArray( + @NonNull LinkedHashSet<String> emojiSet) { + String[] emojis = new String[emojiSet.size()]; + int i = emojiSet.size() - 1; + for (String emoji : emojiSet) { + emojis[i--] = emoji; + } + return emojis; + } +} diff --git a/briar-android/src/org/thoughtcrime/securesms/components/emoji/StaticEmojiPageModel.java b/briar-android/src/org/thoughtcrime/securesms/components/emoji/StaticEmojiPageModel.java new file mode 100644 index 0000000000000000000000000000000000000000..688ee3ae7a4f778c4c79bd30e3b8616679252082 --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/components/emoji/StaticEmojiPageModel.java @@ -0,0 +1,73 @@ +package org.thoughtcrime.securesms.components.emoji; + +import android.content.Context; +import android.support.annotation.ArrayRes; +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; + +@UiThread +class StaticEmojiPageModel implements EmojiPageModel { + + @DrawableRes + private final int icon; + @NonNull + private final String[] emoji; + @Nullable + private final String sprite; + + StaticEmojiPageModel(@DrawableRes int icon, @NonNull String[] emoji, + @Nullable String sprite) { + this.icon = icon; + this.emoji = emoji; + this.sprite = sprite; + } + + StaticEmojiPageModel(Context ctx, @DrawableRes int icon, + @ArrayRes int res, @Nullable String sprite) { + this(icon, getEmoji(ctx, res), sprite); + } + + @DrawableRes + @Override + public int getIcon() { + return icon; + } + + @Override + @NonNull + public String[] getEmoji() { + return emoji; + } + + @Override + public boolean hasSpriteMap() { + return sprite != null; + } + + @Nullable + @Override + public String getSprite() { + return sprite; + } + + @NonNull + private static String[] getEmoji(Context ctx, @ArrayRes int res) { + String[] rawStrings = ctx.getResources().getStringArray(res); + String[] emoji = new String[rawStrings.length]; + int i = 0; + for (String codePoint : rawStrings) { + String[] bytes = codePoint.split(","); + int[] codePoints = new int[bytes.length]; + int j = 0; + for (String b : bytes) { + codePoints[j] = Integer.valueOf(b, 16); + } + emoji[i] = new String(codePoints, 0, codePoints.length); + i++; + } + return emoji; + } + +} diff --git a/briar-android/src/org/thoughtcrime/securesms/components/util/FutureTaskListener.java b/briar-android/src/org/thoughtcrime/securesms/components/util/FutureTaskListener.java new file mode 100644 index 0000000000000000000000000000000000000000..80da2eccf6420c4c9f597eccbba39035cb0b7759 --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/components/util/FutureTaskListener.java @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2014 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.thoughtcrime.securesms.components.util; + +public interface FutureTaskListener<V> { + void onSuccess(V result); + + void onFailure(Throwable error); +} diff --git a/briar-android/src/org/thoughtcrime/securesms/components/util/ListenableFutureTask.java b/briar-android/src/org/thoughtcrime/securesms/components/util/ListenableFutureTask.java new file mode 100644 index 0000000000000000000000000000000000000000..1283148846464c4e8098a3cca1dc16b0a1eb10ad --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/components/util/ListenableFutureTask.java @@ -0,0 +1,110 @@ +/** + * Copyright (C) 2014 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.thoughtcrime.securesms.components.util; + +import android.support.annotation.Nullable; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +public class ListenableFutureTask<V> extends FutureTask<V> { + + private final List<FutureTaskListener<V>> listeners = new LinkedList<>(); + + @Nullable + private final Object identifier; + + public ListenableFutureTask(Callable<V> callable) { + this(callable, null); + } + + private ListenableFutureTask(Callable<V> callable, + @Nullable Object identifier) { + super(callable); + this.identifier = identifier; + } + + public ListenableFutureTask(final V result) { + this(result, null); + } + + private ListenableFutureTask(final V result, @Nullable Object identifier) { + super(new Callable<V>() { + @Override + public V call() throws Exception { + return result; + } + }); + this.identifier = identifier; + this.run(); + } + + public synchronized void addListener(FutureTaskListener<V> listener) { + if (this.isDone()) { + callback(listener); + } else { + this.listeners.add(listener); + } + } + + public synchronized void removeListener(FutureTaskListener<V> listener) { + this.listeners.remove(listener); + } + + @Override + protected synchronized void done() { + callback(); + } + + private void callback() { + for (FutureTaskListener<V> listener : listeners) { + callback(listener); + } + } + + private void callback(FutureTaskListener<V> listener) { + if (listener != null) { + try { + listener.onSuccess(get()); + } catch (InterruptedException e) { + throw new AssertionError(e); + } catch (ExecutionException e) { + listener.onFailure(e); + } + } + } + + @Override + public boolean equals(Object other) { + if (other != null && other instanceof ListenableFutureTask && + this.identifier != null) { + return identifier.equals(other); + } else { + return super.equals(other); + } + } + + @Override + public int hashCode() { + if (identifier != null) return identifier.hashCode(); + else return super.hashCode(); + } + +} diff --git a/briar-android/src/org/thoughtcrime/securesms/util/BitmapDecodingException.java b/briar-android/src/org/thoughtcrime/securesms/util/BitmapDecodingException.java new file mode 100644 index 0000000000000000000000000000000000000000..777fdfb2ead9fe8cc8c140432cbc35ff522281c4 --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/util/BitmapDecodingException.java @@ -0,0 +1,12 @@ +package org.thoughtcrime.securesms.util; + +public class BitmapDecodingException extends Exception { + + BitmapDecodingException(String s) { + super(s); + } + + BitmapDecodingException(Exception nested) { + super(nested); + } +} diff --git a/briar-android/src/org/thoughtcrime/securesms/util/BitmapUtil.java b/briar-android/src/org/thoughtcrime/securesms/util/BitmapUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..0bc0ed00f95d93721f923863bc475303fb086f01 --- /dev/null +++ b/briar-android/src/org/thoughtcrime/securesms/util/BitmapUtil.java @@ -0,0 +1,95 @@ +package org.thoughtcrime.securesms.util; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.Pair; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.Priority; +import com.bumptech.glide.load.DecodeFormat; +import com.bumptech.glide.load.engine.Resource; +import com.bumptech.glide.load.resource.bitmap.BitmapResource; +import com.bumptech.glide.load.resource.bitmap.Downsampler; +import com.bumptech.glide.load.resource.bitmap.FitCenter; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Logger; + +import static java.util.logging.Level.WARNING; + +public class BitmapUtil { + + private static final Logger LOG = + Logger.getLogger(BitmapUtil.class.getName()); + + private static <T> InputStream getInputStreamForModel(Context context, + T model) + throws BitmapDecodingException { + try { + return Glide.buildStreamModelLoader(model, context) + .getResourceFetcher(model, -1, -1) + .loadData(Priority.NORMAL); + } catch (Exception e) { + throw new BitmapDecodingException(e); + } + } + + private static <T> Bitmap createScaledBitmapInto(Context context, T model, + int width, int height) + throws BitmapDecodingException { + final Bitmap rough = Downsampler.AT_LEAST + .decode(getInputStreamForModel(context, model), + Glide.get(context).getBitmapPool(), + width, height, DecodeFormat.PREFER_RGB_565); + + final Resource<Bitmap> resource = BitmapResource + .obtain(rough, Glide.get(context).getBitmapPool()); + final Resource<Bitmap> result = + new FitCenter(context).transform(resource, width, height); + + if (result == null) { + throw new BitmapDecodingException("unable to transform Bitmap"); + } + return result.get(); + } + + public static <T> Bitmap createScaledBitmap(Context context, T model, + float scale) throws BitmapDecodingException { + Pair<Integer, Integer> dimens = + getDimensions(getInputStreamForModel(context, model)); + return createScaledBitmapInto(context, model, + (int) (dimens.first * scale), (int) (dimens.second * scale)); + } + + private static BitmapFactory.Options getImageDimensions( + InputStream inputStream) + throws BitmapDecodingException { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BufferedInputStream fis = new BufferedInputStream(inputStream); + BitmapFactory.decodeStream(fis, null, options); + try { + fis.close(); + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + + if (options.outWidth == -1 || options.outHeight == -1) { + throw new BitmapDecodingException( + "Failed to decode image dimensions: " + options.outWidth + + ", " + options.outHeight); + } + + return options; + } + + private static Pair<Integer, Integer> getDimensions(InputStream inputStream) + throws BitmapDecodingException { + BitmapFactory.Options options = getImageDimensions(inputStream); + return new Pair<>(options.outWidth, options.outHeight); + } + +} diff --git a/briar-android/test/java/org/briarproject/android/forum/ForumActivityTest.java b/briar-android/test/java/org/briarproject/android/forum/ForumActivityTest.java index 38f64c5b25241af34913ec05aa92559f324a3add..e61b557beb392dbfedac9dd9620f0fd5ade0417b 100644 --- a/briar-android/test/java/org/briarproject/android/forum/ForumActivityTest.java +++ b/briar-android/test/java/org/briarproject/android/forum/ForumActivityTest.java @@ -29,6 +29,8 @@ import java.util.List; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; +import static org.briarproject.api.identity.Author.Status.UNKNOWN; +import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -77,11 +79,12 @@ public class ForumActivityTest { private List<ForumEntry> getDummyData() { ForumEntry[] forumEntries = new ForumEntry[6]; for (int i = 0; i < forumEntries.length; i++) { - forumEntries[i] = - new ForumEntry(new MessageId(TestUtils.getRandomId()), - AUTHORS[i], LEVELS[i], System.currentTimeMillis(), - AUTHORS[i], new AuthorId(TestUtils.getRandomId()), - Author.Status.UNKNOWN); + AuthorId authorId = new AuthorId(TestUtils.getRandomId()); + byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH); + Author author = new Author(authorId, AUTHORS[i], publicKey); + forumEntries[i] = new ForumEntry( + new MessageId(TestUtils.getRandomId()), AUTHORS[i], + LEVELS[i], System.currentTimeMillis(), author, UNKNOWN); } return new ArrayList<>(Arrays.asList(forumEntries)); } diff --git a/build.gradle b/build.gradle index ab0a691b0e22064db01ab17e8ee05bae150267f6..6896cc0d6878db512b32a8e07e218298fc0560cf 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.1.3' + classpath 'com.android.tools.build:gradle:2.2.0' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' classpath 'de.undercouch:gradle-download-task:2.1.0' classpath files('briar-core/libs/gradle-witness.jar')