diff --git a/assets/emoji/1f601.png b/assets/emoji/1f601.png deleted file mode 100644 index 591cfcef8b..0000000000 Binary files a/assets/emoji/1f601.png and /dev/null differ diff --git a/assets/emoji/1f602.png b/assets/emoji/1f602.png deleted file mode 100644 index 47df693d42..0000000000 Binary files a/assets/emoji/1f602.png and /dev/null differ diff --git a/assets/emoji/1f603.png b/assets/emoji/1f603.png deleted file mode 100644 index 77b581d68f..0000000000 Binary files a/assets/emoji/1f603.png and /dev/null differ diff --git a/assets/emoji/1f604.png b/assets/emoji/1f604.png deleted file mode 100644 index 81a8396899..0000000000 Binary files a/assets/emoji/1f604.png and /dev/null differ diff --git a/assets/emoji/1f605.png b/assets/emoji/1f605.png deleted file mode 100644 index 3903f717f3..0000000000 Binary files a/assets/emoji/1f605.png and /dev/null differ diff --git a/assets/emoji/1f606.png b/assets/emoji/1f606.png deleted file mode 100644 index 11c91eb22e..0000000000 Binary files a/assets/emoji/1f606.png and /dev/null differ diff --git a/assets/emoji/1f609.png b/assets/emoji/1f609.png deleted file mode 100644 index 756766dd3e..0000000000 Binary files a/assets/emoji/1f609.png and /dev/null differ diff --git a/assets/emoji/1f60a.png b/assets/emoji/1f60a.png deleted file mode 100644 index 1e9021cb6f..0000000000 Binary files a/assets/emoji/1f60a.png and /dev/null differ diff --git a/assets/emoji/1f60b.png b/assets/emoji/1f60b.png deleted file mode 100644 index fc39637ecd..0000000000 Binary files a/assets/emoji/1f60b.png and /dev/null differ diff --git a/assets/emoji/1f60c.png b/assets/emoji/1f60c.png deleted file mode 100644 index 820cf315a1..0000000000 Binary files a/assets/emoji/1f60c.png and /dev/null differ diff --git a/assets/emoji/1f60d.png b/assets/emoji/1f60d.png deleted file mode 100644 index 0e5794270e..0000000000 Binary files a/assets/emoji/1f60d.png and /dev/null differ diff --git a/assets/emoji/1f60f.png b/assets/emoji/1f60f.png deleted file mode 100644 index bc6e5082c8..0000000000 Binary files a/assets/emoji/1f60f.png and /dev/null differ diff --git a/assets/emoji/1f612.png b/assets/emoji/1f612.png deleted file mode 100644 index 3722e6f575..0000000000 Binary files a/assets/emoji/1f612.png and /dev/null differ diff --git a/assets/emoji/1f613.png b/assets/emoji/1f613.png deleted file mode 100644 index e894b76996..0000000000 Binary files a/assets/emoji/1f613.png and /dev/null differ diff --git a/assets/emoji/1f614.png b/assets/emoji/1f614.png deleted file mode 100644 index 2f3bad9453..0000000000 Binary files a/assets/emoji/1f614.png and /dev/null differ diff --git a/assets/emoji/1f616.png b/assets/emoji/1f616.png deleted file mode 100644 index a5877a0a79..0000000000 Binary files a/assets/emoji/1f616.png and /dev/null differ diff --git a/assets/emoji/1f618.png b/assets/emoji/1f618.png deleted file mode 100644 index af9a80b7f0..0000000000 Binary files a/assets/emoji/1f618.png and /dev/null differ diff --git a/assets/emoji/1f61a.png b/assets/emoji/1f61a.png deleted file mode 100644 index 449de19704..0000000000 Binary files a/assets/emoji/1f61a.png and /dev/null differ diff --git a/assets/emoji/1f61c.png b/assets/emoji/1f61c.png deleted file mode 100644 index 6ae9d497d3..0000000000 Binary files a/assets/emoji/1f61c.png and /dev/null differ diff --git a/assets/emoji/1f61d.png b/assets/emoji/1f61d.png deleted file mode 100644 index 333716ee1f..0000000000 Binary files a/assets/emoji/1f61d.png and /dev/null differ diff --git a/assets/emoji/1f61e.png b/assets/emoji/1f61e.png deleted file mode 100644 index 8255200871..0000000000 Binary files a/assets/emoji/1f61e.png and /dev/null differ diff --git a/assets/emoji/1f620.png b/assets/emoji/1f620.png deleted file mode 100644 index 34174f5e5c..0000000000 Binary files a/assets/emoji/1f620.png and /dev/null differ diff --git a/assets/emoji/1f621.png b/assets/emoji/1f621.png deleted file mode 100644 index c65ddff552..0000000000 Binary files a/assets/emoji/1f621.png and /dev/null differ diff --git a/assets/emoji/1f622.png b/assets/emoji/1f622.png deleted file mode 100644 index 6d0d9afd28..0000000000 Binary files a/assets/emoji/1f622.png and /dev/null differ diff --git a/assets/emoji/1f623.png b/assets/emoji/1f623.png deleted file mode 100644 index c7e433e8ec..0000000000 Binary files a/assets/emoji/1f623.png and /dev/null differ diff --git a/assets/emoji/1f624.png b/assets/emoji/1f624.png deleted file mode 100644 index 92f93bd102..0000000000 Binary files a/assets/emoji/1f624.png and /dev/null differ diff --git a/assets/emoji/1f625.png b/assets/emoji/1f625.png deleted file mode 100644 index fa5f9e7f9f..0000000000 Binary files a/assets/emoji/1f625.png and /dev/null differ diff --git a/assets/emoji/1f628.png b/assets/emoji/1f628.png deleted file mode 100644 index 513fce47b6..0000000000 Binary files a/assets/emoji/1f628.png and /dev/null differ diff --git a/assets/emoji/1f629.png b/assets/emoji/1f629.png deleted file mode 100644 index 0c5475411c..0000000000 Binary files a/assets/emoji/1f629.png and /dev/null differ diff --git a/assets/emoji/1f62a.png b/assets/emoji/1f62a.png deleted file mode 100644 index df4f55efd9..0000000000 Binary files a/assets/emoji/1f62a.png and /dev/null differ diff --git a/assets/emoji/1f62b.png b/assets/emoji/1f62b.png deleted file mode 100644 index 3a8eefe565..0000000000 Binary files a/assets/emoji/1f62b.png and /dev/null differ diff --git a/assets/emoji/1f62d.png b/assets/emoji/1f62d.png deleted file mode 100644 index 7d433183aa..0000000000 Binary files a/assets/emoji/1f62d.png and /dev/null differ diff --git a/assets/emoji/1f630.png b/assets/emoji/1f630.png deleted file mode 100644 index b9e39bc60f..0000000000 Binary files a/assets/emoji/1f630.png and /dev/null differ diff --git a/assets/emoji/1f631.png b/assets/emoji/1f631.png deleted file mode 100644 index 76bfc6b8a6..0000000000 Binary files a/assets/emoji/1f631.png and /dev/null differ diff --git a/assets/emoji/1f632.png b/assets/emoji/1f632.png deleted file mode 100644 index 858a83484a..0000000000 Binary files a/assets/emoji/1f632.png and /dev/null differ diff --git a/assets/emoji/1f633.png b/assets/emoji/1f633.png deleted file mode 100644 index 9b49410c0c..0000000000 Binary files a/assets/emoji/1f633.png and /dev/null differ diff --git a/assets/emoji/1f635.png b/assets/emoji/1f635.png deleted file mode 100644 index 8001d6ff8f..0000000000 Binary files a/assets/emoji/1f635.png and /dev/null differ diff --git a/assets/emoji/1f637.png b/assets/emoji/1f637.png deleted file mode 100644 index 05887e99c6..0000000000 Binary files a/assets/emoji/1f637.png and /dev/null differ diff --git a/assets/emoji/1f638.png b/assets/emoji/1f638.png deleted file mode 100644 index ad333ba3b6..0000000000 Binary files a/assets/emoji/1f638.png and /dev/null differ diff --git a/assets/emoji/1f639.png b/assets/emoji/1f639.png deleted file mode 100644 index 6c60cb0efc..0000000000 Binary files a/assets/emoji/1f639.png and /dev/null differ diff --git a/assets/emoji/1f63a.png b/assets/emoji/1f63a.png deleted file mode 100644 index dbf1b0276a..0000000000 Binary files a/assets/emoji/1f63a.png and /dev/null differ diff --git a/assets/emoji/1f63b.png b/assets/emoji/1f63b.png deleted file mode 100644 index eeba240e53..0000000000 Binary files a/assets/emoji/1f63b.png and /dev/null differ diff --git a/assets/emoji/1f63c.png b/assets/emoji/1f63c.png deleted file mode 100644 index 351565e246..0000000000 Binary files a/assets/emoji/1f63c.png and /dev/null differ diff --git a/assets/emoji/1f63d.png b/assets/emoji/1f63d.png deleted file mode 100644 index adc62fbe3c..0000000000 Binary files a/assets/emoji/1f63d.png and /dev/null differ diff --git a/assets/emoji/1f63e.png b/assets/emoji/1f63e.png deleted file mode 100644 index 4325fd48dd..0000000000 Binary files a/assets/emoji/1f63e.png and /dev/null differ diff --git a/assets/emoji/1f63f.png b/assets/emoji/1f63f.png deleted file mode 100644 index 42d4c27cab..0000000000 Binary files a/assets/emoji/1f63f.png and /dev/null differ diff --git a/assets/emoji/1f640.png b/assets/emoji/1f640.png deleted file mode 100644 index d94cd34ff5..0000000000 Binary files a/assets/emoji/1f640.png and /dev/null differ diff --git a/assets/emoji/1f645.png b/assets/emoji/1f645.png deleted file mode 100644 index d459a35bc1..0000000000 Binary files a/assets/emoji/1f645.png and /dev/null differ diff --git a/assets/emoji/1f646.png b/assets/emoji/1f646.png deleted file mode 100644 index e8b98194ed..0000000000 Binary files a/assets/emoji/1f646.png and /dev/null differ diff --git a/assets/emoji/1f647.png b/assets/emoji/1f647.png deleted file mode 100644 index 024cb61049..0000000000 Binary files a/assets/emoji/1f647.png and /dev/null differ diff --git a/assets/emoji/1f648.png b/assets/emoji/1f648.png deleted file mode 100644 index 0890a62227..0000000000 Binary files a/assets/emoji/1f648.png and /dev/null differ diff --git a/assets/emoji/1f649.png b/assets/emoji/1f649.png deleted file mode 100644 index f97a1f9a09..0000000000 Binary files a/assets/emoji/1f649.png and /dev/null differ diff --git a/assets/emoji/1f64a.png b/assets/emoji/1f64a.png deleted file mode 100644 index 87944c4de5..0000000000 Binary files a/assets/emoji/1f64a.png and /dev/null differ diff --git a/assets/emoji/1f64b.png b/assets/emoji/1f64b.png deleted file mode 100644 index e1741a40e7..0000000000 Binary files a/assets/emoji/1f64b.png and /dev/null differ diff --git a/assets/emoji/1f64c.png b/assets/emoji/1f64c.png deleted file mode 100644 index e03142bdce..0000000000 Binary files a/assets/emoji/1f64c.png and /dev/null differ diff --git a/assets/emoji/1f64d.png b/assets/emoji/1f64d.png deleted file mode 100644 index 6f34d5e159..0000000000 Binary files a/assets/emoji/1f64d.png and /dev/null differ diff --git a/assets/emoji/1f64e.png b/assets/emoji/1f64e.png deleted file mode 100644 index c4a95c3b2a..0000000000 Binary files a/assets/emoji/1f64e.png and /dev/null differ diff --git a/assets/emoji/1f64f.png b/assets/emoji/1f64f.png deleted file mode 100644 index f86c992d5a..0000000000 Binary files a/assets/emoji/1f64f.png and /dev/null differ diff --git a/assets/emoji_0_wrapped.png b/assets/emoji_0_wrapped.png new file mode 100644 index 0000000000..579d641700 Binary files /dev/null and b/assets/emoji_0_wrapped.png differ diff --git a/assets/emoji_1_wrapped.png b/assets/emoji_1_wrapped.png new file mode 100644 index 0000000000..c91282043f Binary files /dev/null and b/assets/emoji_1_wrapped.png differ diff --git a/assets/emoji_2_wrapped.png b/assets/emoji_2_wrapped.png new file mode 100644 index 0000000000..a7b8e52d50 Binary files /dev/null and b/assets/emoji_2_wrapped.png differ diff --git a/assets/emoji_3_wrapped.png b/assets/emoji_3_wrapped.png new file mode 100644 index 0000000000..21f171a0aa Binary files /dev/null and b/assets/emoji_3_wrapped.png differ diff --git a/assets/emoji_4_wrapped.png b/assets/emoji_4_wrapped.png new file mode 100644 index 0000000000..2320a62173 Binary files /dev/null and b/assets/emoji_4_wrapped.png differ diff --git a/build.gradle b/build.gradle index c076c4c2ec..701f0316be 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,8 @@ dependencies { compile 'com.actionbarsherlock:actionbarsherlock:4.4.0@aar' compile 'com.android.support:support-v4:19.1.0' compile 'se.emilsjolander:stickylistheaders:2.2.0' - compile "com.google.android.gms:play-services:5.0.77" + compile 'com.google.android.gms:play-services:5.0.77' + compile 'com.astuetz:pagerslidingtabstrip:1.0.1' androidTestCompile 'com.squareup:fest-android:1.0.8' @@ -37,6 +38,7 @@ dependencyVerification { 'com.actionbarsherlock:actionbarsherlock:5ab04d74101f70024b222e3ff9c87bee151ec43331b4a2134b6cc08cf8565819', 'com.android.support:support-v4:3f40fa7b3a4ead01ce15dce9453b061646e7fe2e7c51cb75ca01ee1e77037f3f', 'se.emilsjolander:stickylistheaders:89146b46c96fea0e40200474a2625cda10fe94891e4128f53cdb42375091b9b6', + 'com.astuetz:pagerslidingtabstrip:f1641396732c7132a7abb837e482e5ee2b0ebb8d10813fc52bbaec2c15c184c2', 'com.google.protobuf:protobuf-java:ad9769a22989e688a46af4d3accc348cc501ced22118033230542bc916e33f0b', 'com.madgag:sc-light-jdk15on:931f39d351429fb96c2f749e7ecb1a256a8ebbf5edca7995c9cc085b94d1841d', 'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab', diff --git a/res/drawable-hdpi/ic_smiles_backspace.png b/res/drawable-hdpi/ic_smiles_backspace.png new file mode 100644 index 0000000000..9173039598 Binary files /dev/null and b/res/drawable-hdpi/ic_smiles_backspace.png differ diff --git a/res/drawable-hdpi/ic_smiles_backspace_active.png b/res/drawable-hdpi/ic_smiles_backspace_active.png new file mode 100644 index 0000000000..2fa818795b Binary files /dev/null and b/res/drawable-hdpi/ic_smiles_backspace_active.png differ diff --git a/res/drawable-hdpi/ic_smiles_bell.png b/res/drawable-hdpi/ic_smiles_bell.png new file mode 100644 index 0000000000..d018c17398 Binary files /dev/null and b/res/drawable-hdpi/ic_smiles_bell.png differ diff --git a/res/drawable-hdpi/ic_smiles_bell_active.png b/res/drawable-hdpi/ic_smiles_bell_active.png new file mode 100644 index 0000000000..98c7f66cae Binary files /dev/null and b/res/drawable-hdpi/ic_smiles_bell_active.png differ diff --git a/res/drawable-hdpi/ic_smiles_car.png b/res/drawable-hdpi/ic_smiles_car.png new file mode 100644 index 0000000000..449b32b47a Binary files /dev/null and b/res/drawable-hdpi/ic_smiles_car.png differ diff --git a/res/drawable-hdpi/ic_smiles_car_active.png b/res/drawable-hdpi/ic_smiles_car_active.png new file mode 100644 index 0000000000..3f03ef7351 Binary files /dev/null and b/res/drawable-hdpi/ic_smiles_car_active.png differ diff --git a/res/drawable-hdpi/ic_smiles_flower.png b/res/drawable-hdpi/ic_smiles_flower.png new file mode 100644 index 0000000000..8f190cb4d5 Binary files /dev/null and b/res/drawable-hdpi/ic_smiles_flower.png differ diff --git a/res/drawable-hdpi/ic_smiles_flower_active.png b/res/drawable-hdpi/ic_smiles_flower_active.png new file mode 100644 index 0000000000..ca9dd97487 Binary files /dev/null and b/res/drawable-hdpi/ic_smiles_flower_active.png differ diff --git a/res/drawable-hdpi/ic_smiles_grid.png b/res/drawable-hdpi/ic_smiles_grid.png new file mode 100644 index 0000000000..ed02b9a1e5 Binary files /dev/null and b/res/drawable-hdpi/ic_smiles_grid.png differ diff --git a/res/drawable-hdpi/ic_smiles_grid_active.png b/res/drawable-hdpi/ic_smiles_grid_active.png new file mode 100644 index 0000000000..4b1f86867b Binary files /dev/null and b/res/drawable-hdpi/ic_smiles_grid_active.png differ diff --git a/res/drawable-hdpi/ic_smiles_recent.png b/res/drawable-hdpi/ic_smiles_recent.png new file mode 100644 index 0000000000..6d032d62bc Binary files /dev/null and b/res/drawable-hdpi/ic_smiles_recent.png differ diff --git a/res/drawable-hdpi/ic_smiles_recent_active.png b/res/drawable-hdpi/ic_smiles_recent_active.png new file mode 100644 index 0000000000..756df9196f Binary files /dev/null and b/res/drawable-hdpi/ic_smiles_recent_active.png differ diff --git a/res/drawable-hdpi/ic_smiles_smile.png b/res/drawable-hdpi/ic_smiles_smile.png new file mode 100644 index 0000000000..70ee89c2b8 Binary files /dev/null and b/res/drawable-hdpi/ic_smiles_smile.png differ diff --git a/res/drawable-hdpi/ic_smiles_smile_active.png b/res/drawable-hdpi/ic_smiles_smile_active.png new file mode 100644 index 0000000000..078f863b41 Binary files /dev/null and b/res/drawable-hdpi/ic_smiles_smile_active.png differ diff --git a/res/drawable-mdpi/ic_smiles_backspace.png b/res/drawable-mdpi/ic_smiles_backspace.png new file mode 100644 index 0000000000..76076a60a0 Binary files /dev/null and b/res/drawable-mdpi/ic_smiles_backspace.png differ diff --git a/res/drawable-mdpi/ic_smiles_backspace_active.png b/res/drawable-mdpi/ic_smiles_backspace_active.png new file mode 100644 index 0000000000..23bbc4b885 Binary files /dev/null and b/res/drawable-mdpi/ic_smiles_backspace_active.png differ diff --git a/res/drawable-mdpi/ic_smiles_bell.png b/res/drawable-mdpi/ic_smiles_bell.png new file mode 100644 index 0000000000..1242d7dcd3 Binary files /dev/null and b/res/drawable-mdpi/ic_smiles_bell.png differ diff --git a/res/drawable-mdpi/ic_smiles_bell_active.png b/res/drawable-mdpi/ic_smiles_bell_active.png new file mode 100644 index 0000000000..647f84536e Binary files /dev/null and b/res/drawable-mdpi/ic_smiles_bell_active.png differ diff --git a/res/drawable-mdpi/ic_smiles_car.png b/res/drawable-mdpi/ic_smiles_car.png new file mode 100644 index 0000000000..4c9d24fcd4 Binary files /dev/null and b/res/drawable-mdpi/ic_smiles_car.png differ diff --git a/res/drawable-mdpi/ic_smiles_car_active.png b/res/drawable-mdpi/ic_smiles_car_active.png new file mode 100644 index 0000000000..f52038ef70 Binary files /dev/null and b/res/drawable-mdpi/ic_smiles_car_active.png differ diff --git a/res/drawable-mdpi/ic_smiles_flower.png b/res/drawable-mdpi/ic_smiles_flower.png new file mode 100644 index 0000000000..aece417639 Binary files /dev/null and b/res/drawable-mdpi/ic_smiles_flower.png differ diff --git a/res/drawable-mdpi/ic_smiles_flower_active.png b/res/drawable-mdpi/ic_smiles_flower_active.png new file mode 100644 index 0000000000..26026ef5d6 Binary files /dev/null and b/res/drawable-mdpi/ic_smiles_flower_active.png differ diff --git a/res/drawable-mdpi/ic_smiles_grid.png b/res/drawable-mdpi/ic_smiles_grid.png new file mode 100644 index 0000000000..3d8934c0ec Binary files /dev/null and b/res/drawable-mdpi/ic_smiles_grid.png differ diff --git a/res/drawable-mdpi/ic_smiles_grid_active.png b/res/drawable-mdpi/ic_smiles_grid_active.png new file mode 100644 index 0000000000..6bea9c24dc Binary files /dev/null and b/res/drawable-mdpi/ic_smiles_grid_active.png differ diff --git a/res/drawable-mdpi/ic_smiles_recent.png b/res/drawable-mdpi/ic_smiles_recent.png new file mode 100644 index 0000000000..41d6f8a29c Binary files /dev/null and b/res/drawable-mdpi/ic_smiles_recent.png differ diff --git a/res/drawable-mdpi/ic_smiles_recent_active.png b/res/drawable-mdpi/ic_smiles_recent_active.png new file mode 100644 index 0000000000..0f8caad5c1 Binary files /dev/null and b/res/drawable-mdpi/ic_smiles_recent_active.png differ diff --git a/res/drawable-mdpi/ic_smiles_smile.png b/res/drawable-mdpi/ic_smiles_smile.png new file mode 100644 index 0000000000..9b7c59f8ff Binary files /dev/null and b/res/drawable-mdpi/ic_smiles_smile.png differ diff --git a/res/drawable-mdpi/ic_smiles_smile_active.png b/res/drawable-mdpi/ic_smiles_smile_active.png new file mode 100644 index 0000000000..f621769578 Binary files /dev/null and b/res/drawable-mdpi/ic_smiles_smile_active.png differ diff --git a/res/drawable-xhdpi/ic_smiles_backspace.png b/res/drawable-xhdpi/ic_smiles_backspace.png new file mode 100644 index 0000000000..561f125e31 Binary files /dev/null and b/res/drawable-xhdpi/ic_smiles_backspace.png differ diff --git a/res/drawable-xhdpi/ic_smiles_backspace_active.png b/res/drawable-xhdpi/ic_smiles_backspace_active.png new file mode 100644 index 0000000000..204a9ee4cd Binary files /dev/null and b/res/drawable-xhdpi/ic_smiles_backspace_active.png differ diff --git a/res/drawable-xhdpi/ic_smiles_bell.png b/res/drawable-xhdpi/ic_smiles_bell.png new file mode 100644 index 0000000000..d299db86b3 Binary files /dev/null and b/res/drawable-xhdpi/ic_smiles_bell.png differ diff --git a/res/drawable-xhdpi/ic_smiles_bell_active.png b/res/drawable-xhdpi/ic_smiles_bell_active.png new file mode 100644 index 0000000000..075fc5fa9c Binary files /dev/null and b/res/drawable-xhdpi/ic_smiles_bell_active.png differ diff --git a/res/drawable-xhdpi/ic_smiles_car.png b/res/drawable-xhdpi/ic_smiles_car.png new file mode 100644 index 0000000000..0c8519f118 Binary files /dev/null and b/res/drawable-xhdpi/ic_smiles_car.png differ diff --git a/res/drawable-xhdpi/ic_smiles_car_active.png b/res/drawable-xhdpi/ic_smiles_car_active.png new file mode 100644 index 0000000000..3bae05c067 Binary files /dev/null and b/res/drawable-xhdpi/ic_smiles_car_active.png differ diff --git a/res/drawable-xhdpi/ic_smiles_flower.png b/res/drawable-xhdpi/ic_smiles_flower.png new file mode 100644 index 0000000000..c5643d4fd2 Binary files /dev/null and b/res/drawable-xhdpi/ic_smiles_flower.png differ diff --git a/res/drawable-xhdpi/ic_smiles_flower_active.png b/res/drawable-xhdpi/ic_smiles_flower_active.png new file mode 100644 index 0000000000..fc5082c78d Binary files /dev/null and b/res/drawable-xhdpi/ic_smiles_flower_active.png differ diff --git a/res/drawable-xhdpi/ic_smiles_grid.png b/res/drawable-xhdpi/ic_smiles_grid.png new file mode 100644 index 0000000000..550d0016cd Binary files /dev/null and b/res/drawable-xhdpi/ic_smiles_grid.png differ diff --git a/res/drawable-xhdpi/ic_smiles_grid_active.png b/res/drawable-xhdpi/ic_smiles_grid_active.png new file mode 100644 index 0000000000..4d3758fc64 Binary files /dev/null and b/res/drawable-xhdpi/ic_smiles_grid_active.png differ diff --git a/res/drawable-xhdpi/ic_smiles_recent.png b/res/drawable-xhdpi/ic_smiles_recent.png new file mode 100644 index 0000000000..69f4c19ac3 Binary files /dev/null and b/res/drawable-xhdpi/ic_smiles_recent.png differ diff --git a/res/drawable-xhdpi/ic_smiles_recent_active.png b/res/drawable-xhdpi/ic_smiles_recent_active.png new file mode 100644 index 0000000000..9b270fc31d Binary files /dev/null and b/res/drawable-xhdpi/ic_smiles_recent_active.png differ diff --git a/res/drawable-xhdpi/ic_smiles_smile.png b/res/drawable-xhdpi/ic_smiles_smile.png new file mode 100644 index 0000000000..e367ee6991 Binary files /dev/null and b/res/drawable-xhdpi/ic_smiles_smile.png differ diff --git a/res/drawable-xhdpi/ic_smiles_smile_active.png b/res/drawable-xhdpi/ic_smiles_smile_active.png new file mode 100644 index 0000000000..cc7b338e3e Binary files /dev/null and b/res/drawable-xhdpi/ic_smiles_smile_active.png differ diff --git a/res/drawable-xxhdpi/ic_smiles_backspace.png b/res/drawable-xxhdpi/ic_smiles_backspace.png new file mode 100644 index 0000000000..83d822615c Binary files /dev/null and b/res/drawable-xxhdpi/ic_smiles_backspace.png differ diff --git a/res/drawable-xxhdpi/ic_smiles_backspace_active.png b/res/drawable-xxhdpi/ic_smiles_backspace_active.png new file mode 100644 index 0000000000..6a1900234e Binary files /dev/null and b/res/drawable-xxhdpi/ic_smiles_backspace_active.png differ diff --git a/res/drawable-xxhdpi/ic_smiles_bell.png b/res/drawable-xxhdpi/ic_smiles_bell.png new file mode 100644 index 0000000000..e155c18ef3 Binary files /dev/null and b/res/drawable-xxhdpi/ic_smiles_bell.png differ diff --git a/res/drawable-xxhdpi/ic_smiles_bell_active.png b/res/drawable-xxhdpi/ic_smiles_bell_active.png new file mode 100644 index 0000000000..34be89d5c0 Binary files /dev/null and b/res/drawable-xxhdpi/ic_smiles_bell_active.png differ diff --git a/res/drawable-xxhdpi/ic_smiles_car.png b/res/drawable-xxhdpi/ic_smiles_car.png new file mode 100644 index 0000000000..9b5299f1ed Binary files /dev/null and b/res/drawable-xxhdpi/ic_smiles_car.png differ diff --git a/res/drawable-xxhdpi/ic_smiles_car_active.png b/res/drawable-xxhdpi/ic_smiles_car_active.png new file mode 100644 index 0000000000..03b0b76585 Binary files /dev/null and b/res/drawable-xxhdpi/ic_smiles_car_active.png differ diff --git a/res/drawable-xxhdpi/ic_smiles_flower.png b/res/drawable-xxhdpi/ic_smiles_flower.png new file mode 100644 index 0000000000..0843eb9413 Binary files /dev/null and b/res/drawable-xxhdpi/ic_smiles_flower.png differ diff --git a/res/drawable-xxhdpi/ic_smiles_flower_active.png b/res/drawable-xxhdpi/ic_smiles_flower_active.png new file mode 100644 index 0000000000..ed10b13699 Binary files /dev/null and b/res/drawable-xxhdpi/ic_smiles_flower_active.png differ diff --git a/res/drawable-xxhdpi/ic_smiles_grid.png b/res/drawable-xxhdpi/ic_smiles_grid.png new file mode 100644 index 0000000000..73e324c41f Binary files /dev/null and b/res/drawable-xxhdpi/ic_smiles_grid.png differ diff --git a/res/drawable-xxhdpi/ic_smiles_grid_active.png b/res/drawable-xxhdpi/ic_smiles_grid_active.png new file mode 100644 index 0000000000..de877ff80d Binary files /dev/null and b/res/drawable-xxhdpi/ic_smiles_grid_active.png differ diff --git a/res/drawable-xxhdpi/ic_smiles_recent.png b/res/drawable-xxhdpi/ic_smiles_recent.png new file mode 100644 index 0000000000..9f37782603 Binary files /dev/null and b/res/drawable-xxhdpi/ic_smiles_recent.png differ diff --git a/res/drawable-xxhdpi/ic_smiles_recent_active.png b/res/drawable-xxhdpi/ic_smiles_recent_active.png new file mode 100644 index 0000000000..232d52544f Binary files /dev/null and b/res/drawable-xxhdpi/ic_smiles_recent_active.png differ diff --git a/res/drawable-xxhdpi/ic_smiles_smile.png b/res/drawable-xxhdpi/ic_smiles_smile.png new file mode 100644 index 0000000000..c473c122da Binary files /dev/null and b/res/drawable-xxhdpi/ic_smiles_smile.png differ diff --git a/res/drawable-xxhdpi/ic_smiles_smile_active.png b/res/drawable-xxhdpi/ic_smiles_smile_active.png new file mode 100644 index 0000000000..1981d0c775 Binary files /dev/null and b/res/drawable-xxhdpi/ic_smiles_smile_active.png differ diff --git a/res/drawable/emoji_category_bell.xml b/res/drawable/emoji_category_bell.xml new file mode 100644 index 0000000000..5c3d57160b --- /dev/null +++ b/res/drawable/emoji_category_bell.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/res/drawable/emoji_category_car.xml b/res/drawable/emoji_category_car.xml new file mode 100644 index 0000000000..30c4baa250 --- /dev/null +++ b/res/drawable/emoji_category_car.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/res/drawable/emoji_category_flower.xml b/res/drawable/emoji_category_flower.xml new file mode 100644 index 0000000000..86e934246f --- /dev/null +++ b/res/drawable/emoji_category_flower.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/res/drawable/emoji_category_recent.xml b/res/drawable/emoji_category_recent.xml new file mode 100644 index 0000000000..49c84a7fd2 --- /dev/null +++ b/res/drawable/emoji_category_recent.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/res/drawable/emoji_category_smile.xml b/res/drawable/emoji_category_smile.xml new file mode 100644 index 0000000000..97d0c7a55d --- /dev/null +++ b/res/drawable/emoji_category_smile.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/res/drawable/emoji_category_symbol.xml b/res/drawable/emoji_category_symbol.xml new file mode 100644 index 0000000000..63a97e2f10 --- /dev/null +++ b/res/drawable/emoji_category_symbol.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/res/drawable/ic_emoji_backspace.xml b/res/drawable/ic_emoji_backspace.xml new file mode 100644 index 0000000000..0346dc27c8 --- /dev/null +++ b/res/drawable/ic_emoji_backspace.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/res/drawable/ic_emoji_recent.xml b/res/drawable/ic_emoji_recent.xml new file mode 100644 index 0000000000..aa39b4f504 --- /dev/null +++ b/res/drawable/ic_emoji_recent.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/res/layout/conversation_activity.xml b/res/layout/conversation_activity.xml index 94600c89b1..6d10cae928 100644 --- a/res/layout/conversation_activity.xml +++ b/res/layout/conversation_activity.xml @@ -30,7 +30,7 @@ android:orientation="vertical"> @@ -80,7 +80,7 @@ - + - + - + - - - \ No newline at end of file + \ No newline at end of file diff --git a/res/layout/emoji_grid_layout.xml b/res/layout/emoji_grid_layout.xml index 958cc24422..fe17f6e449 100644 --- a/res/layout/emoji_grid_layout.xml +++ b/res/layout/emoji_grid_layout.xml @@ -5,16 +5,16 @@ android:layout_height="match_parent"> \ No newline at end of file diff --git a/res/values/colors.xml b/res/values/colors.xml index 99278ea4c3..b32daa4770 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -30,4 +30,7 @@ #ff333333 #ffd5d5d5 #ff222222 + + #66ffffff + #11ffffff diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 8d0cb4c2ff..26ee82e64a 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -1,6 +1,10 @@ - 40dip + 36sp + 200dp + 8dp + 2dp + 15dp 3dp 2dp 50dp diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index 6224e994fe..2a0530f99f 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -45,8 +45,8 @@ import android.view.ContextThemeWrapper; import android.view.KeyEvent; import android.view.View; import android.view.View.OnClickListener; +import android.view.View.OnFocusChangeListener; import android.view.View.OnKeyListener; -import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; @@ -188,7 +188,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi initializeReceivers(); initializeResources(); initializeDraft(); - initializeTitleBar(); } @Override @@ -680,7 +679,8 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi for (Draft draft : drafts) { if (draft.getType().equals(Draft.TEXT) && !nativeEmojiSupported) { - composeText.setText(Emoji.getInstance(context).emojify(draft.getValue()), + composeText.setText(Emoji.getInstance(context).emojify(draft.getValue(), + new Emoji.InvalidatingPageLoadedListener(composeText)), TextView.BufferType.SPANNABLE); } else if (draft.getType().equals(Draft.TEXT)) { composeText.setText(draft.getValue()); @@ -781,6 +781,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi composeText.addTextChangedListener(composeKeyPressedListener); composeText.setOnEditorActionListener(sendButtonListener); composeText.setOnClickListener(composeKeyPressedListener); + composeText.setOnFocusChangeListener(composeKeyPressedListener); emojiDrawer.setComposeEditText(composeText); emojiToggle.setOnClickListener(new EmojiToggleListener()); @@ -1137,12 +1138,13 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi public void onClick(View v) { InputMethodManager input = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); - if (emojiDrawer.getVisibility() == View.VISIBLE) { + if (emojiDrawer.isOpen()) { input.showSoftInput(composeText, 0); - emojiDrawer.setVisibility(View.GONE); + emojiDrawer.hide(); } else { input.hideSoftInputFromWindow(composeText.getWindowToken(), 0); - emojiDrawer.setVisibility(View.VISIBLE); + + emojiDrawer.show(); } } } @@ -1164,7 +1166,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi } } - private class ComposeKeyPressedListener implements OnKeyListener, OnClickListener, TextWatcher { + private class ComposeKeyPressedListener implements OnKeyListener, OnClickListener, TextWatcher, OnFocusChangeListener { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_DOWN) { @@ -1194,6 +1196,13 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi public void beforeTextChanged(CharSequence s, int start, int count,int after) {} @Override public void onTextChanged(CharSequence s, int start, int before,int count) {} + + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus && emojiDrawer.isOpen()) { + emojiToggle.performClick(); + } + } } @Override diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java index e76828da93..54fe0aaadd 100644 --- a/src/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/ConversationItem.java @@ -228,13 +228,8 @@ public class ConversationItem extends LinearLayout { } private void setBodyText(MessageRecord messageRecord) { - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - bodyText.setText(Emoji.getInstance(context).emojify(messageRecord.getDisplayBody(), Emoji.EMOJI_LARGE), + bodyText.setText(Emoji.getInstance(context).emojify(messageRecord.getDisplayBody(), new Emoji.InvalidatingPageLoadedListener(bodyText)), TextView.BufferType.SPANNABLE); - } else { - bodyText.setText(messageRecord.getDisplayBody()); - } } private void setContactPhoto(MessageRecord messageRecord) { diff --git a/src/org/thoughtcrime/securesms/ConversationListItem.java b/src/org/thoughtcrime/securesms/ConversationListItem.java index bdd10a9d93..b5f8e660f8 100644 --- a/src/org/thoughtcrime/securesms/ConversationListItem.java +++ b/src/org/thoughtcrime/securesms/ConversationListItem.java @@ -102,13 +102,10 @@ public class ConversationListItem extends RelativeLayout this.recipients.addListener(this); this.fromView.setText(formatFrom(recipients, count, read)); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { this.subjectView.setText(Emoji.getInstance(context).emojify(thread.getDisplayBody(), - Emoji.EMOJI_SMALL), + Emoji.EMOJI_SMALL, + new Emoji.InvalidatingPageLoadedListener(subjectView)), TextView.BufferType.SPANNABLE); - } else { - this.subjectView.setText(thread.getDisplayBody()); - } if (thread.getDate() > 0) this.dateView.setText(DateUtils.getBetterRelativeTimeSpanString(getContext(), thread.getDate())); diff --git a/src/org/thoughtcrime/securesms/components/EmojiDrawer.java b/src/org/thoughtcrime/securesms/components/EmojiDrawer.java index f5a8fa7e8e..57ce0feff6 100644 --- a/src/org/thoughtcrime/securesms/components/EmojiDrawer.java +++ b/src/org/thoughtcrime/securesms/components/EmojiDrawer.java @@ -1,14 +1,14 @@ package org.thoughtcrime.securesms.components; +import android.annotation.TargetApi; import android.content.Context; import android.graphics.drawable.Drawable; +import android.os.Build.VERSION_CODES; import android.support.v4.view.PagerAdapter; -import android.support.v4.view.PagerTabStrip; import android.support.v4.view.ViewPager; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.style.ImageSpan; import android.util.AttributeSet; +import android.util.Log; +import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -19,34 +19,39 @@ import android.widget.EditText; import android.widget.FrameLayout; import android.widget.GridView; import android.widget.ImageView; -import android.widget.TextView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; + +import com.astuetz.PagerSlidingTabStrip; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.util.Emoji; -public class EmojiDrawer extends FrameLayout { +public class EmojiDrawer extends KeyboardAwareLinearLayout { private static final int RECENT_TYPE = 0; private static final int ALL_TYPE = 1; - private FrameLayout emojiGridLayout; - private FrameLayout recentEmojiGridLayout; - private EditText composeText; - private Emoji emoji; - private GridView emojiGrid; - private GridView recentEmojiGrid; - private ViewPager pager; + private FrameLayout[] gridLayouts = new FrameLayout[Emoji.PAGES.length+1]; + private EditText composeText; + private Emoji emoji; + private ViewPager pager; + private PagerSlidingTabStrip strip; + @SuppressWarnings("unused") public EmojiDrawer(Context context) { super(context); initialize(); } + @SuppressWarnings("unused") public EmojiDrawer(Context context, AttributeSet attrs) { super(context, attrs); initialize(); } + @SuppressWarnings("unused") + @TargetApi(VERSION_CODES.HONEYCOMB) public EmojiDrawer(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initialize(); @@ -61,7 +66,7 @@ public class EmojiDrawer extends FrameLayout { } private void initialize() { - LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.emoji_drawer, this, true); initializeResources(); @@ -69,26 +74,48 @@ public class EmojiDrawer extends FrameLayout { } private void initializeResources() { - LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - this.pager = (ViewPager ) findViewById(R.id.emoji_pager); - this.emojiGridLayout = (FrameLayout ) inflater.inflate(R.layout.emoji_grid_layout, null); - this.recentEmojiGridLayout = (FrameLayout ) inflater.inflate(R.layout.emoji_grid_layout, null); - this.emojiGrid = (GridView ) emojiGridLayout.findViewById(R.id.emoji); - this.recentEmojiGrid = (GridView ) recentEmojiGridLayout.findViewById(R.id.emoji); - this.emoji = Emoji.getInstance(getContext()); + this.pager = (ViewPager ) findViewById(R.id.emoji_pager); + this.strip = (PagerSlidingTabStrip ) findViewById(R.id.tabs); + this.emoji = Emoji.getInstance(getContext()); + } + + public void hide() { + setVisibility(View.GONE); + } + + public void show() { + int keyboardHeight = getKeyboardHeight(); + Log.w("EmojiDrawer", "setting emoji drawer to height " + keyboardHeight); + setLayoutParams(new LinearLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, keyboardHeight)); + requestLayout(); + setVisibility(View.VISIBLE); } private void initializeEmojiGrid() { - emojiGrid.setAdapter(new EmojiGridAdapter(ALL_TYPE)); - emojiGrid.setOnItemClickListener(new EmojiClickListener(ALL_TYPE)); - recentEmojiGrid.setAdapter(new EmojiGridAdapter(RECENT_TYPE)); - recentEmojiGrid.setOnItemClickListener(new EmojiClickListener(RECENT_TYPE)); + LayoutInflater inflater = LayoutInflater.from(getContext()); + for (int i = 0; i < gridLayouts.length; i++) { + gridLayouts[i] = (FrameLayout) inflater.inflate(R.layout.emoji_grid_layout, pager, false); + final GridView gridView = (GridView) gridLayouts[i].findViewById(R.id.emoji); + gridLayouts[i].setTag(gridView); + final int type = (i == 0 ? RECENT_TYPE : ALL_TYPE); + gridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.emoji_drawer_size) + 2*getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding)); + gridView.setAdapter(new EmojiGridAdapter(type, i-1)); + gridView.setOnItemClickListener(new EmojiClickListener(ALL_TYPE)); + } pager.setAdapter(new EmojiPagerAdapter()); if (emoji.getRecentlyUsedAssetCount() <= 0) { pager.setCurrentItem(1); } + strip.setTabPaddingLeftRight(getResources().getDimensionPixelSize(R.dimen.emoji_drawer_left_right_padding)); + strip.setAllCaps(false); + strip.setShouldExpand(true); + strip.setUnderlineColorResource(R.color.emoji_tab_underline); + strip.setIndicatorColorResource(R.color.emoji_tab_indicator); + strip.setIndicatorHeight(getResources().getDimensionPixelSize(R.dimen.emoji_drawer_indicator_height)); + + strip.setViewPager(pager); } private class EmojiClickListener implements AdapterView.OnItemClickListener { @@ -101,44 +128,43 @@ public class EmojiDrawer extends FrameLayout { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { - String characters; - - if (type == ALL_TYPE ) characters = emoji.getEmojiUnicode(position); - else characters = emoji.getRecentEmojiUnicode(position); - - int start = composeText.getSelectionStart(); - int end = composeText.getSelectionEnd (); - - composeText.getText().replace(Math.min(start, end), Math.max(start, end), - characters, 0, characters.length()); - - composeText.setText(emoji.emojify(composeText.getText().toString()), - TextView.BufferType.SPANNABLE); - - composeText.setSelection(end+2); - + Integer unicodePoint = (Integer) view.getTag(); + insertEmoji(composeText, unicodePoint); if (type != RECENT_TYPE) { - emoji.setRecentlyUsed(position); - ((BaseAdapter)recentEmojiGrid.getAdapter()).notifyDataSetChanged(); + emoji.setRecentlyUsed(Integer.toHexString(unicodePoint)); + ((BaseAdapter)((GridView)gridLayouts[0].getTag()).getAdapter()).notifyDataSetChanged(); } } + + private void insertEmoji(EditText editText, Integer unicodePoint) { + final char[] chars = Character.toChars(unicodePoint); + String characters = new String(chars); + int start = editText.getSelectionStart(); + int end = editText.getSelectionEnd(); + + CharSequence text = emoji.emojify(characters, new Emoji.InvalidatingPageLoadedListener(composeText)); + editText.getText().replace(Math.min(start, end), Math.max(start, end), text, 0, text.length()); + + editText.setSelection(end+chars.length); + } } private class EmojiGridAdapter extends BaseAdapter { private final int type; + private final int page; private final int emojiSize; - public EmojiGridAdapter(int type) { - this.type = type; - emojiSize = (int) getResources().getDimension(R.dimen.emoji_drawer_size); + public EmojiGridAdapter(int type, int page) { + this.type = type; + this.page = page; + emojiSize = (int) getResources().getDimension(R.dimen.emoji_drawer_size); } @Override public int getCount() { if (type == RECENT_TYPE) return emoji.getRecentlyUsedAssetCount(); - else return emoji.getEmojiAssetCount(); - + else return Emoji.PAGES[page].length; } @Override @@ -148,57 +174,49 @@ public class EmojiDrawer extends FrameLayout { @Override public long getItemId(int position) { - return 0; + return position; } @Override - public View getView(int position, View convertView, ViewGroup parent) { - Drawable drawable; - - if (type == RECENT_TYPE) drawable = emoji.getRecentlyUsed(position); - else drawable = emoji.getEmojiDrawable(position); - + public View getView(final int position, final View convertView, final ViewGroup parent) { + final ImageView view; + final int pad = getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding); if (convertView != null && convertView instanceof ImageView) { - ((ImageView)convertView).setImageDrawable(drawable); - return convertView; + view = (ImageView) convertView; } else { ImageView imageView = new ImageView(getContext()); - imageView.setLayoutParams(new AbsListView.LayoutParams(emojiSize, emojiSize)); - imageView.setImageDrawable(drawable); - return imageView; + imageView.setPadding(pad, pad, pad, pad); + imageView.setLayoutParams(new AbsListView.LayoutParams(emojiSize + 2*pad, emojiSize + 2*pad)); + view = imageView; } + + final Drawable drawable; + final Integer unicodeTag; + if (type == ALL_TYPE) { + unicodeTag = Emoji.PAGES[page][position]; + drawable = emoji.getEmojiDrawable(new Emoji.DrawInfo(page, position), + Emoji.EMOJI_HUGE, + new Emoji.InvalidatingPageLoadedListener(view)); + } else { + Pair recentlyUsed = emoji.getRecentlyUsed(position, + Emoji.EMOJI_HUGE, + new Emoji.InvalidatingPageLoadedListener(view)); + unicodeTag = recentlyUsed.first; + drawable = recentlyUsed.second; + } + + view.setImageDrawable(drawable); + view.setPadding(pad, pad, pad, pad); + view.setTag(unicodeTag); + return view; } } - private class EmojiPagerAdapter extends PagerAdapter { + private class EmojiPagerAdapter extends PagerAdapter implements PagerSlidingTabStrip.IconTabProvider { @Override public int getCount() { - return 2; - } - - @Override - public CharSequence getPageTitle(int position) { - switch (position) { - case 0: - SpannableString recent = new SpannableString(" Recent "); - ImageSpan recentImage = new ImageSpan(getContext(), R.drawable.ic_emoji_recent_light, - ImageSpan.ALIGN_BASELINE); - - recent.setSpan(recentImage, 1, recent.length()-1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - - return recent; - case 1: - SpannableString emoji = new SpannableString(" Emoji "); - ImageSpan emojiImage = new ImageSpan(getContext(), R.drawable.ic_emoji_light, - ImageSpan.ALIGN_BASELINE); - - emoji.setSpan(emojiImage, 1, emoji.length()-1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - - return emoji; - default: - throw new AssertionError("Bad position!"); - } + return gridLayouts.length; } @Override @@ -207,17 +225,31 @@ public class EmojiDrawer extends FrameLayout { } public Object instantiateItem(ViewGroup container, int position) { - View view; + if (position < 0 || position >= gridLayouts.length) + throw new AssertionError("position out of range!"); - switch (position) { - case 0: view = recentEmojiGridLayout; break; - case 1: view = emojiGridLayout; break; - default: throw new AssertionError("Too many positions!"); + container.addView(gridLayouts[position], 0); + + return gridLayouts[position]; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + Log.w("EmojiDrawer", "destroying item at " + position); + container.removeView(gridLayouts[position]); + } + + @Override + public int getPageIconResId(int i) { + switch (i) { + case 0: return R.drawable.emoji_category_recent; + case 1: return R.drawable.emoji_category_smile; + case 2: return R.drawable.emoji_category_flower; + case 3: return R.drawable.emoji_category_bell; + case 4: return R.drawable.emoji_category_car; + case 5: return R.drawable.emoji_category_symbol; + default: return 0; } - - container.addView(view, 0); - - return view; } } } diff --git a/src/org/thoughtcrime/securesms/components/KeyboardAwareLinearLayout.java b/src/org/thoughtcrime/securesms/components/KeyboardAwareLinearLayout.java new file mode 100644 index 0000000000..810f39d5fe --- /dev/null +++ b/src/org/thoughtcrime/securesms/components/KeyboardAwareLinearLayout.java @@ -0,0 +1,137 @@ +/** + * 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 . + */ +package org.thoughtcrime.securesms.components; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.graphics.Rect; +import android.os.Build; +import android.preference.PreferenceManager; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Surface; +import android.view.WindowManager; +import android.widget.LinearLayout; + +import org.thoughtcrime.securesms.R; + +/** + * LinearLayout that, when a view container, will report back when it thinks a soft keyboard + * has been opened and what its height would be. + */ +public class KeyboardAwareLinearLayout extends LinearLayout { + private static final String TAG = KeyboardAwareLinearLayout.class.getSimpleName(); + private static final Rect rect = new Rect(); + + public KeyboardAwareLinearLayout(Context context) { + super(context); + } + + public KeyboardAwareLinearLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public KeyboardAwareLinearLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * inspired by http://stackoverflow.com/a/7104303 + * @param widthMeasureSpec width measure + * @param heightMeasureSpec height measure + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int res = getResources().getIdentifier("status_bar_height", "dimen", "android"); + int statusBarHeight = res > 0 ? getResources().getDimensionPixelSize(res) : 0; + + final int availableHeight = this.getRootView().getHeight() - statusBarHeight; + getWindowVisibleDisplayFrame(rect); + + final int keyboardHeight = availableHeight - (rect.bottom - rect.top); + + if (keyboardHeight > getResources().getDimensionPixelSize(R.dimen.min_emoji_drawer_height)) { + onKeyboardShown(keyboardHeight); + } + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + protected void onKeyboardShown(int keyboardHeight) { + Log.w(TAG, "keyboard shown, height " + keyboardHeight); + + WindowManager wm = (WindowManager) getContext().getSystemService(Activity.WINDOW_SERVICE); + if (wm == null || wm.getDefaultDisplay() == null) { + return; + } + int rotation = wm.getDefaultDisplay().getRotation(); + + switch (rotation) { + case Surface.ROTATION_270: + case Surface.ROTATION_90: + setKeyboardLandscapeHeight(keyboardHeight); + break; + case Surface.ROTATION_0: + case Surface.ROTATION_180: + setKeyboardPortraitHeight(keyboardHeight); + } + } + + public int getKeyboardHeight() { + WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE); + if (wm == null || wm.getDefaultDisplay() == null) { + throw new AssertionError("WindowManager was null or there is no default display"); + } + + int rotation = wm.getDefaultDisplay().getRotation(); + + switch (rotation) { + case Surface.ROTATION_270: + case Surface.ROTATION_90: + return getKeyboardLandscapeHeight(); + case Surface.ROTATION_0: + case Surface.ROTATION_180: + default: + return getKeyboardPortraitHeight(); + } + } + + private int getKeyboardLandscapeHeight() { + return PreferenceManager.getDefaultSharedPreferences(getContext()) + .getInt("keyboard_height_landscape", + getResources().getDimensionPixelSize(R.dimen.min_emoji_drawer_height)); + } + + private int getKeyboardPortraitHeight() { + return PreferenceManager.getDefaultSharedPreferences(getContext()) + .getInt("keyboard_height_portrait", + getResources().getDimensionPixelSize(R.dimen.min_emoji_drawer_height)); + } + + private void setKeyboardLandscapeHeight(int height) { + PreferenceManager.getDefaultSharedPreferences(getContext()) + .edit().putInt("keyboard_height_landscape", height).apply(); + } + + private void setKeyboardPortraitHeight(int height) { + PreferenceManager.getDefaultSharedPreferences(getContext()) + .edit().putInt("keyboard_height_portrait", height).apply(); + } + +} diff --git a/src/org/thoughtcrime/securesms/util/BitmapUtil.java b/src/org/thoughtcrime/securesms/util/BitmapUtil.java index b62192c26f..a3e8db9fa0 100644 --- a/src/org/thoughtcrime/securesms/util/BitmapUtil.java +++ b/src/org/thoughtcrime/securesms/util/BitmapUtil.java @@ -47,33 +47,48 @@ public class BitmapUtil { else throw new IOException("Unable to scale image below: " + baos.size()); } - public static Bitmap createScaledBitmap(InputStream measure, InputStream data, - int maxWidth, int maxHeight) + public static Bitmap createScaledBitmap(InputStream measure, InputStream data, float scale) throws BitmapDecodingException { - BitmapFactory.Options options = getImageDimensions(measure); - int imageWidth = options.outWidth; - int imageHeight = options.outHeight; + final BitmapFactory.Options options = getImageDimensions(measure); + final int outWidth = (int)(options.outWidth * scale); + final int outHeight = (int)(options.outHeight * scale); + Log.w("BitmapUtil", "creating scaled bitmap with scale " + scale + " => " + outWidth + "x" + outHeight); + return createScaledBitmap(data, outWidth, outHeight, options); + } + + public static Bitmap createScaledBitmap(InputStream measure, InputStream data, + int maxWidth, int maxHeight) + throws BitmapDecodingException + { + final BitmapFactory.Options options = getImageDimensions(measure); + return createScaledBitmap(data, maxWidth, maxHeight, options); + } + + private static Bitmap createScaledBitmap(InputStream data, + int maxWidth, int maxHeight, BitmapFactory.Options options) + throws BitmapDecodingException + { + final int imageWidth = options.outWidth; + final int imageHeight = options.outHeight; int scaler = 1; - while ((imageWidth / scaler > maxWidth) && (imageHeight / scaler > maxHeight)) + while ((imageWidth / scaler / 2 >= maxWidth) && (imageHeight / scaler / 2 >= maxHeight)) scaler *= 2; - if (scaler > 1) - scaler /= 2; - options.inSampleSize = scaler; options.inJustDecodeBounds = false; Bitmap roughThumbnail = BitmapFactory.decodeStream(new BufferedInputStream(data), null, options); - + Log.w("BitmapUtil", "rough scale " + (imageWidth) + "x" + (imageHeight) + + " => " + (options.outWidth) + "x" + (options.outHeight)); if (roughThumbnail == null) { throw new BitmapDecodingException("Decoded stream was null."); } - if (roughThumbnail.getWidth() > maxWidth || roughThumbnail.getHeight() > maxHeight) { - float aspectWidth, aspectHeight; + if (options.outWidth > maxWidth || options.outHeight > maxHeight) { + final float aspectWidth, aspectHeight; if (imageWidth == 0 || imageHeight == 0) { aspectWidth = maxWidth; @@ -86,7 +101,8 @@ public class BitmapUtil { aspectWidth = (aspectHeight / options.outHeight) * options.outWidth; } - Log.w("BitmapUtil", "Scaling to max width and height: " + aspectWidth + "," + aspectHeight); + Log.w("BitmapUtil", "fine scale " + options.outWidth + "x" + options.outHeight + + " => " + aspectWidth + "x" + aspectHeight); Bitmap scaledThumbnail = Bitmap.createScaledBitmap(roughThumbnail, (int)aspectWidth, (int)aspectHeight, true); if (roughThumbnail != scaledThumbnail) roughThumbnail.recycle(); return scaledThumbnail; diff --git a/src/org/thoughtcrime/securesms/util/Emoji.java b/src/org/thoughtcrime/securesms/util/Emoji.java index 67a8d3b7c6..895d640af0 100644 --- a/src/org/thoughtcrime/securesms/util/Emoji.java +++ b/src/org/thoughtcrime/securesms/util/Emoji.java @@ -3,37 +3,173 @@ package org.thoughtcrime.securesms.util; import android.content.Context; import android.content.SharedPreferences; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.BitmapDrawable; +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.os.Build; import android.preference.PreferenceManager; import android.text.Spannable; import android.text.SpannableString; import android.text.style.ImageSpan; -import android.util.DisplayMetrics; import android.util.Log; +import android.util.Pair; +import android.util.SparseArray; +import android.view.View; import com.google.thoughtcrimegson.Gson; import com.google.thoughtcrimegson.reflect.TypeToken; import org.thoughtcrime.securesms.R; -import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.Type; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; +import java.util.concurrent.ExecutorService; import java.util.regex.Matcher; import java.util.regex.Pattern; public class Emoji { + private static ExecutorService executor = Util.newSingleThreadedLifoExecutor(); + + public static final int[][] PAGES = { + { + 0x263a, 0x1f60a, 0x1f600, 0x1f601, 0x1f602, 0x1f603, 0x1f604, 0x1f605, + 0x1f606, 0x1f607, 0x1f608, 0x1f609, 0x1f62f, 0x1f610, 0x1f611, 0x1f615, + 0x1f620, 0x1f62c, 0x1f621, 0x1f622, 0x1f634, 0x1f62e, 0x1f623, 0x1f624, + 0x1f625, 0x1f626, 0x1f627, 0x1f628, 0x1f629, 0x1f630, 0x1f61f, 0x1f631, + 0x1f632, 0x1f633, 0x1f635, 0x1f636, 0x1f637, 0x1f61e, 0x1f612, 0x1f60d, + 0x1f61b, 0x1f61c, 0x1f61d, 0x1f60b, 0x1f617, 0x1f619, 0x1f618, 0x1f61a, + 0x1f60e, 0x1f62d, 0x1f60c, 0x1f616, 0x1f614, 0x1f62a, 0x1f60f, 0x1f613, + 0x1f62b, 0x1f64b, 0x1f64c, 0x1f64d, 0x1f645, 0x1f646, 0x1f647, 0x1f64e, + 0x1f64f, 0x1f63a, 0x1f63c, 0x1f638, 0x1f639, 0x1f63b, 0x1f63d, 0x1f63f, + 0x1f63e, 0x1f640, 0x1f648, 0x1f649, 0x1f64a, 0x1f4a9, 0x1f476, 0x1f466, + 0x1f467, 0x1f468, 0x1f469, 0x1f474, 0x1f475, 0x1f48f, 0x1f491, 0x1f46a, + 0x1f46b, 0x1f46c, 0x1f46d, 0x1f464, 0x1f465, 0x1f46e, 0x1f477, 0x1f481, + 0x1f482, 0x1f46f, 0x1f470, 0x1f478, 0x1f385, 0x1f47c, 0x1f471, 0x1f472, + 0x1f473, 0x1f483, 0x1f486, 0x1f487, 0x1f485, 0x1f47b, 0x1f479, 0x1f47a, + 0x1f47d, 0x1f47e, 0x1f47f, 0x1f480, 0x1f4aa, 0x1f440, 0x1f442, 0x1f443, + 0x1f463, 0x1f444, 0x1f445, 0x1f48b, 0x2764, 0x1f499, 0x1f49a, 0x1f49b, + 0x1f49c, 0x1f493, 0x1f494, 0x1f495, 0x1f496, 0x1f497, 0x1f498, 0x1f49d, + 0x1f49e, 0x1f49f, 0x1f44d, 0x1f44e, 0x1f44c, 0x270a, 0x270c, 0x270b, + 0x1f44a, 0x261d, 0x1f446, 0x1f447, 0x1f448, 0x1f449, 0x1f44b, 0x1f44f, + 0x1f450 + }, + { + 0x1f530, 0x1f484, 0x1f45e, 0x1f45f, 0x1f451, 0x1f452, 0x1f3a9, 0x1f393, + 0x1f453, 0x231a, 0x1f454, 0x1f455, 0x1f456, 0x1f457, 0x1f458, 0x1f459, + 0x1f460, 0x1f461, 0x1f462, 0x1f45a, 0x1f45c, 0x1f4bc, 0x1f392, 0x1f45d, + 0x1f45b, 0x1f4b0, 0x1f4b3, 0x1f4b2, 0x1f4b5, 0x1f4b4, 0x1f4b6, 0x1f4b7, + 0x1f4b8, 0x1f4b1, 0x1f4b9, 0x1f52b, 0x1f52a, 0x1f4a3, 0x1f489, 0x1f48a, + 0x1f6ac, 0x1f514, 0x1f515, 0x1f6aa, 0x1f52c, 0x1f52d, 0x1f52e, 0x1f526, + 0x1f50b, 0x1f50c, 0x1f4dc, 0x1f4d7, 0x1f4d8, 0x1f4d9, 0x1f4da, 0x1f4d4, + 0x1f4d2, 0x1f4d1, 0x1f4d3, 0x1f4d5, 0x1f4d6, 0x1f4f0, 0x1f4db, 0x1f383, + 0x1f384, 0x1f380, 0x1f381, 0x1f382, 0x1f388, 0x1f386, 0x1f387, 0x1f389, + 0x1f38a, 0x1f38d, 0x1f38f, 0x1f38c, 0x1f390, 0x1f38b, 0x1f38e, 0x1f4f1, + 0x1f4f2, 0x1f4df, 0x260e, 0x1f4de, 0x1f4e0, 0x1f4e6, 0x2709, 0x1f4e8, + 0x1f4e9, 0x1f4ea, 0x1f4eb, 0x1f4ed, 0x1f4ec, 0x1f4ee, 0x1f4e4, 0x1f4e5, + 0x1f4ef, 0x1f4e2, 0x1f4e3, 0x1f4e1, 0x1f4ac, 0x1f4ad, 0x2712, 0x270f, + 0x1f4dd, 0x1f4cf, 0x1f4d0, 0x1f4cd, 0x1f4cc, 0x1f4ce, 0x2702, 0x1f4ba, + 0x1f4bb, 0x1f4bd, 0x1f4be, 0x1f4bf, 0x1f4c6, 0x1f4c5, 0x1f4c7, 0x1f4cb, + 0x1f4c1, 0x1f4c2, 0x1f4c3, 0x1f4c4, 0x1f4ca, 0x1f4c8, 0x1f4c9, 0x26fa, + 0x1f3a1, 0x1f3a2, 0x1f3a0, 0x1f3aa, 0x1f3a8, 0x1f3ac, 0x1f3a5, 0x1f4f7, + 0x1f4f9, 0x1f3a6, 0x1f3ad, 0x1f3ab, 0x1f3ae, 0x1f3b2, 0x1f3b0, 0x1f0cf, + 0x1f3b4, 0x1f004, 0x1f3af, 0x1f4fa, 0x1f4fb, 0x1f4c0, 0x1f4fc, 0x1f3a7, + 0x1f3a4, 0x1f3b5, 0x1f3b6, 0x1f3bc, 0x1f3bb, 0x1f3b9, 0x1f3b7, 0x1f3ba, + 0x1f3b8, 0x303d + }, + { + 0x1f415, 0x1f436, 0x1f429, 0x1f408, 0x1f431, 0x1f400, 0x1f401, 0x1f42d, + 0x1f439, 0x1f422, 0x1f407, 0x1f430, 0x1f413, 0x1f414, 0x1f423, 0x1f424, + 0x1f425, 0x1f426, 0x1f40f, 0x1f411, 0x1f410, 0x1f43a, 0x1f403, 0x1f402, + 0x1f404, 0x1f42e, 0x1f434, 0x1f417, 0x1f416, 0x1f437, 0x1f43d, 0x1f438, + 0x1f40d, 0x1f43c, 0x1f427, 0x1f418, 0x1f428, 0x1f412, 0x1f435, 0x1f406, + 0x1f42f, 0x1f43b, 0x1f42b, 0x1f42a, 0x1f40a, 0x1f433, 0x1f40b, 0x1f41f, + 0x1f420, 0x1f421, 0x1f419, 0x1f41a, 0x1f42c, 0x1f40c, 0x1f41b, 0x1f41c, + 0x1f41d, 0x1f41e, 0x1f432, 0x1f409, 0x1f43e, 0x1f378, 0x1f37a, 0x1f37b, + 0x1f377, 0x1f379, 0x1f376, 0x2615, 0x1f375, 0x1f37c, 0x1f374, 0x1f368, + 0x1f367, 0x1f366, 0x1f369, 0x1f370, 0x1f36a, 0x1f36b, 0x1f36c, 0x1f36d, + 0x1f36e, 0x1f36f, 0x1f373, 0x1f354, 0x1f35f, 0x1f35d, 0x1f355, 0x1f356, + 0x1f357, 0x1f364, 0x1f363, 0x1f371, 0x1f35e, 0x1f35c, 0x1f359, 0x1f35a, + 0x1f35b, 0x1f372, 0x1f365, 0x1f362, 0x1f361, 0x1f358, 0x1f360, 0x1f34c, + 0x1f34e, 0x1f34f, 0x1f34a, 0x1f34b, 0x1f344, 0x1f345, 0x1f346, 0x1f347, + 0x1f348, 0x1f349, 0x1f350, 0x1f351, 0x1f352, 0x1f353, 0x1f34d, 0x1f330, + 0x1f331, 0x1f332, 0x1f333, 0x1f334, 0x1f335, 0x1f337, 0x1f338, 0x1f339, + 0x1f340, 0x1f341, 0x1f342, 0x1f343, 0x1f33a, 0x1f33b, 0x1f33c, 0x1f33d, + 0x1f33e, 0x1f33f, 0x2600, 0x1f308, 0x26c5, 0x2601, 0x1f301, 0x1f302, + 0x2614, 0x1f4a7, 0x26a1, 0x1f300, 0x2744, 0x26c4, 0x1f319, 0x1f31e, + 0x1f31d, 0x1f31a, 0x1f31b, 0x1f31c, 0x1f311, 0x1f312, 0x1f313, 0x1f314, + 0x1f315, 0x1f316, 0x1f317, 0x1f318, 0x1f391, 0x1f304, 0x1f305, 0x1f307, + 0x1f306, 0x1f303, 0x1f30c, 0x1f309, 0x1f30a, 0x1f30b, 0x1f30e, 0x1f30f, + 0x1f30d, 0x1f310 + }, + { + 0x1f3e0, 0x1f3e1, 0x1f3e2, 0x1f3e3, 0x1f3e4, 0x1f3e5, 0x1f3e6, 0x1f3e7, + 0x1f3e8, 0x1f3e9, 0x1f3ea, 0x1f3eb, 0x26ea, 0x26f2, 0x1f3ec, 0x1f3ef, + 0x1f3f0, 0x1f3ed, 0x1f5fb, 0x1f5fc, 0x1f5fd, 0x1f5fe, 0x1f5ff, 0x2693, + 0x1f3ee, 0x1f488, 0x1f527, 0x1f528, 0x1f529, 0x1f6bf, 0x1f6c1, 0x1f6c0, + 0x1f6bd, 0x1f6be, 0x1f3bd, 0x1f3a3, 0x1f3b1, 0x1f3b3, 0x26be, 0x26f3, + 0x1f3be, 0x26bd, 0x1f3bf, 0x1f3c0, 0x1f3c1, 0x1f3c2, 0x1f3c3, 0x1f3c4, + 0x1f3c6, 0x1f3c7, 0x1f40e, 0x1f3c8, 0x1f3c9, 0x1f3ca, 0x1f682, 0x1f683, + 0x1f684, 0x1f685, 0x1f686, 0x1f687, 0x24c2, 0x1f688, 0x1f68a, 0x1f68b, + 0x1f68c, 0x1f68d, 0x1f68e, 0x1f68f, 0x1f690, 0x1f691, 0x1f692, 0x1f693, + 0x1f694, 0x1f695, 0x1f696, 0x1f697, 0x1f698, 0x1f699, 0x1f69a, 0x1f69b, + 0x1f69c, 0x1f69d, 0x1f69e, 0x1f69f, 0x1f6a0, 0x1f6a1, 0x1f6a2, 0x1f6a3, + 0x1f681, 0x2708, 0x1f6c2, 0x1f6c3, 0x1f6c4, 0x1f6c5, 0x26f5, 0x1f6b2, + 0x1f6b3, 0x1f6b4, 0x1f6b5, 0x1f6b7, 0x1f6b8, 0x1f689, 0x1f680, 0x1f6a4, + 0x1f6b6, 0x26fd, 0x1f17f, 0x1f6a5, 0x1f6a6, 0x1f6a7, 0x1f6a8, 0x2668, + 0x1f48c, 0x1f48d, 0x1f48e, 0x1f490, 0x1f492, 0xfe4e5, 0xfe4e6, 0xfe4e7, + 0xfe4e8, 0xfe4e9, 0xfe4ea, 0xfe4eb, 0xfe4ec, 0xfe4ed, 0xfe4ee + }, + { + 0x1f51d, 0x1f519, 0x1f51b, 0x1f51c, 0x1f51a, 0x23f3, 0x231b, 0x23f0, + 0x2648, 0x2649, 0x264a, 0x264b, 0x264c, 0x264d, 0x264e, 0x264f, + 0x2650, 0x2651, 0x2652, 0x2653, 0x26ce, 0x1f531, 0x1f52f, 0x1f6bb, + 0x1f6ae, 0x1f6af, 0x1f6b0, 0x1f6b1, 0x1f170, 0x1f171, 0x1f18e, 0x1f17e, + 0x1f4ae, 0x1f4af, 0x1f520, 0x1f521, 0x1f522, 0x1f523, 0x1f524, 0x27bf, + 0x1f4f6, 0x1f4f3, 0x1f4f4, 0x1f4f5, 0x1f6b9, 0x1f6ba, 0x1f6bc, 0x267f, + 0x267b, 0x1f6ad, 0x1f6a9, 0x26a0, 0x1f201, 0x1f51e, 0x26d4, 0x1f192, + 0x1f197, 0x1f195, 0x1f198, 0x1f199, 0x1f193, 0x1f196, 0x1f19a, 0x1f232, + 0x1f233, 0x1f234, 0x1f235, 0x1f236, 0x1f237, 0x1f238, 0x1f239, 0x1f202, + 0x1f23a, 0x1f250, 0x1f251, 0x3299, 0x00ae, 0x00a9, 0x2122, 0x1f21a, + 0x1f22f, 0x3297, 0x2b55, 0x274c, 0x274e, 0x2139, 0x1f6ab, 0x2705, + 0x2714, 0x1f517, 0x2734, 0x2733, 0x2795, 0x2796, 0x2716, 0x2797, + 0x1f4a0, 0x1f4a1, 0x1f4a4, 0x1f4a2, 0x1f525, 0x1f4a5, 0x1f4a8, 0x1f4a6, + 0x1f4ab, 0x1f55b, 0x1f567, 0x1f550, 0x1f55c, 0x1f551, 0x1f55d, 0x1f552, + 0x1f55e, 0x1f553, 0x1f55f, 0x1f554, 0x1f560, 0x1f555, 0x1f561, 0x1f556, + 0x1f562, 0x1f557, 0x1f563, 0x1f558, 0x1f564, 0x1f559, 0x1f565, 0x1f55a, + 0x1f566, 0x2195, 0x2b06, 0x2197, 0x27a1, 0x2198, 0x2b07, 0x2199, + 0x2b05, 0x2196, 0x2194, 0x2934, 0x2935, 0x23ea, 0x23eb, 0x23ec, + 0x23e9, 0x25c0, 0x25b6, 0x1f53d, 0x1f53c, 0x2747, 0x2728, 0x1f534, + 0x1f535, 0x26aa, 0x26ab, 0x1f533, 0x1f532, 0x2b50, 0x1f31f, 0x1f320, + 0x25ab, 0x25aa, 0x25fd, 0x25fe, 0x25fb, 0x25fc, 0x2b1c, 0x2b1b, + 0x1f538, 0x1f539, 0x1f536, 0x1f537, 0x1f53a, 0x1f53b, 0x1f51f, /*0x20e3,*/ + 0x2754, 0x2753, 0x2755, 0x2757, 0x203c, 0x2049, 0x3030, 0x27b0, + 0x2660, 0x2665, 0x2663, 0x2666, 0x1f194, 0x1f511, 0x21a9, 0x1f191, + 0x1f50d, 0x1f512, 0x1f513, 0x21aa, 0x1f510, 0x2611, 0x1f518, 0x1f50e, + 0x1f516, 0x1f50f, 0x1f503, 0x1f500, 0x1f501, 0x1f502, 0x1f504, 0x1f4e7, + 0x1f505, 0x1f506, 0x1f507, 0x1f508, 0x1f509, 0x1f50a + } + }; + + private static final SparseArray offsets; + + static { + offsets = new SparseArray(); + for (int i = 0; i < PAGES.length; i++) { + for (int j = 0; j < PAGES[i].length; j++) { + offsets.put(PAGES[i][j], new DrawInfo(i, j)); + } + } + } + + private static Bitmap[] bitmaps = new Bitmap[PAGES.length]; + private static Emoji instance = null; public synchronized static Emoji getInstance(Context context) { @@ -44,157 +180,287 @@ public class Emoji { return instance; } - private static final Pattern EMOJI_RANGE = Pattern.compile("[\ud83d\ude01-\ud83d\ude4f]"); - public static final double EMOJI_LARGE = 1; - public static final double EMOJI_SMALL = 0.75; - public static final int EMOJI_LARGE_SIZE = 22; + @SuppressWarnings("MalformedRegex") + // 0x20a0-0x32ff 0x1f00-0x1fff 0xfe4e5-0xfe4ee + // |==== misc ====||======== emoticons ========||========= flags ==========| + private static final Pattern EMOJI_RANGE = Pattern.compile("[\\u20a0-\\u32ff\\ud83c\\udc00-\\ud83d\\udeff\\udbb9\\udce5-\\udbb9\\udcee]"); - private final Context context; - private final String[] emojiAssets; - private final Set emojiAssetsSet; - private final BitmapFactory.Options bitmapOptions; - private final int bigDrawSize; + public static final double EMOJI_HUGE = 1.00; + public static final double EMOJI_LARGE = 0.75; + public static final double EMOJI_SMALL = 0.50; + public static final int EMOJI_RAW_SIZE = 128; + public static final int EMOJI_PER_ROW = 16; + + private final Context context; + private final int bigDrawSize; private Emoji(Context context) { - this.context = context.getApplicationContext(); - this.emojiAssets = initializeEmojiAssets(); - this.emojiAssetsSet = new HashSet(); - this.bitmapOptions = initializeBitmapOptions(); - - this.bigDrawSize = scale(EMOJI_LARGE_SIZE); - - Collections.addAll(this.emojiAssetsSet, emojiAssets); + this.context = context.getApplicationContext(); + this.bigDrawSize = context.getResources().getDimensionPixelSize(R.dimen.emoji_drawer_size); } - private int scale(float value) { - return (int)(context.getResources().getDisplayMetrics().density * value); - } - - public int getEmojiAssetCount() { - return emojiAssets.length; - } - - public String getEmojiUnicode(int position) { - return getEmojiUnicodeFromAssetName(emojiAssets[position]); - } - - public String getRecentEmojiUnicode(int position) { - return getEmojiUnicodeFromAssetName(EmojiLRU.getRecentlyUsed(context).get(position)); - } - - public Drawable getEmojiDrawable(int position) { - return getEmojiDrawable(emojiAssets[position]); - } - - public SpannableString emojify(String text) { - return emojify(new SpannableString(text), EMOJI_LARGE); - } - - public SpannableString emojify(SpannableString text, double size) { - if (text.toString().contains("\ud83d")) { - - Matcher matches = EMOJI_RANGE.matcher(text); - - while (matches.find()) { - String resource = Integer.toHexString(matches.group().codePointAt(0)) + ".png"; - - if (emojiAssetsSet.contains(resource)) { - Drawable drawable = getEmojiDrawable(resource); - drawable.setBounds(0, 0, (int)(bigDrawSize*size), - (int)(bigDrawSize*size)); - - ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM); - text.setSpan(imageSpan, matches.start(), matches.end(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - + private void preloadPage(final int page, final PageLoadedListener pageLoadListener) { + executor.submit(new Runnable() { + @Override + public void run() { + try { + loadPage(page); + if (pageLoadListener != null) pageLoadListener.onPageLoaded(); + } catch (IOException ioe) { + Log.w("Emoji", ioe); } } + }); + } + + private void loadPage(int page) throws IOException { + if (page < 0 || page >= PAGES.length) { + throw new IndexOutOfBoundsException("can't load page that doesn't exist"); + } + + if (bitmaps[page] != null) return; + + try { + final String file = "emoji_" + page + "_wrapped.png"; + final InputStream measureStream = context.getAssets().open(file); + final InputStream bitmapStream = context.getAssets().open(file); + + bitmaps[page] = BitmapUtil.createScaledBitmap(measureStream, bitmapStream, (float) bigDrawSize / (float) EMOJI_RAW_SIZE); + } catch (IOException ioe) { + Log.w("Emoji", ioe); + throw ioe; + } catch (BitmapDecodingException bde) { + Log.w("Emoji", bde); + throw new AssertionError("emoji sprite asset is corrupted or android decoding is broken"); + } + } + + public SpannableString emojify(String text, PageLoadedListener pageLoadedListener) { + return emojify(new SpannableString(text), pageLoadedListener); + } + + public SpannableString emojify(SpannableString text, PageLoadedListener pageLoadedListener) { + return emojify(text, EMOJI_LARGE, pageLoadedListener); + } + + public SpannableString emojify(SpannableString text, double size, PageLoadedListener pageLoadedListener) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) return text; + + Matcher matches = EMOJI_RANGE.matcher(text); + + while (matches.find()) { + String resource = Integer.toHexString(matches.group().codePointAt(0)); + + Drawable drawable = getEmojiDrawable(resource, size, pageLoadedListener); + if (drawable != null) { + ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM); + text.setSpan(imageSpan, matches.start(), matches.end(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } } return text; } - public void setRecentlyUsed(int position) { - String assetName = emojiAssets[position]; - EmojiLRU.putRecentlyUsed(context, assetName); + public Pair getRecentlyUsed(int position, double size, PageLoadedListener pageLoadedListener) { + String code = EmojiLRU.getRecentlyUsed(context)[position]; + return new Pair(Integer.parseInt(code, 16), getEmojiDrawable(code, size, pageLoadedListener)); + } + + public void setRecentlyUsed(String emojiCode) { + EmojiLRU.putRecentlyUsed(context, emojiCode); } public int getRecentlyUsedAssetCount() { - return EmojiLRU.getRecentlyUsed(context).size(); + return EmojiLRU.getRecentlyUsedCount(context); } - public Drawable getRecentlyUsed(int position) { - return getEmojiDrawable(EmojiLRU.getRecentlyUsed(context).get(position)); + public Drawable getEmojiDrawable(String emojiCode, double size, PageLoadedListener pageLoadedListener) { + return getEmojiDrawable(offsets.get(Integer.parseInt(emojiCode, 16)), size, pageLoadedListener); } - private String getEmojiUnicodeFromAssetName(String assetName) { - String hexString = assetName.split("\\.")[0]; - Integer unicodePoint = Integer.parseInt(hexString, 16); - return new String(Character.toChars(unicodePoint)); - } - - private Drawable getEmojiDrawable(String assetName) { - try { - Bitmap bitmap = BitmapFactory.decodeStream(context.getAssets().open("emoji" + File.separator + assetName), - null, bitmapOptions); - - bitmap = Bitmap.createScaledBitmap(bitmap, 64, 64, true); - - return new BitmapDrawable(context.getResources(), bitmap); - } catch (IOException e) { - throw new AssertionError(e); + public Drawable getEmojiDrawable(DrawInfo drawInfo, double size, PageLoadedListener pageLoadedListener) { + if (drawInfo == null) { + return null; } - } - - private String[] initializeEmojiAssets() { - try { - return context.getAssets().list("emoji"); - } catch (IOException e) { - Log.w("Emoji", e); - return new String[0]; + final Drawable drawable = new EmojiDrawable(drawInfo, bigDrawSize); + drawable.setBounds(0, 0, (int) ((double) bigDrawSize * size), (int) ((double) bigDrawSize * size)); + if (bitmaps[drawInfo.page] == null) { + preloadPage(drawInfo.page, pageLoadedListener); } - } - - private BitmapFactory.Options initializeBitmapOptions() { - BitmapFactory.Options options = new BitmapFactory.Options(); - - options.inScaled = true; - options.inDensity = DisplayMetrics.DENSITY_MEDIUM; - options.inTargetDensity = context.getResources().getDisplayMetrics().densityDpi; - options.inSampleSize = 1; - options.inJustDecodeBounds = false; - - return options; + return drawable; } private static class EmojiLRU { + private static SharedPreferences prefs = null; + private static LinkedHashSet recentlyUsed = null; + private static final String EMOJI_LRU_PREFERENCE = "pref_popular_emoji"; + private static final int EMOJI_LRU_SIZE = 50; - private static final String EMOJI_LRU_PREFERENCE = "pref_popular_emoji"; - private static final int EMOJI_LRU_SIZE = 10; + private static void initializeCache(Context context) { + if (prefs == null) { + prefs = PreferenceManager.getDefaultSharedPreferences(context); + } - public static List getRecentlyUsed(Context context) { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - String serialized = preferences.getString(EMOJI_LRU_PREFERENCE, "[]"); - Type type = new TypeToken>(){}.getType(); + String serialized = prefs.getString(EMOJI_LRU_PREFERENCE, "[]"); + Type type = new TypeToken>() { + }.getType(); - return new Gson().fromJson(serialized, type); + recentlyUsed = new Gson().fromJson(serialized, type); + } + + public static String[] getRecentlyUsed(Context context) { + if (recentlyUsed == null) initializeCache(context); + return recentlyUsed.toArray(new String[recentlyUsed.size()]); + } + + public static int getRecentlyUsedCount(Context context) { + if (recentlyUsed == null) initializeCache(context); + return recentlyUsed.size(); } public static void putRecentlyUsed(Context context, String asset) { - LinkedHashSet recentlyUsed = new LinkedHashSet(getRecentlyUsed(context)); + if (recentlyUsed == null) initializeCache(context); + if (prefs == null) { + prefs = PreferenceManager.getDefaultSharedPreferences(context); + } + recentlyUsed.add(asset); - if (recentlyUsed.size() > 10) { + if (recentlyUsed.size() > EMOJI_LRU_SIZE) { Iterator iterator = recentlyUsed.iterator(); iterator.next(); iterator.remove(); } - String serialized = new Gson().toJson(recentlyUsed); - PreferenceManager.getDefaultSharedPreferences(context) - .edit() - .putString(EMOJI_LRU_PREFERENCE, serialized) - .apply(); + new AsyncTask() { + + @Override + protected Void doInBackground(Void... params) { + String serialized = new Gson().toJson(recentlyUsed); + prefs.edit() + .putString(EMOJI_LRU_PREFERENCE, serialized) + .apply(); + return null; + } + }.execute(); + + } + } + + public static class EmojiDrawable extends Drawable { + private final int index; + private final int page; + private final int emojiSize; + private static final Paint placeholderPaint; + private static final Paint paint; + private Bitmap bmp; + + static { + paint = new Paint(); + paint.setFilterBitmap(true); + placeholderPaint = new Paint(); + placeholderPaint.setColor(0x55000000); + } + + public EmojiDrawable(DrawInfo info, int emojiSize) { + this.index = info.index; + this.page = info.page; + this.emojiSize = emojiSize; + } + + @Override + public void draw(Canvas canvas) { + if (bitmaps[page] == null) { + Log.w("Emoji", "bitmap for this page was null"); + canvas.drawRect(getBounds(), placeholderPaint); + return; + } + if (bmp == null) { + bmp = bitmaps[page]; + } + + Rect b = copyBounds(); +// int cX = b.centerX(), cY = b.centerY(); +// b.left = cX - emojiSize / 2; +// b.right = cX + emojiSize / 2; +// b.top = cY - emojiSize / 2; +// b.bottom = cY + emojiSize / 2; + + final int row = index / EMOJI_PER_ROW; + final int row_index = index % EMOJI_PER_ROW; + + canvas.drawBitmap(bmp, + new Rect(row_index * emojiSize, + row * emojiSize, + (row_index + 1) * emojiSize, + (row + 1) * emojiSize), + b, + paint); + } + + @Override + public int getOpacity() { + return 0; + } + + @Override + public void setAlpha(int alpha) { } + + @Override + public void setColorFilter(ColorFilter cf) { } + + @Override + public String toString() { + return "EmojiDrawable{" + + "page=" + page + + ", index=" + index + + '}'; + } + } + + public static interface PageLoadedListener { + public void onPageLoaded(); + } + + public static class InvalidatingPageLoadedListener implements PageLoadedListener { + private final View view; + + public InvalidatingPageLoadedListener(final View view) { + this.view = view; + } + + @Override + public void onPageLoaded() { + view.post(new Runnable() { + @Override + public void run() { + view.invalidate(); + } + }); + } + + @Override + public String toString() { + return "InvalidatingPageLoadedListener{}"; + } + } + + public static class DrawInfo { + int page; + int index; + + public DrawInfo(final int page, final int index) { + this.page = page; + this.index = index; + } + + @Override + public String toString() { + return "DrawInfo{" + + "page=" + page + + ", index=" + index + + '}'; } } } diff --git a/src/org/thoughtcrime/securesms/util/Util.java b/src/org/thoughtcrime/securesms/util/Util.java index cbfb6fdf08..d16a1be1b6 100644 --- a/src/org/thoughtcrime/securesms/util/Util.java +++ b/src/org/thoughtcrime/securesms/util/Util.java @@ -161,29 +161,4 @@ public class Util { throw new AssertionError(e); } } - - // public static Bitmap loadScaledBitmap(InputStream src, int targetWidth, int targetHeight) { - // return BitmapFactory.decodeStream(src); - //// BitmapFactory.Options options = new BitmapFactory.Options(); - //// options.inJustDecodeBounds = true; - //// BitmapFactory.decodeStream(src, null, options); - //// - //// Log.w("Util", "Bitmap Origin Width: " + options.outWidth); - //// Log.w("Util", "Bitmap Origin Height: " + options.outHeight); - //// - //// boolean scaleByHeight = - //// Math.abs(options.outHeight - targetHeight) >= - //// Math.abs(options.outWidth - targetWidth); - //// - //// if (options.outHeight * options.outWidth >= targetWidth * targetHeight * 2) { - //// double sampleSize = scaleByHeight ? (double)options.outHeight / (double)targetHeight : (double)options.outWidth / (double)targetWidth; - ////// options.inSampleSize = (int)Math.pow(2d, Math.floor(Math.log(sampleSize) / Math.log(2d))); - //// Log.w("Util", "Sampling by: " + options.inSampleSize); - //// } - //// - //// options.inJustDecodeBounds = false; - //// - //// return BitmapFactory.decodeStream(src, null, options); - // } - }