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